相信每个人都用过 ruby script/generate model xxx.创建自己的generator 可以将一些简单的可重用的代码自动添加到项目代码中。
先看一下原理
建议先下载 restful_authentication 参考
Rails在以下地方查找用户自定义的Generators:
RAILS_ROOT/lib/generators
RAILS_ROOT/vendor/generators
RAILS_ROOT/vendor/plugins/any_subdirectory/generators
~/.rails/generators
以及以_generator为后缀的Gems
我们看看一个例子代码:
class TumblepostGenerator < Rails::Generator::NamedBase
def manifest
record do |m|
m.class_collisions class_name
m.template "app/controllers/controller_template.rb",
"app/controllers/#{file_name}_controller.rb"
m.template "app/models/model_template.rb",
"app/models/#{file_name}.rb"
m.directory File.join('app/views', file_name)
m.template "app/views/form_template.rhtml",
"app/views/#{file_name}/_form.rhtml"
m.template "app/views/view_template.rhtml",
"app/views/#{file_name}/_view.rhtml"
m.readme "POST_GENERATION_REMINDER"
end
end
end
其中Rails::Generator::NamedBase是ruby script/generator后面带参数的,而Rails::Generator::Base是不带参数的
生成的文件会先用ERB解析,这样我们可以自己构建生成文件的模板,如:
class <%= class_name %>Controller < TumblepostController
def new
@thing = <%= class_name %>.new
end
end
如果我们要生成数据库Migration,我们可以这样写:
m.migration_template "db/migrations/migration_template.rb", "db/migrate"
今天我要做的是想把文件上传这一类型做成一个plugin,只要每个项目 generate一下 就能轻松使用。
文件结构如图:

