2012-05-25 5 views
5

Sto tentando di estendere un modello ActiveRecord (Vote) che una gemma (https://github.com/peteonrails/vote_fu) fornisce alla mia applicazione. (Vale a dire, non v'è alcun vote.rb in app/models)Riapri un modello ActiveRecord fornito da una gemma

Il mio primo approccio è stato quello di creare un file chiamato lib/extend_vote.rb che contiene il codice:

Vote.class_eval do 
    after_create :create_activity_stream_event 
    has_one :activity_stream_event 

    def create_activity_stream_event 
    # something.. 
    end 
end 

Questo funziona quando viene creato il primo voto, ma quando provo a creare ogni voto successivo ottengo l'errore TypeError (can't dup NilClass).

penso che questo errore è causato dal fatto che la classe Vote viene ricaricato automaticamente dopo ogni richiesta, ma il codice di lib/extend_vote.rb viene caricato solo una volta all'avvio del server e questo fa sì che l'associazione has_one :activity_stream_event a comportarsi stranamente. (Inoltre, il problema va via se ho impostato config.cache_classes = true in development.rb)

Per risolvere questo problema, ho cercato di fare il voto estensioni ricaricare su ogni richiesta con l'aggiunta di un blocco to_prepare al mio development.rb:

config.to_prepare do 
    load 'extend_vote.rb' 
end 

