建立rails engine gem ( how to write a rails engine gem )

访问量: 2192

注意:这个文章只针对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 = ["sg552sg552@gmail.com"]
  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 %>
<%# paginate @audits %> ]]>

订阅/RSS Feed

Subscribe

分类/category