Rails方法如何像“has_one”一样调用?
我是PHP开发人员,目前我正在学习Rails(3),当然还有Ruby。 I don't want to believe in magic,所以我尽可能多地了解Rails背后发生的事情。我发现有趣的是ActiveRecord模型中的方法调用如has_one
或belongs_to
。Rails方法如何像“has_one”一样调用?
我试图重现,并用简单的例子来:“请问它的工作”
# 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。如果我理解了这一点 - 首先会发生的是基类尝试包含Bar模块。在包含这个时候,调用self.included
方法,该方法使用Baz模块扩展Bar模块(及其实例has_one
方法)。所以在本质has_one
方法被包括(混合?)到基类。但是,我仍然不完全明白。 Object#extend从模块中添加方法,但我仍然不确定如何使用扩展来重现此行为。所以我的问题是:
究竟发生了什么。我的意思是,仍然不知道has_one方法如何成为类方法?究竟是哪一部分造成的
使这种方法调用(看起来像配置)的这种可能性非常酷。有没有其他更简单的方法来实现这一目标?
您可以将extend
和include
作为模块。
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?
我赞扬你拒绝相信魔术。我高度推荐你得到Metaprogramming Ruby书。我刚刚得到了它,它引发了脑海中左右的顿悟。它经历了许多人们通常称之为“魔术”的东西。一旦它涵盖了所有内容,它就会以Active Record为例向您展示您现在了解的主题。最重要的是,这本书读起来非常简单:它非常容易理解和简短。
此外,你可以使用一个(通常是严重虐待,但有时非常有用)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