阅读更多
本文英文原文来自:
http://edgeguides.rubyonrails.org/generators.html
花了两个小时左右将这篇文章译为中文,因为有一些词不是很明确,作为刚刚学习Ruby on Rails的人,只好求大家帮忙在回复里面修改了。
Creating and Customizing Rails Generators & Templates
生成和定制Rails生成器和模板
Rails generators are an essential tool if you plan to improve your workflow. With this guide you will learn how to create generators and customize existing ones.
Rails生成器是一个核心的工具,它可以提高你的工作流。这个向导就是教你创建生成器和定制已有的生成器的。
在这个指南中,你可以:
Learn how to see which generators are available in your application
学习查看你的应用程序里面哪一个生成器是可用的。
Create a generator using templates
使用模板创建一个生成器。
Learn how Rails searches for generators before invoking them
学习Rails在调用生成器前怎样寻找它们。
Customize your scaffold by creating new generators
通过创建生成器定制你的脚手架。
Customize your scaffold by changing generator templates
通过修改生成器模板,定制你的脚手架。
Learn how to use fallbacks to avoid overwriting a huge set of generators
学习怎样使用回退来避免修改一组大量的生成器。
Learn how to create an application template
学习怎样创建一个应用模板。
此指南基于Rails 3,以前的版本不包含在内。
1第一次接触
你使用rails命令建立应用程序时,实际上你使用了Rails的生成器,然后,只要调用rails generate,你会得到可用的生成器列表:
$ rails new myapp
$ cd myapp
$ rails generate
你可以查看Rails生成的所有可用的生成器列表。举例来说,如果你需要helper生成器的详细描述,可以输入:
$ rails generate helper --help
2创建你的第一个生成器
从Rails 3.0开始,生成器基于Thor(http://github.com/wycats/thor)创建。Thor提供了强大的转换选项,以及强大的文件处理API。例如,建立一个生成器,在目录config/initializers生成名为initializer.rb的初始化文件。
第一步是建一个文件lib/generators/initializer_generator.rb,内容如下:
class InitializerGenerator < Rails::Generators::Base
def create_initializer_file
create_file "config/initializers/initializer.rb", "# Add initialization content here"
end
注:create_file是Thor::Actions提供的一个方法,关于它的解释文档和其他相关材料,请见rdoc.infohttp://rdoc.info/github/wycats/thor/master/Thor/Actions
我们的新生成器极其简单:继承自Rails::Generators::Base,有一个方法definition。在生成器被执行时,它里面的每个公共方法都会被执行。
最后,我们调用方法create_file,这将建立一个文件,内容和位置是我们定义的。如果你熟悉Rails Application Templates API,你也会立刻熟悉新的generators API。
调用新的生成器,只需要运行:
$ rails generate initializer
在我们继续之前,让我们看我们全新的生成器描述:
$ rails generate initializer --help
如果一个生成器有命名空间,如同:ActiveRecord::Generators::ModelGenerator一样,Rails通常可以生成完好的描述,但是这种特殊的情况不行。
我们可以用两种方法解决这个问题。一种是在生成器里调用desc:
class InitializerGenerator < Rails::Generators::Base
desc "This generator creates an initializer file at config/initializers"
def create_initializer_file
create_file "config/initializers/initializer.rb", "# Add initialization content here"
end
现在我们在新生成器上使用--help选项可以看到新的描述。第二种加描述的方法是在。在生成器相同的目录里创建一个名为USAGE的文件. 我们准备在下一步这样做。
3用生成器建立生成器
生成器自己也有生成器:
$ rails generate generator initializer
create lib/generators/initializer
create lib/generators/initializer/initializer_generator.rb
create lib/generators/initializer/USAGE
create lib/generators/initializer/templates
这是刚生成的生成器:
class InitializerGenerator < Rails::Generators::NamedBase
source_root File.expand_path("../templates", __FILE__)
end
首先,注意我们是从Rails::Generators::NamedBase继承了一个类,不是从Rails::Generators::Base。就是说我们的生成器需要至少一个初始化名称的参数,参数名name,在程序中可用。
调用这个新的生成器的描述(别忘了删除旧的生成器文件),我们可以看到:
$ rails generate initializer --help
Usage:
rails generate initializer NAME [options]
也可以看见生成器有一个source_root类方法,它指向生成器模板放置的地方,默认是已经存在的目录:lib/generators/initializer/templates。
In order to understand what a generator template means,
为了理解生成器模板的含义,我们创建文件lib/generators/initializer/templates/initializer.rb,内容如下:
# Add initialization content here
现在修改生成器,在调用时拷贝这个模板:
class InitializerGenerator < Rails::Generators::NamedBase
source_root File.expand_path("../templates", __FILE__)
def copy_initializer_file
copy_file "initializer.rb", "config/initializers/#{file_name}.rb"
end
end
运行生成器:
$ rails generate initializer core_extensions
我们可以看到现在一个叫core_extensions的初始化器,用我们的模板的内容,在config/initializers/core_extensions.rb创建了。意味着,源代码里的copy_file确实拷贝了一个文件到给定的目的地址。file_name方法在我们继承了Rails::Generators::NamedBase后,自动生成了。
这些生成器可用的方法在这个指南的最后一章有介绍。
4生成器查找
你运行rails generate initializer core_extensions时,Rails需要按次序查找以下文件,直到找到一个:
rails/generators/initializer/initializer_generator.rb
generators/initializer/initializer_generator.rb
rails/generators/initializer_generator.rb
generators/initializer_generator.rb
如果没找到,就会有错误提示。
上述例子将文件放到应用的库下因为那些目录属于$LOAD_PATH。?
5 定制你的工作流
Rails自身的生成器可灵活定制脚手架,在config/application.rb中修改。
这里是些默认的:
config.generators do |g|
g.orm :active_record
g.template_engine :erb
g.test_framework :test_unit, :fixture => true
end
在定制工作流之前,我们先看看我们的脚手架是什么样子:
$ rails generate scaffold User name:string
invoke active_record
create db/migrate/20091120125558_create_users.rb
create app/models/user.rb
invoke test_unit
create test/unit/user_test.rb
create test/fixtures/users.yml
route resources :users
invoke scaffold_controller
create app/controllers/users_controller.rb
invoke erb
create app/views/users
create app/views/users/index.html.erb
create app/views/users/edit.html.erb
create app/views/users/show.html.erb
create app/views/users/new.html.erb
create app/views/users/_form.html.erb
invoke test_unit
create test/functional/users_controller_test.rb
invoke helper
create app/helpers/users_helper.rb
invoke test_unit
create test/unit/helpers/users_helper_test.rb
invoke stylesheets
create public/stylesheets/scaffold.css
从这个输出可以看出,生成器在Rails3和之前是怎样工作的。脚手架生成器实际不生成什么,只是调用其它的生成器。这使我们可以增删改这些请求。比如,脚手架生成器调用scaffold_controller生成器,scaffold_controller调用erb,test_unit和helper生成器。因为每一个生成器有单独的任务,就容易复用,也避免了复制代码。
我们的第一个自定义工作流,将停止生成stylesheet,测试脚手架紧固(fixture),我们可以通过改变配置得到如下:
config.generators do |g|
g.orm :active_record
g.template_engine :erb
g.test_framework :test_unit, :fixture => false
g.stylesheets false
end
如果我们用这个脚手架生成器生成另一个源代码,我们可以看到不再有stylesheet或者fixture生成了。如果想更加深度定制的话,比如不用Active Record and TestUnit,用DataMapper和Rspec,只要增加他们的gems到你的应用程序里,并配置生成器。
为了示范一下,我们准备建立一个新的helper生成器,简单的加一些实例变量读取。首先在rails命名空间建立一个生成器,rails将以此为提示在这里查找生成器:
$ rails generate generator rails/my_helper
然后可以从新生成器中删除模板目录和source_root类方法,因为不需要了。我们的新生成器就像以下:
class Rails::MyHelperGenerator < Rails::Generators::NamedBase
def create_helper_file
create_file "app/helpers/#{file_name}_helper.rb", <<-FILE
module #{class_name}Helper
attr_reader :#{plural_name}, :#{plural_name.singularize}
end
FILE
end
end
我们为用户建立一个helper,试试新的生成器:
$ rails generate my_helper products
在app/helpers将建立如下helper文件:
module ProductsHelper
attr_reader :products, :product
end
正如预期。再次编辑config/application.rb,我们现在可以告诉脚手架用新的helper生成器:
config.generators do |g|
g.orm :active_record
g.template_engine :erb
g.test_framework :test_unit, :fixture => false
g.stylesheets false
g.helper :my_helper
end
调用生成器时,看到它的动作:
$ rails generate scaffold Post body:text
[...]
invoke my_helper
create app/helpers/posts_helper.rb
注意,我们可以看到,在输出中我们的新helper调用了,而不是Rails的默认调用。不过有一点漏掉了,是新生成器测试,要做到这一点,我们要重新使用旧helper测试生成器。
从Rails3.0后,由于挂钩的概念,这是很容易做到的。我们的新的helper并不需要着重在特定的测试框架,它可以简单地提供一个钩子和一个执行这个钩子的测试框架,以便兼容。
我们可以这样做:
class Rails::MyHelperGenerator < Rails::Generators::NamedBase
def create_helper_file
create_file "app/helpers/#{file_name}_helper.rb", <<-FILE
module #{class_name}Helper
attr_reader :#{plural_name}, :#{plural_name.singularize}
end
FILE
end
hook_for :test_framework
end
现在当调用helper生成器,TestUnit配置成测试框架,它会尝试调用:Rails::TestUnitGenerator and TestUnit::MyHelperGenerator。因为这两个都没有定义,所以我们可以告诉我们自定义的生成器,调用TestUnit::Generators::HelperGenerator。只需要添加:
# Search for :helper instead of :my_helper
hook_for :test_framework, :as => :helper
现在你可以为其它资源再次运行脚手架并看到它也生成了测试!
6用修改模板定制你的工作流
在以上的步骤中,我们仅想加一行到已经生成了的helper,没有任何多出来的功能。要加新功能也很简单,将已有的生成器模板替换掉,在这个例子里是:
Rails::Generators::HelperGenerator.
在Rails 3.0后,生成器不只在源代码root里寻找模板,也在其它路径里寻找,比如lib/templates。
我们想定制Rails::Generators::HelperGenerator,只需用helper.rb的名字,在lib/templates/rails/helper做一个模板拷贝,内容如下:
module <%= class_name %>Helper
attr_reader :<%= plural_name %>, <%= plural_name.singularize %>
end
还原在config/application.rb的最后一个更改:
config.generators do |g|
g.orm :active_record
g.template_engine :erb
g.test_framework :test_unit, :fixture => false
g.stylesheets false
end
如果你生成另一个资源,可以看到确实得到了同样的结果!如果你想定制你的脚手架模板和布局,这就派上用场了,只需要在lib/templates/erb/scaffold创建edit.html.erb, index.html.erb等等。
7 加生成器撤退
生成器的最后一个特性是对插件生成器非常有用的,就是回滚。比如,想象在TestUnit上加一个特性,像shoulda(http://github.com/thoughtbot/shoulda)一样。
因为TestUnit实现了Rails需要的所有生成器,shoulda只是想重写其中的一部分,不需全部覆盖,它就可以告诉Rails如果在Shoulda命名空间里找不到生成器,就用TestUnit生成器。
再次修改config/application.rb,我们可以简单的模仿这个行为:
config.generators do |g|
g.orm :active_record
g.template_engine :erb
g.test_framework :shoulda, :fixture => false
g.stylesheets false
# Add a fallback!
g.fallbacks[:shoulda] = :test_unit
end
现在,如果你建立了一个注释脚手架,可以看到shoulda生成器被调用,在最后,它们都退到TestUnit的生成器了。
$ rails generate scaffold Comment body:text
invoke active_record
create db/migrate/20091120151323_create_comments.rb
create app/models/comment.rb
invoke shoulda
create test/unit/comment_test.rb
create test/fixtures/comments.yml
route map.resources :comments
invoke scaffold_controller
create app/controllers/comments_controller.rb
invoke erb
create app/views/comments
create app/views/comments/index.html.erb
create app/views/comments/edit.html.erb
create app/views/comments/show.html.erb
create app/views/comments/new.html.erb
create app/views/comments/_form.html.erb
create app/views/layouts/comments.html.erb
invoke shoulda
create test/functional/comments_controller_test.rb
invoke my_helper
create app/helpers/comments_helper.rb
invoke shoulda
create test/unit/helpers/comments_helper_test.rb
回退使你的生成器有单独的职责,越来越多的代码重用并减少代码重写。
8应用模板
现在你已经看见了生成器在应用程序里可以被调用,你知道它也可生成应用吗?这种生成器叫做“模板”。
gem("rspec-rails", :group => "test")
gem("cucumber-rails", :group => "test")
if yes?("Would you like to install Devise?")
gem("devise")
generate("devise:install")
model_name = ask("What would you like the user model to be called? [user]")
model_name = "user" if model_name.blank?
generate("devise", model_name)
end
在上面的模板中,我们指定应用程序依赖于rspec-rails和cucumber-rails的gem,这两个东西将被加到Gemfile的测试组里。然后,我们提出一个问题给用户,他们是否想安装设备,如果用户回答“Y”或“yes”,模板将在所有Gemfile组的外面添加设备,运行devise:install生成器。然后这个模板读入用户输入,运行设备生成器,用户对最后一个问题的回答被传给生成器。
modify the outcome of the rails new command by using the -m option and passing in the filename:
想象这个模板在文件template.rb里,使用rails的new命令的时候,使用-m选项,提交文件名,我们可以用它修改new的结果。
$ rails new thud -m template.rb
这个命令将生成Thud应用,执行模板,并输出。
模板不一定要存在本地系统,-m选项支持在线模板。
$ rails new thud -mhttps://gist.github.com/722911.txt
同时最后一个章节不包括生成怎样生成对人类来说最可怕的模板,它将根据你的处理,使你的方法可用,这样你就可以自己开发。这些方法对生成器也同样可用。
9生成器方法
以下对Rails生成器和模板同样适用:
注:本指南不包括Thor提供的方法,在Thor的文档里可以找到它。
9.1 plugin
plugin将安装一个插件到现有应用里。
plugin("dynamic-form", :git => "git://github.com/rails/dynamic-form.git")
Available options are:
? :git – Takes the path to the git repository where this plugin can be found.
? :branch – The name of the branch of the git repository where the plugin is found.
? :submodule – Set to true for the plugin to be installed as a submodule. Defaults to false.
? :svn – Takes the path to the svn repository where this plugin can be found.
? :revision – The revision of the plugin in an SVN repository.
9.2 gem
为应用程序指定一个gem依赖。
gem("rspec", :group => "test", :version => "2.1.0")
gem("devise", "1.1.5")
Available options are:
? :group – The group in the Gemfile where this gem should go.
? :version – The version string of the gem you want to use. Can also be specified as the second argument to the method.
? :git – The URL to the git repository for this gem.
Any additional options passed to this method are put on the end of the line:
gem("devise", :git => "git://github.com/plataformatec/devise", :branch => "master")
The above code will put the following line into Gemfile:
gem "devise", :git => "git://github.com/plataformatec/devise", :branch => "master"
9.3 add_source
加一个特定的源到Gemfile:
add_source "http://gems.github.com"
9.4 application
在应用的类定义之后,直接加入一行到config/application.rb。
application "config.asset_host = 'http://example.com'"
This method can also take a block:
application do
"config.asset_host = 'http://example.com'"
end
Available options are:
? :env – Specify an environment for this configuration option. If you wish to use this option with the block syntax the recommended syntax is as follows:
application(nil, :env => "development") do
"config.asset_host = 'http://localhost:3000'"
end
9.5 git
运行指定的git命令:
git :init
git :add => "."
git :commit => "-m First commit!"
git :add => "onefile.rb", :rm => "badfile.cxx"
The values of the hash here being the arguments or options passed to the specific git command. As per the final example shown here, multiple git commands can be specified at a time, but the order of their running is not guaranteed to be the same as the order that they were specified in.
9.6 vendor
将一个文件放入vendor,包含特定的代码。
vendor("sekrit.rb", '#top secret stuff')
This method also takes a block:
vendor("seeds.rb") do
"puts 'in ur app, seeding ur database'"
end
9.7 lib
将一个文件放入lib,包含特定的代码。
lib("special.rb", 'p Rails.root')
This method also takes a block:
lib("super_special.rb") do
puts "Super special!"
end
9.8 rakefile
在应用的lib/tasks创建一个Rake文件。
rakefile("test.rake", 'hello there')
This method also takes a block:
rakefile("test.rake") do
%Q{
task :rock => :environment do
puts "Rockin'"
end
}
end
9.9 initializer
在应用的config/initializers创建一个initializer文件:
initializer("begin.rb", "puts 'this is the beginning'")
This method also takes a block:
initializer("begin.rb") do
puts "Almost done!"
end
9.10 generate
运行指定的生成器,参数是生成器名,后面的参数将赋给生成器。
generate("scaffold", "forums title:string description:text")
9.11 rake
Runs the specified Rake task.
rake("db:migrate")
Available options are:
? :env – Specifies the environment in which to run this rake task.
? :sudo – Whether or not to run this task using sudo. Defaults to false.
9.12 capify!
在Capistrano运行capify命令,在应用程序的根目录,生成Capistrano配置。
capify!
9.13 route
给文件config/routes.rb加内容:
route("resources :people")
9.14 readme
输出参数中模板源路径的文件内容, 通常是一个README.
readme("README")
10 更改列表
December 1, 2010:加入模板和生成器的可用方法和选项文档,Ryan Bigg
December 1, 2010: 补充Rails应用模板,Ryan Bigg
August 23, 2010: 编辑通过, Xavier Noria
April 30, 2010: José Valim复核
November 20, 2009:第一稿,José Valim