代码如下:
rails_commands.rb(直接抄restful_authentication)
Rails::Generator::Commands::Create.class_eval do
def route_resource(*resources)
resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
sentinel = 'ActionController::Routing::Routes.draw do |map|'
logger.route "map.resource #{resource_list}"
unless options[:pretend]
gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
"#{match}\n map.resource #{resource_list}\n"
end
end
end
end
Rails::Generator::Commands::Destroy.class_eval do
def route_resource(*resources)
resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
look_for = "\n map.resource #{resource_list}\n"
logger.route "map.resource #{resource_list}"
gsub_file 'config/routes.rb', /(#{look_for})/mi, ''
end
end
Rails::Generator::Commands::List.class_eval do
def route_resource(*resources)
resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
logger.route "map.resource #{resource_list}"
end
end
attached_generator.rb
require 'rails_commands'
#generate attached attachment
class AttachedGenerator < Rails::Generator::NamedBase
default_options :skip_migration => false
attr_reader :model_controller_name,
:model_controller_class_path,
:model_controller_file_path,
:model_controller_class_nesting,
:model_controller_class_nesting_depth,
:model_controller_class_name,
:model_controller_singular_name,
:model_controller_plural_name
alias_method :model_controller_file_name, :model_controller_singular_name
alias_method :model_controller_table_name, :model_controller_plural_name
def initialize(runtime_args, runtime_options = {})
super
@model_controller_name = @name.pluralize
#@class_name = @name.camelize
# model controller
base_name, @model_controller_class_path, @model_controller_file_path, @model_controller_class_nesting, @model_controller_class_nesting_depth = extract_modules(@model_controller_name)
@model_controller_class_name_without_nesting, @model_controller_singular_name, @model_controller_plural_name = inflect_names(base_name)
if @model_controller_class_nesting.empty?
@model_controller_class_name = @model_controller_class_name_without_nesting
else
@model_controller_class_name = "#{@model_controller_class_nesting}::#{@model_controller_class_name_without_nesting}"
end
p base_name
self.instance_variables.sort.each do |var|
method = var.gsub(/@/,"")
p var => self.send(method) if self.respond_to?( method )
end
p "*" * 80
end
def manifest
recorded_session = record do |m|
# Check for class naming collisions.
m.class_collisions model_controller_class_path, "#{model_controller_class_name}Controller", # Model Controller
"#{model_controller_class_name}Helper"
m.class_collisions [], 'AttachedSystem'
# Controller, helper, views, and test directories.
m.directory File.join('app/models', class_path)
m.directory File.join('app/controllers', model_controller_class_path)
m.directory File.join('app/helpers', model_controller_class_path)
m.directory File.join('app/views', model_controller_class_path, model_controller_file_name)
p '1' * 70
m.template 'model.rb',
File.join('app/models',
class_path,
"#{file_name}.rb")
m.template 'main_image.rb',
File.join('app/models',
class_path,
"main_image.rb")
m.template 'sub_image.rb',
File.join('app/models',
class_path,
"sub_image.rb")
p '2' * 70
m.template 'model_controller.rb',
File.join('app/controllers',
model_controller_class_path,
"#{model_controller_file_name}_controller.rb")
p '3' * 70
m.template 'attached_system.rb',
File.join('lib', 'attached_system.rb')
p '4' * 70
m.template 'model_helper.rb',
File.join('app/helpers',
model_controller_class_path,
"#{model_controller_file_name}_helper.rb")
p '5' * 70
# Controller templates
#m.template 'show.html.erb', File.join('app/views', controller_class_path, controller_file_name, "new.html.erb")
#m.template 'signup.html.erb', File.join('app/views', model_controller_class_path, model_controller_file_name, "new.html.erb")
p '6' * 70
unless options[:skip_migration]
m.migration_template 'migration.rb', 'db/migrate', :assigns => {
:migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
}, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
end
p '7' * 70
m.route_resources model_controller_plural_name
p '8' * 70
end
action = nil
action = $0.split("/")[1]
case action
when "generate"
puts
puts ("-" * 70)
puts "Don't forget to:"
when "destroy"
puts
puts ("-" * 70)
puts
puts "Thanks for using restful_authentication"
puts
puts "Don't forget to comment out the observer line in environment.rb"
puts " (This was optional so it may not even be there)"
puts " # config.active_record.observers = :#{file_name}_observer"
puts
puts ("-" * 70)
puts
else
puts
end
recorded_session
end
protected
# Override with your own usage banner.
def banner
"Usage: #{$0} attached [ModelName]"
end
def add_options!(opt)
opt.separator ''
opt.separator 'Options:'
opt.on("--skip-migration",
"Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
opt.on("--rspec",
"Force rspec mode (checks for RAILS_ROOT/spec by default)") { |v| options[:rspec] = true }
end
end
完成之后就可以使用命令ruby script/generate attached attachment –backtrace 来调用了。
运行结果如下:
D:\app\suitablehouse>ruby script/generate attached attachment --backtrace --skip
-migration
"attachments"
{"@args"=>[]}
{"@class_name"=>"Attachment"}
{"@class_nesting"=>""}
{"@class_nesting_depth"=>0}
{"@class_path"=>[]}
{"@destination_root"=>"D:/app/suitablehouse"}
{"@file_path"=>"attachment"}
{"@model_controller_class_name"=>"Attachments"}
{"@model_controller_class_nesting"=>""}
{"@model_controller_class_nesting_depth"=>0}
{"@model_controller_class_path"=>[]}
{"@model_controller_file_path"=>"attachments"}
{"@model_controller_name"=>"attachments"}
{"@model_controller_plural_name"=>"attachments"}
{"@model_controller_singular_name"=>"attachments"}
{"@name"=>"attachment"}
{"@options"=>{:collision=>:ask, :skip_migration=>true, :quiet=>false, :generator
=>"attached", :command=>:create, :backtrace=>true}}
{"@plural_name"=>"attachments"}
{"@singular_name"=>"attachment"}
{"@source_root"=>"D:/app/suitablehouse/vendor/plugins/restful_attachment/generat
ors/attached/templates"}
{"@table_name"=>"attachments"}
"*******************************************************************************
*"
"1111111111111111111111111111111111111111111111111111111111111111111111"
"2222222222222222222222222222222222222222222222222222222222222222222222"
"3333333333333333333333333333333333333333333333333333333333333333333333"
"4444444444444444444444444444444444444444444444444444444444444444444444"
"5555555555555555555555555555555555555555555555555555555555555555555555"
"6666666666666666666666666666666666666666666666666666666666666666666666"
"7777777777777777777777777777777777777777777777777777777777777777777777"
"8888888888888888888888888888888888888888888888888888888888888888888888"
----------------------------------------------------------------------
Don't forget to:
exists app/models/
exists app/controllers/
exists app/helpers/
exists app/views/attachments
identical app/models/attachment.rb
identical app/models/main_image.rb
identical app/models/sub_image.rb
identical app/controllers/attachments_controller.rb
identical lib/attached_system.rb
identical app/helpers/attachments_helper.rb
route map.resources :attachments
可以看得出来,里面有很多debug的代码,也有一些没有用的代码没有去掉。不过这都是因为我完全没写过generator的缘故。
Rails在以下地方查找用户自定义的Generators:
RAILS_ROOT/lib/generators
RAILS_ROOT/vendor/generators
RAILS_ROOT/vendor/plugins/any_subdirectory/generators
~/.rails/generators
以及以_generator为后缀的Gems
我们看看一个例子代码:
- class TumblepostGenerator < Rails::Generator::NamedBase
- def manifest
- record do |m|
- m.class_collisions class_name
- m.template “app/controllers/controller_template.rb”,
- “app/controllers/#{file_name}_controller.rb”
- m.template “app/models/model_template.rb”,
- “app/models/#{file_name}.rb”
- m.directory File.join(‘app/views’, file_name)
- m.template “app/views/form_template.rhtml”,
- “app/views/#{file_name}/_form.rhtml”
- m.template “app/views/view_template.rhtml”,
- “app/views/#{file_name}/_view.rhtml”
- m.readme “POST_GENERATION_REMINDER”
- end
- end
- end
class TumblepostGenerator < Rails::Generator::NamedBase
def manifest
record do |m|
m.class_collisions class_name
m.template "app/controllers/controller_template.rb",
"app/controllers/#{file_name}_controller.rb"
m.template "app/models/model_template.rb",
"app/models/#{file_name}.rb"
m.directory File.join('app/views', file_name)
m.template "app/views/form_template.rhtml",
"app/views/#{file_name}/_form.rhtml"
m.template "app/views/view_template.rhtml",
"app/views/#{file_name}/_view.rhtml"
m.readme "POST_GENERATION_REMINDER"
end
end
end
其中Rails::Generator::NamedBase是ruby script/generator后面带参数的,而Rails::Generator::Base是不带参数的
生成的文件会先用ERB解析,这样我们可以自己构建生成文件的模板,如:
- class <%= class_name %>Controller < TumblepostController
- def new
- @thing = <%= class_name %>.new
- end
class <%= class_name %>Controller < TumblepostController
def new
@thing = <%= class_name %>.new
end
如果我们要生成数据库Migration,我们可以这样写:
- m.migration_template “db/migrations/migration_template.rb”, “db/migrate”
m.migration_template "db/migrations/migration_template.rb", "db/migrate"
事实上已经有很多Generators创建好并以gems部署了,让我搜索一下: