Ruby Diary #12: Ruby attr_accessor dinamici

Lunedì 19 Novembre 2007 - 08:16

di Simone Carletti

Ruby

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à

  1. id (ereditata da Result)
  2. url (ereditata da Result)
  3. name
  4. price
  5. 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.

Tags:

Categoria: Ruby | Permalink

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
end

In 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

Inserisci il tuo commento:





(puoi usare i seguenti tag HTML per formattare il testo -
a href, b, i, br/, p, strong, em, ul, ol, li, blockquote, pre):

 

Anteprima del commento