Ho un problema (il primo che mi risponde "solo uno" lo banno). Ora che ho la mia classe astratta contenente i campi di default dell'oggetto Result
, devo permettere a ProductResult
di ereditare le variabili d'istanza da Result e fonderle con quelle proprie di ProductResult.
Poiché in Ruby, di default, ogni variabile d'istanza è privata ho necessità di dichiarare anche i metodi getter e setter per ogni proprietà . In poche parole, immaginando che ProductResult contenga le seguenti proprietà
- id (ereditata da Result)
- url (ereditata da Result)
- name
- price
- image
non posso permettermi di dichiarare dinamicamente gli attributi usando attr_accessor all'interno di un metodo. Il seguente codice non è consentito.
class ProductResult < Result def initialize() @fields = %w(name price image) # no! @fields.each do |field| attr_accessor field.to_sym end end end
Come posso allora fare in modo che sia un metodo ad incaricarsi di generare automaticamente questi metodi accessori? Ci sono almeno due soluzioni per risolvere questo quesito di metaprogrammazione.
La prima consiste nello "scrivere" i metodi in una stringa e poi richiamare class_eval
per valutare il contenuto della stringa come codice Ruby.
La seconda alternativa, quella che prendo in esame in questo post, è quella di sfruttare il nostro buon vecchio metodo Kernel#method_missing
per intercettare la chiamata al metodo e magicamente restituire la proprietà quando disponibile.
Come fare? Ecco qui, vi regalo un metodo fresco fresco contenuto nella famosa raccolta di cui vi parlavo nel mio post precedente. Lo potete copiare in una classe o inserirlo in un modulo e "mixarlo" in una libreria esistente.
# # Intercept missing methods. # # This is a convenient method to emulate dynamic attribute accessors. # Kernel#method_missing is a magic mathod automatically invoked whenever # a missing method is called. # A missing method is a method that hasn't been defined before. # # class Foo # def inititialize() # // do something # end # end # # foo = Foo.new() # foo.this_is_a_missing_method() #=> NoMethodError, # # this is a missing method # # Using the Kernel#missing_method method you can intercept any missing method # and create your custom action, including the ability to emulate # any attr_reader and attr_writer methods dynamically. # # # === Arguments: # [+String+ _name_] The name of the missing method # [+Mixed+ _args_] Missing methd arguments # # === Return: # +void+ # def method_missing(name, *args) attr_name = name.to_s.split(/=/).first.to_sym() instance_variable_name = "@#{attr_name}" # perform attr_writer if argument given unless args.empty? instance_variable_set(instance_variable_name, args.first) end # try to return value or forward to parent class instance_variables.include?(instance_variable_name) ? instance_variable_get(instance_variable_name) : super end
A proposito. A questo punto, dovreste già avere la soluzione al quesito precedente. Se così non fosse, sappiate che la libreria di Rails che si basa proprio su method_missing
e su una implementazione simile a quanto sopra descritto è ActiveRecord
.