Creare metodi dinamicamente

Ho questa classe di esempio:

class Primo
def initialize arg_nome
@nome = arg_nome.capitalize
# Infine definisco i metodi dinamici
define_singleton_method arg_nome, lambda { “Si. #{presentazione}” }
end
def presentazione
“Sono #{@nome}, un #{self.class.name}”
end
def descrizione
“Io sono un #{self.class.name}”
end
end

irb(main):015:0> p=Primo.new “marco”
=> #<Primo:0x2549c10 @nome=“Marco”>

irb(main):017:0> p.presentazione
=> “Sono Marco, un Primo”

irb(main):018:0> p.descrizione
=> “Io sono un Primo”

Il metodo dinamico funziona e con una flessibilita’ pazzesca, viene
richiamato infatti il metodo presentazione che usa una variabile di
istanza
irb(main):016:0> p.marco
=> “Si. Sono Marco, un Primo”

Ora ho questa seconda classe che eredita dalla prima:

class Secondo < Primo
def initialize arg_nome
super arg_nome
# Definisco il metodo dinamico del figlio
define_singleton_method “cose_del_#{self.class.name}”, lambda {
return “Questo lo posso fare solo io” }
end

Aggiungo elementi alla descrizione del padre

def descrizione
“#{super} ma faccio anche le cose del #{self.class.superclass.name}”
end
end

irb(main):036:0> s=Secondo.new “luca”
=> #<Secondo:0x27f4320 @nome=“Luca”>

irb(main):038:0> s.presentazione
=> “Sono Luca, un Secondo”

irb(main):039:0> s.descrizione
=> “Io sono un Secondo ma faccio anche le cose del Primo”

Il metodo dinamico della classe padre viene correttamente aggiunto:

irb(main):037:0> s.luca
=> “Si. Sono Luca, un Secondo”

Ma non viene aggiunto il secondo metodo dinamico solo per la seconda
classe

irb(main):040:0> s.respond_to? :cose_del_secondo
=> false

Perche’?

Ciao Marco,

prova a fare un s.public_methods.grep /cose_del/

:wink:

2015-02-02 13:08 GMT+01:00 Marco M.
[email protected]:

Ciao Marco,

credo che il problema sia legato al fatto che il self.class.name ritorni
“Secondo" con la lettera S maiuscola. Per questo ti definisce il metodo
:cose_del_Secondo e non :cose_del_secondo come ti aspettavi.

A presto

Alberto V.

On Mon, Feb 2, 2015 at 1:29 PM, maurizio de magnis

Ops :slight_smile:

Ok funziona.
Se applichiamo l’esempio precedente a activerecord:
class Primo < ActiveRecord::Base

end

class Secondo < Primo

end

primo = Primo.last
irb(main):046:0> primo.class
=> Primo

secondo = Secondo.last
irb(main):047:0> secondo.class
=> Secondo

#Trasformare il primo in secondo
primo = Primo.last
secondo = Secondo.find(primo.id)

C’è un modo per non passare dal database?
Qualcosa tipo questo ma ottenendo un oggetto che punti anche al database
secondo = Secondo.new(primo.attributes)

2015-02-02 14:35 GMT+01:00 Marco M.
[email protected]:
[cut]

C’è un modo per non passare dal database?
Qualcosa tipo questo ma ottenendo un oggetto che punti anche al database
secondo = Secondo.new(primo.attributes)

Mmh, ho difficoltà a seguirti.
Cosa intendi con “non passare dal database” e “che punti anche al
database”?

Marco, credo che il tuo problema sia gi stato risolto con la Single
Table Inheritance

http://guides.rubyonrails.org/active_record_basics.html

@Maurizio
Con “Non passare dal database” intendevo non fare una seconda query
Con “che punti anche al database” intendevo un oggetto riferito all’id a
cui sono riferiti quei dati (banale ma x intenderci: se uso una new è un
nuovo record)

