Necesitas algo más flexible que Module#delegate de Rails, entonces Module#delegate_method
Publicado por el Viernes, 26 de Octubre de 2007
Rails a través de una extensión de la clase Module en ActiveSupport permite delegar métodos de manera comoda. Veamos un ejemplo, para entenderlo:
class A
def a
'a'
end
def aa
'aa'
end
end
class B
attr_accessor :the_a
def initialize(the_a)
@the_a = the_a
end
delegate :a, :to => :the_a
end
the_b = B.new(A.new)
the_b.a # => "a"
Interesante, pero con limitaciones. Probemos otras posibiliades:
class B delegate :a2, :to => :the_a end the_b = B.new(A.new) the_b.a2 # => NoMethodError: undefined method `a2' ... the_b = B.new(nil) the_b.a # => NoMethodError: You have a nil object... the_b.a2 # => NoMethodError: You have a nil object...El código causante de este comportamiento tiene la siguiente pinta:
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
methods.each do |method|
module_eval("def \#{method}(*args, &block)\n\#{to}.__send__(\#{method.inspect}, *args, &block)\nend\n", "(__DELEGATION__)", 1)
end
end
Esto no nos agrada del todo, ya que lo interesante sería que se pudiesen escribir cosas como las siguientes sin preocuparse:
p1.product.name # => 'FypURL' p1.product.brand.name # => "Trabe" p1.product.brand_name # => "Trabe" p2.product.name # => "Unbranded product" p2.product.brand.name # => NoMethodError: You have a nil object... p2.product.brand_name # => "unknown"
Aqui os pongo nuestro Module#delegate_method que permite todo esto:
class Module
def delegate_method(method, options)
to,to_method = options[:to].to_s.split('.')
to_method = method if to_method.nil?
default = options[:default]
code = %{
def #{method}(*args, &block)
if #{to}
#{to}.__send__(#{to_method.inspect}, *args, &block) || #{default.inspect}
else
#{default.inspect}
end
end
}
module_eval code, __FILE__, __LINE__
end
end
Esta versión se diferencia de Module#delegate_method anterior en lo siguiente:
No reescribimos el método original ya que la implementación no es compatible al 100% con la anterior. No soportamos la definición de multiples delegaciones y no queremeos que código ya escrito deje de funcionar. Si queremos delegar del modo Rails seguimos teniendo
Module#delegate. La posibilidad de definir varias delegaciones en una sola sentencia no es una característica fundamental (para delegar muchos métodos a lo mejor compensa trastear con elmethod_missing).- No chequeamos la existencia del parametro :to (presuponemos que vamos a escirbir correctamente el código y si no es asís ya saltará otra expceción.
Permitimos delegar en un método diferente al método delegado. Esto es super útil como se vió en el ejemplo de productos y marcas.
Si no existe el delegado o éste no tiene valor obtenemos
nily no una sucia excpeción, o lo que es mejor, obtenemos un valor por defecto.
Probemos de nuevo el código con el que abrimos el post sutituyendo delegate por delegate_method
class B ... delegate_method :a, :to => :the_a, :default => 'default' delegate_method :aa, :to => 'the_a.aa' end the_b = B.new(A.new) the_b.a # => "a" the_b.aa # => "aa" the_b = B.new(nil) the_b.a # => default the_b.aa # => nil

Tu comentario