Ruby Diary #12: Ruby attr_accessor dinamici
Lunedì 19 Novembre 2007 - 08:16
di Simone Carletti

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.
Commenti
1
Ciao.
Mmh… forse non ho capito esattamente il problema che hai esposto in questo post :) Se l’obiettivo era quello di mettere a disposizione della classe figlia le variabili della classe padre si poteva benissimo fare così:
# Classe Result class Result attr_accessor :id attr_accessor :url end # Classe ProductResult class ProductResult < Result attr_accessor :name attr_accessor :price attr_accessor :image endIn questo modo istanziando un oggetto della classe ProductResult questo automaticamente erediterà anche le variabili id e url con rispettivi getter e setter.
# - postato da Sandro Paganotti - 19 Novembre 2007 - 09:53
2
Ciao Sandro,
in realtà l’esigenza è proprio quella descritta dal titolo e dalle prime righe.
Ovvero, data una lista di attributi, avere modo di creare i metodi accessori in modo dinamico.# - postato da Simone Carletti - 19 Novembre 2007 - 10:50







