开发者

How does Rails method call like "has_one" work?

I am PHP dev and at the moment I am learning Rails (3) and of course - Ruby. I don't want to believe in magic and so I try to understand as much as I can about things that happen "behind" Rails. What I found interesting are the method calls like has_one or belongs_to in ActiveRecord models.

I tried to reproduce that, and came with naive example:

# 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

Just running this file will output "Will it work?", as I expected.

While searching through rails source I found responsible function: def has_one(association_id, options = {}).

How could this be, because it is obviously an instance (?) and not a class method, it should 开发者_运维百科not work.

After some researching I found an example that could be an answer:

# 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

Now running has_one_test_2.rb file will output I CAN HAS CHEEZBURGER. If I understood this well - first thing that happens is that Base class tries to include Bar module. On the time of this inclusion the self.included method is invoked, which extends Bar module with Baz module (and its instance has_one method). So in the essence has_one method is included (mixed?) into Base class. But still, I don't fully get it. Object#extend adds the method from module but still, I am not sure how to reproduce this behaviour using extend. So my questions are:

  1. What exactly happened here. I mean, still don't know how has_one method become class method? Which part exactly caused it?

  2. This possibility to make this method calls (which looks like configuration) is really cool. Is there an alternative or simpler way to achieve this?


You can extend and include a module.

extend adds the methods from the module as class methods A simpler implementation of your example:

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

class Model
  extend Bar
  has_one 'CHEEZBURGER'
end

include adds the methods from the module as instance methods

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

Rails uses this to dynamically add methods to your class.

For example you could use 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?


I commend you for refusing to believe in the magic. I highly recommend you get the Metaprogramming Ruby book. I just recently got it and it was triggering epiphanies left and right in mah brainz. It goes over many of these things that people commonly refer to as 'magic'. Once it covers them all, it goes over Active Record as an example to show you that you now understand the topics. Best of all, the book reads very easily: it's very digestible and short.


Yehuda went through some alternatives on way to Rails3: http://yehudakatz.com/2009/11/12/better-ruby-idioms/


Moreover, you can use a (usually heavily abused, but sometimes quite useful) method_missing concept:

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')

yields

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

Generally, this mechanism is quite often used as

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