注意:这个文章只针对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 %> |