注意:这个文章只针对Rails3.0.x 对于 后面的版本 , 请看 $ rails plugin new (Enginex is only available for Rails 3.0. *For Rails 3.1 onwards, Enginex was ported to Rails as `rails plugin new` by Piotr Sarnacki.* )
参考: http://coding.smashingmagazine.com/2011/06/23/a-guide-to-starting-your-own-rails-engine-gem/
昨天上午打算发布自己的第一个RAILS GEM (也叫 rails engine gem) ,但是失败了,原因是不知道如何引入,或者扩展RAILS 的controller, views 等等。搜了一些文章,终于找到关键的了,所以一步一步的记录下来。
我们使用 enginex 这个gem, 作者是 Rails 的核心提交人员。这个GEM的作用是使大家对于RAILS ENGINE的开发更加快速,不必关注一些边缘的知识,把精力用在刀刃上~ 。
先看它是如何安装的
$ gem install enginex $ engine audited_controller STEP 1 Creating gem skeleton create create audited_controller.gemspec create Gemfile create MIT-LICENSE create README.rdoc create Rakefile create lib/audited_controller.rb create test create test/audited_controller_test.rb create test/integration/navigation_test.rb create test/support/integration_case.rb create test/test_helper.rb create .gitignore STEP 2 Vendoring Rails application at test/dummy create create README create Rakefile create config.ru create .gitignore ..... STEP 3 Configuring Rails application force test/dummy/config/boot.rb force test/dummy/config/application.rb STEP 4 Removing unneeded files remove test/dummy/.gitignore remove test/dummy/db/seeds.rb remove test/dummy/doc ......
1. 修改 gemspec 文件。加入必要的内容
# audited_controller.gemspec Gem::Specification.new do |s| s.name = "audited_controller" s.summary = "a tool to help auditing the actions of controller." s.description = "easily audit your actions" #s.files = Dir["{app,lib,config}/**/*"] + ["MIT-LICENSE", "Rakefile", "Gemfile", "README.rdoc"] s.version = AuditedController::VERSION s.authors = ["Siwei Shen"] s.email = ["[email protected]"] s.homepage = "siwei.me" s.files = `git ls-files`.split("\n") s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact s.require_path = 'lib' end
2. 编辑 Gemfile
# Gemfile source "http://rubygems.org" gem "rails", ">= 3.0.0" gem "capybara", ">= 0.4.0" gem "sqlite3"
3. 增加3个文件
1. version.rb : # lib/audited_controller/version.rb module AuditedController VERSION='0.0.7' end 2. # lib/audited_controller.rb require 'active_support/dependencies' module AuditedController mattr_accessor :app_root def self.setup yield self end end require 'audited_controller/engine' 3. engine.rb # lib/audited_controller/engine.rb module AuditedController class Engine < Rails::Engine initialize 'audited_controller.load_app_instance_data' do |app| config.app_root = app.root end initialize "team_page.load_static_assets" do |app| app.middleware.use ::ActionDispatch::Static, "#{root}/public" end end end
4. 重点来了: 建立 model. 在这个GEM中,我要用到一个model: audit. 所以,要有一个task 来运行migration:
# file: lib/generators/audited_controller/audited_controller_generator.rb require 'rails/generators' require 'rails/generators/migration' class AuditedControllerGenerator < Rails::Generators::Base include Rails::Generators::Migration def self.source_root @source_root ||= File.join(File.dirname(__FILE__), 'templates') end def self.next_migration_number(dirname) if ActiveRecord::Base.timestamped_migrations Time.new.utc.strftime("%Y%m%d%H%M%S") else "%.3d" % (current_migration_number(dirname) + 1) end end def create_migration_file migration_template 'migration.rb', 'db/migrate/create_audits_table.rb' end end
4.2 还要增加对应的migration 模板文件:
# lib/generators/audited_controller/templates/migration.rb # -*- encoding : utf-8 -*- class CreateAudits< ActiveRecord::Migration def change create_table :audits, :comment => '记录用户操作日志' do |t| t.string :action, :comment => '用户访问的action' t.string :controller, :comment => '用户访问的controller' t.string :description, :comment => '具体描述' t.string :user_name, :comment => '用户名' t.text :params, :comment => 'request的详细参数' t.string :remote_ip, :comment => '用户的IP地址' t.string :restful_method, :comment => 'RESTful method, get, put, post, delete 中的一种' t.timestamps end end end
4.3 还要增加对应的model 文件
# app/models/audited_controller/audit.rb # -*- encoding : utf-8 -*- class Audit < ActiveRecord::Base attr_accessible :action, :controller, :description, :user_name, :params, :remote_ip, :restful_method AUDIT_TYPE_NO_GET = 'no get' AUDIT_TYPE_PUSH = 'push' AUDIT_TYPE_CREATE_MESSAGE = 'create_message' AUDIT_TYPE_UPDATE_MESSAGE = 'update_message' AUDIT_TYPE_APPROVAL = 'approval' end
5. 配置正确的话, rubygem中的 app 和 config 目录是会被自动加载的。所以。... 建立我们的config/routes.rb :
# config/routes.rb Rails.application.routes.draw do resources :audits end
6. 还要加上controllers 啊亲!
# application_controller.rb # -*- encoding : utf-8 -*- class ApplicationController < ActionController::Base puts "== in gem's application_controller" def add_to_audit audit_config = HashWithIndifferentAccess.new(YAML.load(File.read( File.expand_path("#{Rails.root}/config/audits.yml", __FILE__)))) controller = params[:controller] action = params[:action] request_type = restful_method(params) return if !audit_get_request?(audit_config) && request_type == 'get' Audit.create!(action: action, controller: controller, user_name: current_user.login, description: audit_config[controller][action], :params => params.inspect, remote_ip: request.remote_ip, restful_method: restful_method(params) ) end private def audit_get_request?(audit_config) audit_config["audit_get_request"] end # return: get, post, put or delete def restful_method(params) params[:authenticity_token].blank? ? 'get' : ((params[:_method]) || 'post') end end
以及对应的 audits_controller.rb
# app/controllers/application_controller.rb # -*- encoding : utf-8 -*- class AuditsController < ApplicationController before_filter CASClient::Frameworks::Rails::Filter def index @audits = params[:user_name].blank? ? Audit : Audit.where("user_name like ?", "%#{params[:user_name]}%") @audits = case params[:audits_type] when Audit::AUDIT_TYPE_NO_GET then @audits.where("restful_method != 'get'") when Audit::AUDIT_TYPE_PUSH then @audits.where("action = 'confirm_push'") when Audit::AUDIT_TYPE_APPROVAL then @audits.where("action = 'update_approval'") when Audit::AUDIT_TYPE_CREATE_MESSAGE then @audits.where("description = '建立了一条消息'") when Audit::AUDIT_TYPE_UPDATE_MESSAGE then @audits.where("description= '更新消息'") else @audits end @audits = @audits.where("created_at >= ? ", params[:created_at_before]) unless params[:created_at_befo @audits = @audits.where("created_at <= ? ", DateTime.strptime(params[:created_at_after], '%Y-%m-%d').t @audits = @audits.order('created_at desc').page(params[:page]) end end
以及这个controller:
# -*- encoding : utf-8 -*- class AuditsController < ApplicationController before_filter CASClient::Frameworks::Rails::Filter def index @audits = params[:user_name].blank? ? Audit : Audit.where("user_name like ?", "%#{params[:user_name]}%") @audits = case params[:audits_type] when Audit::AUDIT_TYPE_NO_GET then @audits.where("restful_method != 'get'") when Audit::AUDIT_TYPE_PUSH then @audits.where("action = 'confirm_push'") when Audit::AUDIT_TYPE_APPROVAL then @audits.where("action = 'update_approval'") when Audit::AUDIT_TYPE_CREATE_MESSAGE then @audits.where("description = '建立了一条消息'") when Audit::AUDIT_TYPE_UPDATE_MESSAGE then @audits.where("description= '更新消息'") else @audits end @audits = @audits.where("created_at >= ? ", params[:created_at_before]) unless params[:created_at_befo @audits = @audits.where("created_at <= ? ", DateTime.strptime(params[:created_at_after], '%Y-%m-%d').t @audits = @audits.order('created_at desc').page(params[:page]) end end
6 .增加 views
用户的操作日志 <%# render :partial => 'search_form' %> <%# paginate @audits %> <% @audits.each do |audit| %> <% end %>
用户名 | 操作 | 时间 | IP | 详细参数 |
---|---|---|---|---|
<%= audit.user_name %> | <%= audit.description %> | <%= audit.created_at %> | <%= audit.remote_ip %> | <%= audit.params %> |