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>