@Sante Gennaro
Grazie! Questa magia me l’ero persa, se non trovo altro direi che è la
soluzione

Ciao Marco,

non credo si possa cambiare la classe di un’istanza in Ruby (mi sembra
che in Python sia possibile).

Ad ogni modo sarei curioso di conoscere lo scenario che ti porta a dover
fare questo cambiamento senza poter/voler eseguire una nuova query.
Magari ci sono altri modi per risolvere il problema, probabilmente come
ha detto Sante ti conviene usare STI o trovare un modo per definire sin
dal principio la classe con l’istanza che ti servirà senza dover
cambiarla in corso.

A presto

Alberto V.

On Tue, Feb 3, 2015 at 10:03 AM, Marco M.

Questo vincolo è effettivamente… “peculiare” :slight_smile:
Concordo con Alberto, magari ripensando i tuoi requisiti riusciresti ad
ottenere comunque l’accentramento della logica senza un aggravio di
performance.
Considera per esempio che per evitare di effettuare chiamate inutili al
db
(come verso qualunque altra sorgente simile), puoi usare un’Identity Map
http://martinfowler.com/eaaCatalog/identityMap.html.
+1 per STI, anche se personalmente è una soluzione che uso solo se
costretto.

2015-02-03 10:21 GMT+01:00 Alberto V. [email protected]:

Ti faccio la proposta indecente: usa la class Zero < ActiveRecord::Base;

stuff; end solo come layer ActiveRecord (senza metodi di
decorazione/presentazione/servizi/etc) e poi implementa Primo e Secondo
come PORO che ricevono un’istanza di Zero in #initialize e condividono
eventuale codice comune tramite moduli.
STI e generazione dinamica di metodi vanno bene solo quando non esistono
strade più esplicite e lineari.

2015-02-03 15:53 GMT+01:00 Marco M.
[email protected]:

Questa è un’altra soluzione e ti ringrazio, anche più ordinata forse ma
mi chiedo se è conveniente avere un modello ed un modulo in più da
gestire.

Ne approfitto per segnalarti che il link sopra non funziona nel caso lo
avessi preso da un preferito

L’applicazione è nata col modello “Primo” a cui sucessivamente si è
aggiunta una seconda variante che non ho gestito con STI ma con: scope,
metodi, controller.

E’ abbastanza pulito nel senso che la logica è dove deve stare ma a
differenza di STI ottengo sempre gli oggetti “Primo” che quando sono
“Secondo” devo riestrarre. Per operazioni massive, una query in più non
è proprio indolore per questo stavo valutando come migliorare. Per
questo progetto probabilmente rimarrà così ma dal prossimo sicuramente
proverò STI.

2015-02-03 18:13 GMT+01:00 Marco M.
[email protected]:

Questa è un’altra soluzione e ti ringrazio, anche più ordinata forse ma
mi chiedo se è conveniente avere un modello ed un modulo in più da
gestire.

Considera che le alternative sono:

  • approccio STI: “sporcare” il database con la colonna “type” e gestire
    comunque la distinzione tra Primo e Secondo all’atto del caricamento
  • approccio “dinamico”: usare codice addizionale per la generazione di
    metodi dinamici, rendendo meno esplicito il comportamento di Secondo

Come al solito, il rapporto costi/benefici dipende dal contesto :slight_smile:

Ne approfitto per segnalarti che il link sopra non funziona nel caso lo

avessi preso da un preferito

Questo dell’Identity Map?
Era: P of EAA: Identity Map
Già che ci sono c’è anche questo altro link, specifico per Ruby/Rails:

http://playersgonnaplay.it/identity-map-in-rails

:wink:

Ah scusa funzionava, era sporco il link sul forum -_-
Base ma interessante, bisogna fare i conti con la consistenza ma può
tornare utile in alcuni casi.
Forse basterebbe una callback che faccia il reset quando i dati vengono
variati.