论坛首页 编程语言技术论坛

Rails源码学习(二) 下

浏览 2077 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (1) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-07-13  
上次已经介绍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生成模板代码过程中涉及到的主要类,抽取了其中的一些关键方法进行一些说明,真正这些类是如何具体完成模板文件生成的,大家可以具体看看所有的源码。

论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics