Rails方法如何像“has_one”一样工作?

时间:2010-11-22 22:53:33

标签: ruby-on-rails ruby ruby-on-rails-3

我是PHP开发人员,目前我正在学习Rails(3),当然还有Ruby。 I don't want to believe in magic所以我尽可能地了解Rails背后发生的事情。我觉得有趣的是ActiveRecord模型中的方法调用,如has_onebelongs_to

我试图重现这一点,并附带了天真的例子:

# has_one_test_1.rb
module Foo
  class Base
    def self.has_one
      puts 'Will it work?'
    end
  end
end

class Model2 < Foo::Base
  has_one
end

运行此文件将输出“它会起作用吗?”,正如我预期的那样。

在搜索rails源代码时,我找到了负责任的功能:def has_one(association_id, options = {})

这怎么可能,因为它显然是一个实例(?)而不是类方法,它应该不起作用。

经过一番研究后,我找到了一个可以回答的例子:

# has_one_test_2.rb
module Foo
  module Bar
    module Baz
      def has_one stuff
        puts "I CAN HAS #{stuff}?"
      end
    end
    def self.included mod
      mod.extend(Baz)
    end
  end
  class Base
    include Bar
  end
end

class Model < Foo::Base
  has_one 'CHEEZBURGER'
end

现在运行has_one_test_2.rb文件将输出I CAN HAS CHEEZBURGER。如果我理解这一点 - 首先发生的事情是Base类试图包含Bar模块。在此包含时,调用self.included方法,该方法使用Baz模块(及其实例has_one方法)扩展Bar模块。因此,本质上has_one方法包含(混合?)到Base类中。但是,我仍然没有完全理解它。 Object#extend从模块中添加了方法,但我仍然不确定如何使用extend重现此行为。所以我的问题是:

  1. 这里究竟发生了什么。我的意思是,还是不知道has_one方法怎么变成类方法?究竟是哪一部分造成的呢?

  2. 这种方法调用(看起来像配置)的可能性非常酷。有没有其他或更简单的方法来实现这一目标?

4 个答案:

答案 0 :(得分:9)

您可以extendinclude模块。

extend将模块中的方法添加为类方法 更简单的示例实现:

module Bar
  def has_one stuff
    puts "I CAN HAS #{stuff}?"
  end
end

class Model
  extend Bar
  has_one 'CHEEZBURGER'
end

include将模块中的方法添加为实例方法

class Model
  include Bar
end
Model.new.has_one 'CHEEZBURGER'

Rails使用它来动态地向你的类添加方法。

例如,您可以使用define_method

module Bar
  def has_one stuff
    define_method(stuff) do
      puts "I CAN HAS #{stuff}?"
    end
  end
end

class Model
  extend Bar
  has_one 'CHEEZBURGER'
end

Model.new.CHEEZBURGER # => I CAN HAS CHEEZBURGER?

答案 1 :(得分:3)

我赞赏你拒绝相信魔法。我高度建议您获取Metaprogramming Ruby本书。我刚刚得到它,它在mah brainz中左右触发顿悟。它涉及许多人们通常称之为“魔术”的东西。一旦它涵盖了所有内容,它就会以Active Record为例向您展示您现在理解的主题。最重要的是,这本书非常容易阅读:它非常容易消化和短暂。

答案 2 :(得分:1)

Yehuda在前往Rails3的途中经历了一些替代方案:http://yehudakatz.com/2009/11/12/better-ruby-idioms/

答案 3 :(得分:0)

此外,您可以使用(通常严重滥用,但有时非常有用)method_missing概念:

class Foo
    def method_missing(method, *arg)
        # Here you are if was some method that wasn't defined before called to an object
        puts "method = #{method.inspect}"
        puts "args = #{arg.inspect}"
        return nil
    end
end

Foo.new.abracadabra(1, 2, 'a')

产量

method = :abracadabra
args = [1, 2, "a"]

通常,这种机制经常被用作

def method_missing(method, *arg)
  case method
  when :has_one
    # implement has_one method
  when :has_many
    # ...
  else
    raise NoMethodError.new
  end
end