Después de un largo silencio, volvemos a publicar un post en 4Trabes y no hay mejor manera de hacerlo que desearos a todos una feliz navidad y un próspero 2008. Estas son sólo nuestras segundas navidades y esperamos seguir felicitándoos puntualmente cada año desde este blog.
Mañana comienza la edición 2007 de la Conferencia Rails en la que participamos un año más como ponentes. Nuestra charla “Proyectos de bajo coste con limitaciones severas de tiempo y recursos” comenzará a las 11:15 en la sala 3. Tenéis más información en la web de las conferencias. Que no os sorprenda que en el estrado me acompañe Asís. Aunque no aparece en el programa también va a dar la charla (para que no se hagan tan largos los 60 minutos que debería durar ni a mi ni a los asistentes). Desde aquí pido disculpas a la organización por avisarles de este cambio con tan poco tiempo.
classAdefa'a'enddefaa'aa'endendclassBattr_accessor:the_adefinitialize(the_a)@the_a=the_aenddelegate:a,:to=>:the_aendthe_b=B.new(A.new)the_b.a# => "a"
Interesante, pero con limitaciones. Probemos otras posibiliades:
12345678910
classBdelegate:a2,:to=>:the_aendthe_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:
12345678910
defdelegate(*methods)options=methods.popunlessoptions.is_a?(Hash)&&to=options[:to]raiseArgumentError,"Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."endmethods.eachdo|method|module_eval("def \#{method}(*args, &block)\n\#{to}.__send__(\#{method.inspect}, *args, &block)\nend\n","(__DELEGATION__)",1)endend
Esto no nos agrada del todo, ya que lo interesante sería que se pudiesen escribir cosas como las siguientes sin preocuparse:
123456
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:
123456789101112131415161718192021
classModuledefdelegate_method(method,options)to,to_method=options[:to].to_s.split('.')to_method=methodifto_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_evalcode,__FILE__,__LINE__endend
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 el method_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 nil y 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