como o método de delegação do rails funciona?
Depois de ler a resposta por jvans abaixo e ver o código-fonte mais um pouco, entendi agora :). E caso alguém ainda esteja se perguntando como exatamente os delegados do Rails funcionam. Tudo o que os rails estão fazendo é criar um novo método com (module_eval) no arquivo / classe a partir do qual você executou o método delegado.
Então, por exemplo:
class A
delegate :hello, :to => :b
end
class B
def hello
p hello
end
end
No momento em que o delegado é chamado de rails, criará um método hello com (* args, & block) na classe A (tecnicamente no arquivo em que a classe A está escrita) e nesse método todos os trilhos usam o valor ": to" (que deve ser um objeto ou uma classe que já esteja definida na classe A) e atribuí-lo a uma variável local _, em seguida, basta chamar o método nesse objeto ou classe que passa nos parâmetros.
Portanto, para que o delegado trabalhe sem gerar uma exceção ... com o exemplo anterior. Uma instância de A já deve ter uma variável de instância referente a uma instância da classe 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
Esta não é uma pergunta sobre "como usar o método delegado em trilhos", que eu já sei. Eu estou querendo saber como exatamente "delegar" delega métodos: D. No código-fonte do Rails 4, delegado é definido na classe principal do Ruby Module, que o torna disponível como método de classe em todos os aplicativos rails.
Na verdade, minha primeira pergunta seria como está incluída a classe Ruby Module? Quero dizer, toda classe Ruby tem ancestrais de> Object> Kernel> BasicObject e qualquer módulo em ruby tem os mesmos ancestrais. Então, como exatamente o ruby adiciona métodos a todas as classes / módulos de ruby quando alguém reabre a classe Module?
Minha segunda pergunta é .. Entendo que o método delegate no rails usa module_eval para a delegação real, mas não entendo realmente como o module_eval funciona.
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
fim