搞一个自己的 rails engine gem


rails3发布之后, 开发人员可以把 rails engine 封装为一个干净的 gem, 然后挂载到其他的现有的 rails 程序上. 这个 engine 可以有自己的对象,视图, 控制器, 生成器和公共的静态文件.

这样,除非你想写很多的 code, 这是一个好消息. 因为它能够让你写一个 engine, 然后在其他的地方复用. 比如你构建了很多的小商务应用. 这些应用共同的部分就是需要人员管理系统. 这个地方就是 rails engine 的发挥作用了. 很小的改动就能够添加这个功能到其他应用.

下面看看怎么写一个 engine gem.

#Enginex

Jose Valim, 一个 rails的核心贡献者,创建了一个工具名字叫做`Enginex`, 它能够构建 rails3兼容的骨架. 这个工具为你解决了很多开发人员遇到的问题在创建一个 engine gem 的时候. 它创建了基础配置, 还有测试程序.

刚开始的时候,你需要安装这个这个 gem

```
gem install enginex
enginex team_page
```

通过这个命令. 你就创建了一个` team_page` 的目录, 它包含了标准了 enginex 的目录结构.

##配置

我们这里需要修改一些文件. `team_page.gemspec` 还有 `Gemfile`

默认的内容如下

`team_page.gemspec`

```
# CURRENT FILE :: team_page.gemspec
require File.expand_path("../lib/team_page/version", __FILE__)

# Provide a simple gemspec so that you can easily use your
# Enginex project in your Rails apps through Git.
Gem::Specification.new do |s|F
  s.name                      = "team_page"
  s.version                   = TeamPage::VERSION
  s.platform                  = Gem::Platform::RUBY
  s.authors                   = [ "Your Name" ]
  s.email                     = [ "your@email.com" ]
  s.homepage                  = "http://yourwebsite.com"
  s.description               = "A simple Rails 3 engine gem that adds a team page to any Rails 3 application."
  s.summary                   = "team_page-#{s.version}"

  s.rubyforge_project         = "team_page"
  s.required_rubygems_version = "> 1.3.6"

  s.add_dependency "activesupport" , "~> 3.0.7"
  s.add_dependency "rails"         , "~> 3.0.7"

  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
```

`Gemfile`

```

# CURRENT FILE :: Gemfile
source "http://rubygems.org"
# Specify any dependencies in the gemspec
gemspec
```

这个配置通过`gemspec` 来获取git 上提交的文件, 然后通过` Gemfile` 来读取` gemspec` 的配置,完成`bundle`.


下面通过修改几个文件来让这个 gem 变成 engine.

1. 作为一个 gem, `lib` 目录下的入口文件` team_page.rb`

```
# CURRENT FILE :: lib/team_page.rb
# Requires
require "active_support/dependencies"

module TeamPage

  # Our host application root path
  # We set this when the engine is initialized
  mattr_accessor :app_root

  # Yield self on setup for nice config blocks
  def self.setup
    yield self
  end

end
# 这里
# Require our engine
require "team_page/engine"
```

2. 就是我们的版本文件 `lib/team_page/version.rb`

```

# CURRENT FILE :: lib/team_page/version.rb
module TeamPage
  VERSION = "0.0.1"
end

```

3. 最后就是` lib/team_page/engine.rb`


```
# CURRENT FILE :: lib/team_page/engine.rb
module TeamPage

  class Engine < Rails::Engine

    initialize "team_page.load_app_instance_data" do |app|
      TeamPage.setup do |config|
        config.app_root = app.root
      end
    end

    initialize "team_page.load_static_assets" do |app|
      app.middleware.use ::ActionDispatch::Static, "#{root}/public"
    end

  end

end
```
这个地方定义了2个` Rails` 的初始化块. 它把这个 engine gem 嵌入到了宿主程序中. 还有就是` public` 目录.

##数据对象

在 gem 中间添加对象,需要声明一个迁移和生成器, 它可以把这个对象 copy 到宿主程序. 

-  首先来创建一个生成器:

```
# CURRENT FILE :: lib/generators/team_page/team_page_generator.rb
# Requires
require 'rails/generators'
require 'rails/generators/migration'

class TeamPageGenerator < 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_team_members_table.rb'
  end
end
```
通过这个, 我们可以执行` rails g team_page` 来创建一个迁移到宿主程序.
下面看看迁移的例子:

```
# CURRENT FILE :: lib/generators/team_page/templates/migration.rb
class CreateTeamMembers < ActiveRecord::Migration
  def self.up
    create_table :team_members do |t|
      t.string :name
      t.string :twitter_url
      t.string :bio
      t.string :image_url
      t.timestamps
    end
  end

  def self.down
    drop_table :team_members
  end
end

```

到这里,我们就可以创建自己的对象了, 我们最好给自己的对象追加一个命名空间.

```
# CURRENT FILE :: app/models/team_page/team_member.rb
module TeamPage
  class TeamMember < ActiveRecord::Base
    attr_accessible :name , :twitter_url , :bio , :image_url
  end
end
```
ok, 这里我们已经完成了对于 rails3中 engine gem 的创建. 接下来看看怎么给这个 gem 添加路由,控制器和视图.

##路由

rails engine 其实也是标致的一个 rails 目录结构,所以我们可以很容易的创建属于自己的路由, 直接去` config/routes.rb` 这里就好了.

```
# CURRENT FILE :: config/routes.rb
Rails.application.routes.draw do
  get "team" => "team_page/team#index" , :as => :team_page
end
```

##控制器
控制器代码同样遵循约定,在` app` 目录下的` controllers`, 这里需要注意的一点就是我们都是会用一个 engine 的名字等字符来创建一个文件夹来组织代码. 其实对于代码实际来说,都是加了命名空间的,也就是` module`.

```
# CURRENT FILE :: app/controllers/team_page/team_controller.rb
module TeamPage
  class TeamController < ::ApplicationController
    def index
      @team_members = TeamMember.all
    end
  end
end
```

##视图

不用多说,和上面一样

```
<!-- CURRENT FILE :: app/views/team_page/index.html.erb -->
<ul class="team-member-list">
  <% @team_members.each do |team_member| %>
  <li class="team-member">
    <span class="team-member-name">
      <%= link_to @team_member.name , @team_member.twitter_url %>
    </span>
    <%= @team_member.bio %>
    <%= image_tag @team_member.image_url , :class => "team-member-image" %>
  </li>
  <% end %>
</ul>


```


详细看这里 [A Guide To Starting Your Own Rails Engine Gem](http://www.smashingmagazine.com/2011/06/23/a-guide-to-starting-your-own-rails-engine-gem/)