Questo risolve il problema (can't dup NilClass), ma ora ogni volta che creo un nuovo voto, la richiamata create_activity_stream_event viene chiamata un tempo aggiuntivo. Il primo voto lo chiama una volta, il secondo lo chiama due volte, ecc. Ecc. Quindi sembra che il blocco to_prepare stia ricaricando l'estensione TROPPO in modo aggressivo e aggiungendo duplicati di callback.

Qual è il modo migliore per aggiungere metodi e callback a questo modello Vote?

+1

Funziona se si utilizza 'class Vote' invece di' Vote.class_eval'? Una cosa che potresti fare è modificare il codice nella gemma stessa e usare la tua versione modificata. – agmcleod

+0

'class Vote' si comporta come' Vote.class_eval' - non funziona. Immagino di poter modificare la gemma, ma davvero non voglio lol. Che casino! –

+0

Perché pensi che la classe Voto venga ricaricata? Nella resposity, la classe è nella directory lib quindi è la stessa di te ... – Dougui

risposta

1

mi piacerebbe provare quello agmcleod suggerito nei commenti, ma invece di metterla in lib, lo mise in config/inizializzatori/vote.rb:

class Vote 
    after_create :create_activity_stream_event 
    has_one :activity_stream_event 

    def create_activity_stream_event 
    # something.. 
    end 
end 

Naturalmente, si potrebbe sborsare la gemma , apporta le tue modifiche e collega alla tua versione biforcuta nel tuo Gemfile (questa è la mia preferenza).

+0

perché 'config/initializers' invece di' lib'?Presumo che ho bisogno di mantenere l'istruzione 'load' nel blocco' to_prepare'? –

+0

nope non dovrebbe aver bisogno di questa istruzione di caricamento poiché gli elementi nei file di inizializzazione vengono caricati una volta all'avvio per tutti gli ambienti. – miked

+0

Questo non funziona - se lascio 'load' nel blocco' to_prepare', ottengo lo stesso errore che ho ottenuto quando il file era in 'lib'. Se RIMUOVO il 'load', allora ottengo l'errore' (non posso dup NilClass) ' –

0

si potrebbe provare qualcosa di simile:

class Vote 
    after_create :create_activity_stream_event 
    has_one :activity_stream_event 

    def create_activity_stream_event 
     # something.. 
    end 
end 

penso che si aggiungerà la funzione e call funzioni "after_create" e "has_hone".

+0

Scusa, non ho visto il commento di agmcleod. Probabilmente è la stessa cosa ... dovrebbe funzionare ma non è così. – Dougui

+0

heh, almeno non hai avuto un voto negativo come ho fatto ... e i nostri suggerimenti erano, beh, lo stesso! lol – miked

+0

Questo può essere perché dici di inserirlo in un inizializzatore. O forse lo dico dopo di te. – Dougui

4

Una parola di cautela: questa è una gemma molto vecchia (l'ultimo commit è di 3 anni) e dall'aspetto non funzionerà con i binari 3.x come è. Nei motori Rails 3.x rende questo tipo di cose molto più semplice.

Come ho capito il problema nel primo caso non è che il modello di voto viene ricaricato (non dovrebbe) ma che il modello activity_stream_event viene ricaricato. Poiché il modello di voto non viene ricaricato, l'associazione viene lasciata sospesa nella versione della classe activity_stream_event prima del ricaricamento. Dal momento che i binari eliminano le classi prima che vengano ricaricate, questo causa problemi.

Con questo nel mio, provate questo hack:

#in config/initializers/abstract_vote.rb 
AbstractVote = Vote 
AbstractVote.abstract_class = true 
Object.send :remove_const, :Vote 

#in app/models/vote.rb 

class Vote < AbstractVote 
    after_create :create_activity_stream_event 
    has_one :activity_stream_event 

    def create_activity_stream_event 
    end 
end 

Quello che fa è consentire di avere la propria classe Votate che eredita da quello della gemma.

Ma ancora una volta, vi esorto a trovare qualcosa di più fino a data o rotolare il proprio (la gemma è solo ~ 250 linee di Ruby)

6

[UPDATE: dovrebbe essere la soluzione giusta per evitare che l'essere del modulo includono più volte nella stessa classe]

Credo che sia possibile utilizzare ActiveSupport::Concern per impedire che il modulo venga incluso più volte risultante dal callback chiamato più volte. Vedere l'esempio seguente:

module VotePatch 
    extend ActiveSupport::Concern 

    included do 
    after_create :create_activity_stream_event 
    has_one :activity_stream_event 
    end 

    module InstanceMethods 
    def create_activity_stream_event 
     #your code here 
    end 
    end 

end 

Vote.send(:include, VotePatch) 
+0

aggiornato la mia risposta, credo che ActiveSupport :: Concern risolverà il tuo problema –

0

Il tuo problema potrebbe essere dovuto al fatto che siete scimmia patching della classe. Quando le rotaie cercano di ricaricare le costanti non sta considerando il tuo file.

Provare ad utilizzare la tecnica del modulo come indicato di seguito.

Aggiungere un file chiamato lib/vote_fu_extension.rb

module VoteFuExtension 
    def self.included(base) 
    base.has_one :activity_stream_event 
    base.after_create :create_activity_stream_event 
    end 
    def create_activity_stream_event 
    # something.. 
    end 
end 
Vote.send(:include, VoteFuExtension) 

Aggiungi un inizializzatore chiamato config/initializers/vote_fu.rb

require "vote_fu_extension" 

Nota

Se si desidera aggiungere metodi di classe al modello Vote fare riferimento a questo answer .

Plug vergognoso: My fork della gemma vote_fu ha alcune nuove funzionalità e miglioramenti.

1

Adrien Coquio ha l'idea giusta con ActiveSupport::Concerns, che sono the Rails way per estendere i modelli. Il suo codice funzionerà e dovresti usarlo.

Tuttavia, questo non funzionerà sempre nello sviluppo perché quando Rails ricarica le classi quando un file cambia, non rivaluterà la riga #send. L'unica soluzione che ho trovato è stato il collegamento a un ActionDispatch richiamata nella produzione per garantire il file è ri-necessaria dopo ogni pagina del carico:

if Rails.env.development? 
    ActionDispatch::Callbacks.to_prepare do 
    require_dependency "../../lib/vote_fu_extensions" 
    end 
end 

Nella produzione, o se si imposta cache_classes true nella configurazione, hai vinto Non c'è bisogno di farlo.