Rails方法如何像“has_one”一样调用?

Rails方法如何像“has_one”一样调用?

问题描述:

我是PHP开发人员,目前我正在学习Rails(3),当然还有Ruby。 I don't want to believe in magic,所以我尽可能多地了解Rails背后发生的事情。我发现有趣的是ActiveRecord模型中的方法调用如has_onebelongs_toRails方法如何像“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从模块中添加方法,但我仍然不确定如何使用扩展来重现此行为。所以我的问题是:

  1. 究竟发生了什么。我的意思是,仍然不知道has_one方法如何成为类方法?究竟是哪一部分造成的

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

您可以将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? 

耶胡达·通过在途中一些替代品去Rails3中:http://yehudakatz.com/2009/11/12/better-ruby-idioms/

我赞扬你拒绝相信魔术。我高度推荐你得到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