10 November 2014

前言


最初接触Rails时,就是被其强大的生成器所吸引的,少量的代码 + 几条命令,一个web程序就搭起来了。结果,工作中却很少使用,都是手工创建的,明明命令啊、自动化啊,什么的超棒了。

正文

Rails命令创建程序(rails new app)时,使用了生成器。rails generate列出项目中使用的所有生成器,其中部分列出如下:

Rails: assets controller generator helper `inherited_resources_controller` `integration_test` jbuilder mailer migration model observer `performance_test` resource `responders_controller` scaffold `scaffold_controller` `session_migration` task
ActiveRecord: `active_record:devise` `active_record:migration` `active_record:model` `active_record:observer` `active_record:session_migration` 
Bootstrap: bootstrap:install bootstrap:layout bootstrap:partial bootstrap:themed 
Devise: devise devise:install devise:views
Kaminari: kaminari:config kaminari:views
Mongoid: mongoid:config mongoid:devise
Rspec: rspec:feature rspec:install rspec:job

备注: 一些gem自生也会提供生成器,也就说生成器很常见。

Rails 3.0中,生成器使用Thor开发,命令行解析和文件处理的API。

生成器主要就是在Rails应用程序中lib/generators/目录下,创建类似xxx_generator.rb这样的文件,然后,在其中写入如下的内容:

class InitGenerator < Rails::Generators::Base 
  def create_init_file
    create_file "config/initializers/init.rb", "# Add initialization content here"
  end
end

值得注意的是,生成器所继承的类,以及其使用的Thor方法 - create_file。 其实,生成器就是命令行程序。可以在调用生成器时,添加--help查看说明。

可以使用生成器生成生成器: rails generate generator initializer, 所谓的元编程。生成生成器的生成器继承类稍微不同

class Init1Generator < Rails::Generators::NamedBase # 生成生成器的生成器继承的类
  source_root File.expand_path("../templates", __FILE__)  # 模板文件存放的是生成器使用的模板
end

执行命令rails generate init, Rails希望从如下的目录列表中找到init_generator.rb文件:

  • rails/generators/initializer/
  • generators/initializer/
  • rails/generators/
  • generators/

这些目录的前缀是$LOAD_PATH中的路径,例如lib目录。$LOAD_PATH可以在irb访问,本地的路径如下(RVM的加载路径好复杂):

["/home/xiajian/.rvm/rubies/ruby-1.9.3-p547/lib/ruby/site_ruby/1.9.1", "/home/xiajian/.rvm/rubies/ruby-1.9.3-p547/lib/ruby/site_ruby/1.9.1/x86_64-linux", "/home/xiajian/.rvm/rubies/ruby-1.9.3-p547/lib/ruby/site_ruby", "/home/xiajian/.rvm/rubies/ruby-1.9.3-p547/lib/ruby/vendor_ruby/1.9.1", "/home/xiajian/.rvm/rubies/ruby-1.9.3-p547/lib/ruby/vendor_ruby/1.9.1/x86_64-linux", "/home/xiajian/.rvm/rubies/ruby-1.9.3-p547/lib/ruby/vendor_ruby", "/home/xiajian/.rvm/rubies/ruby-1.9.3-p547/lib/ruby/1.9.1", "/home/xiajian/.rvm/rubies/ruby-1.9.3-p547/lib/ruby/1.9.1/x86_64-linux"]

定制工作流程

Rails自带的生成器允许在config/application.rb中定制脚手架,默认值如下:

config.generators do |g|
  g.orm :active_record
  g.template_engine :erb
  g.test_framework :test_unit, fixture: true
end

脚手架生成器只是调用其他生成器(erb,test_unit,help等)完成操作,定制工作流程,禁止生成样式表、JavaScript和测试固件:

config.generators do |g| 
  g.orm             :active_record
  g.template_engine :erb
  g.test_framework  :test_unit, fixture: false
  g.stylesheets     false
  g.javascripts     false
end

生成生成器的命令: rails generate generator rails/my_helper,其生成器的代码片段:

# lib/generators/rails/my_helper/my_helper_generator.rb 
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, as:  :helper  # 添加辅助方法测试类的框架。
end

备注: HERE文档的作用,可以用作生成代码的字符串模板。

类似如下的代码称为程序模板:

# template.rb,使用: rails new thud -m template.rb
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

thor中大量可在生成器和模板中使用的方法:

Gemfile相关:

  • gem 选项有 :group, :version, :git, :branch
  • gem_group 多个gem包一个分组
  • add_source 代码库的源

其他诸多方法: inject_into_file, gsub_file, application(配置application.rb), git, vendor, lib, rakefile, initializer(在程序config/initializers中新建初始化脚本), generate(运行生成器), rake(运行rake任务), capify!(生成Capistrano配置), route(向路由添加内容), readme




傲娇的使用Disqus