Прочитав ответ jvans ниже и посмотрев на исходный код еще раз, я получаю его сейчас:). И в случае, если кому-то все еще интересно, как работают делегаты рельсов. Все рельсы делают это, создавая новый метод с (module_eval) в файле/классе, из которого вы запускали метод делегата.
Итак, например:
class A
delegate :hello, :to => :b
end
class B
def hello
p hello
end
end
В момент, когда делегат называется rails, будет создан метод hello с (* args, & block) в классе A (технически в файле, в котором написан класс A), и в этом методе все рельсы используют ": to" (который должен быть объектом или классом, который уже определен в классе A), и назначить его локальной переменной _, а затем просто вызывает метод для этого объекта или класса, проходящий в параметрах.
Итак, чтобы делегат работал без создания исключения... с нашим предыдущим примером. Экземпляр A должен иметь переменную экземпляра, ссылающуюся на экземпляр класса B.
class A
attr_accessor :b
def b
@b ||= B.new
end
delegate :hello, :to => :b
end
class B
def hello
p hello
end
end
Это не вопрос о том, как использовать метод делегата в rails, о котором я уже знаю. Мне интересно, как именно "делегировать" делегаты методы: D. В Rails 4 делегат исходного кода определен в основном классе Ruby Module, что делает его доступным как метод класса во всех приложениях rails.
На самом деле мой первый вопрос: каким будет класс Ruby Module? Я имею в виду, что у каждого класса Ruby есть предки > Object > Kernel > BasicObject, и любой модуль в ruby имеет одинаковые предки. Итак, как именно Ruby добавляет методы ко всем классам/модулям ruby, когда кто-то снова открывает класс модуля?
Мой второй вопрос: я понимаю, что метод делегата в rails использует module_eval для фактической делегирования, но я действительно не понимаю, как работает module_eval.
def delegate(*methods)
options = methods.pop
unless options.is_a?(Hash) && to = options[:to]
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
end
prefix, allow_nil = options.values_at(:prefix, :allow_nil)
if prefix == true && to =~ /^[^a-z_]/
raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
end
method_prefix = \
if prefix
"#{prefix == true ? to : prefix}_"
else
''
end
file, line = caller.first.split(':', 2)
line = line.to_i
to = to.to_s
to = 'self.class' if to == 'class'
methods.each do |method|
# Attribute writer methods only accept one argument. Makes sure []=
# methods still accept two arguments.
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
# The following generated methods call the target exactly once, storing
# the returned value in a dummy variable.
#
# Reason is twofold: On one hand doing less calls is in general better.
# On the other hand it could be that the target has side-effects,
# whereas conceptually, from the user point of view, the delegator should
# be doing one call.
if allow_nil
module_eval(<<-EOS, file, line - 3)
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
_ = #{to} # _ = client
if !_.nil? || nil.respond_to?(:#{method}) # if !_.nil? || nil.respond_to?(:name)
_.#{method}(#{definition}) # _.name(*args, &block)
end # end
end # end
EOS
else
exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
module_eval(<<-EOS, file, line - 2)
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
_ = #{to} # _ = client
_.#{method}(#{definition}) # _.name(*args, &block)
rescue NoMethodError => e # rescue NoMethodError => e
if _.nil? && e.name == :#{method} # if _.nil? && e.name == :name
#{exception} # # add helpful message to the exception
else # else
raise # raise
end # end
end # end
EOS
end
end
конец