浏览 2077 次
锁定老帖子 主题:Rails源码学习(二) 下
精华帖 (0) :: 良好帖 (0) :: 新手帖 (1) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-07-13
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生成模板代码过程中涉及到的主要类,抽取了其中的一些关键方法进行一些说明,真正这些类是如何具体完成模板文件生成的,大家可以具体看看所有的源码。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |