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.
- 作为一个 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"
- 就是我们的版本文件
lib/team_page/version.rb
# CURRENT FILE :: lib/team_page/version.rb
module TeamPage
VERSION = "0.0.1"
end
- 最后就是
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>