上次已经介绍Generator的生成以及工作过程,下面通过源码来详细说明这个过程,先从command/generate.rb文件开始看,
1、rails_generators文件夹下的scripts.rb中的Rails::Generator::Scripts::Base类,截取部分代码:
module Rails
module Generator
module Scripts
# Generator scripts handle command-line invocation. Each script
# responds to an invoke! class method which handles option parsing
# and generator invocation.
class Base
include Options
default_options :collision => :ask, :quiet => false
# Run the generator script. Takes an array of unparsed arguments
# and a hash of parsed arguments, takes the generator as an option
# or first remaining argument, and invokes the requested command.
def run(args = [], runtime_options = {})
begin
parse!(args.dup, runtime_options)
rescue OptionParser::InvalidOption => e
# Don't cry, script. Generators want what you think is invalid.
end
# Generator name is the only required option.
unless options[:generator]
usage if args.empty?
options[:generator] ||= args.shift
end
# Look up generator instance and invoke command on it.
Rails::Generator::Base.instance(options[:generator], args, options).command(options[:command]).invoke!
rescue => e
puts e
puts " #{e.backtrace.join("\n ")}\n" if options[:backtrace]
raise SystemExit
end
#此处省略其他代码
end
这个类里的run方法是Generator的入口,方法的前面部分是解析用户输入的参数。后面Rails::Generator::Base.instance(options[:generator],args,options).command(options[:command]).invoke!,这句话生成一个Commands的实例(它是Generator的委托),调用Commands类中的invoke!方法,invoke!方法执行了manifest.replay(self),manifeast方法在下面第二个类说明中介绍。
module Rails
module Generator
module Commands
# Here's a convenient way to get a handle on generator commands.
# Command.instance('destroy', my_generator) instantiates a Destroy
# delegate of my_generator ready to do your dirty work.
def self.instance(command, generator)
const_get(command.to_s.camelize).new(generator)
end
# Even more convenient access to commands. Include Commands in
# the generator Base class to get a nice #command instance method
# which returns a delegate for the requested command.
def self.included(base)
base.send(:define_method, :command) do |command|
Commands.instance(command, self)
end
end
# Generator commands delegate Rails::Generator::Base and implement
# a standard set of actions. Their behavior is defined by the way
# they respond to these actions: Create brings life; Destroy brings
# death; List passively observes.
#
# Commands are invoked by replaying (or rewinding) the generator's
# manifest of actions. See Rails::Generator::Manifest and
# Rails::Generator::Base#manifest method that generator subclasses
# are required to override.
# Commands allows generators to "plug in" invocation behavior, which
# corresponds to the GoF Strategy pattern.
class Base < DelegateClass(Rails::Generator::Base)
# Replay action manifest. RewindBase subclass rewinds manifest.
def invoke!
manifest.replay(self)
after_generate
end
#此处省略其他代码
2、rails_generators文件夹下的base.rb中的Rails::Generator::Base类是代码生成器的骨架,设置源、目的路径和logger等信息。它有一个子类,NamedBase,比如model,controller等的generator就是继承了这个类。
Base# manifest方法没有实现,需要继承它的generator去实现,这个方法记录了Generator所作的操作,可以看看controller_generator.rb中,就只有这一个方法,里面是创建目录和拷贝文件等操作。
class ControllerGenerator < Rails::Generator::NamedBase
def manifest
record do |m|
# Check for class naming collisions.
m.class_collisions "#{class_name}Controller", "#{class_name}ControllerTest", "#{class_name}Helper", "#{class_name}HelperTest"
# Controller, helper, views, and test directories.
m.directory File.join('app/controllers', class_path)
m.directory File.join('app/helpers', class_path)
m.directory File.join('app/views', class_path, file_name)
m.directory File.join('test/functional', class_path)
m.directory File.join('test/unit/helpers', class_path)
# Controller class, functional test, and helper class.
m.template 'controller.rb',
File.join('app/controllers',
class_path,
"#{file_name}_controller.rb")
m.template 'functional_test.rb',
File.join('test/functional',
class_path,
"#{file_name}_controller_test.rb")
m.template 'helper.rb',
File.join('app/helpers',
class_path,
"#{file_name}_helper.rb")
m.template 'helper_test.rb',
File.join('test/unit/helpers',
class_path,
"#{file_name}_helper_test.rb")
# View template for each action.
actions.each do |action|
path = File.join('app/views', class_path, file_name, "#{action}.html.erb")
m.template 'view.html.erb', path,
:assigns => { :action => action, :path => path }
end
end
end
end
3、rails_generators文件夹下的manifest.rb中的Rails::Generator::Manifest类用来捕获Generator传递的动作,并调用Generator中对应的方法。这里面最重要的方法是Manifest#replay,这个方法通过send_actions调用generator的对应的方法。
module Rails
module Generator
class Manifest
attr_reader :target
# Take a default action target. Yield self if block given.
def initialize(target = nil)
@target, @actions = target, []
yield self if block_given?
end
# Record an action.
def method_missing(action, *args, &block)
@actions << [action, args, block]
end
# Replay recorded actions.
# 真正执行generator传入的action
def replay(target = nil)
send_actions(target || @target, @actions)
end
# Rewind recorded actions.
def rewind(target = nil)
send_actions(target || @target, @actions.reverse)
end
# Erase recorded actions.
def erase
@actions = []
end
private
def send_actions(target, actions)
actions.each do |method, args, block|
target.send(method, *args, &block)
end
end
end
end
4、rails_generators文件夹下的spec.rb中的Rails::Generator::Spec类用来装配指定的Generator,包含了Generator的Source和Templates等信息。
module Rails
module Generator
# A spec knows where a generator was found and how to instantiate it.
# Metadata include the generator's name, its base path, and the source
# which yielded it (PathSource, GemPathSource, etc.)
class Spec
attr_reader :name, :path, :source
def initialize(name, path, source)
@name, @path, @source = name, path, source
end
# Look up the generator class. Require its class file, find the class
# in ObjectSpace, tag it with this spec, and return.
def klass
unless @klass
require class_file
@klass = lookup_class
@klass.spec = self
end
@klass
end
def class_file
"#{path}/#{name}_generator.rb"
end
def class_name
"#{name.camelize}Generator"
end
private
# Search for the first Class descending from Rails::Generator::Base
# whose name matches the requested class name.
def lookup_class
ObjectSpace.each_object(Class) do |obj|
return obj if obj.ancestors.include?(Rails::Generator::Base) and
obj.name.split('::').last == class_name
end
raise NameError, "Missing #{class_name} class in #{class_file}"
end
end
end
klass方法将spec对象的klass域指向对应的generator类。class_file方法是用来查找generator的文件,在生成controller这个例子中就是指controller_generator.rb文件。class_name方法是调用的generator的类名,其中比较重要的方法是lookup_class,这个方法查找ObjectSpace中所有的对象,找出其中是继承自Rails::Generator::Base,同时类的名称又是class_name的对象。
PS:ObjectSpace是一个模块,用来操作所有的对象,它可以销毁对象可以遍历所有的对象,ObjectSpace#each_object方法:若某对象的kind_of?与class_or_module一致时,就对其进行迭代操作. 若省略参数,则对所有对象进行迭代操作.若将class_or_module指定为Fixnum或Symbol的话, 则不会进行任何操作.最后返回迭代操作的次数.
上面介绍的类是在Generator生成模板代码过程中涉及到的主要类,抽取了其中的一些关键方法进行一些说明,真正这些类是如何具体完成模板文件生成的,大家可以具体看看所有的源码。