2009-11-22 12 views
5

Ho avuto questo modello che stava lavorando bene:validates_presence_of provoca after_initialize di essere chiamato con un auto strano

class Weight < ActiveRecord::Base 
    belongs_to :user 
    validates_presence_of :weight, :measured_on 
    attr_accessible :weight, :measured_on 

    def after_initialize 
    self.measured_on ||= Date.today 
    end 
end 

ho aggiunto questa linea

validates_uniqueness_of :measured_on, :scope => :user_id 

ed è iniziato a gettare un errore convalida. Non è un errore di convalida, ma un errore di Rubino:

>> w.valid? 
ActiveRecord::MissingAttributeError: missing attribute: measured_on 
    from /Users/pupeno/Projects/sano/app/models/weight.rb:8:in `after_initialize' 

ho messo una dichiarazione debugger in after_initialize e ho notato qualcosa di inaspettato. Quando creo un nuovo peso funziona come previsto e l'oggetto sé sul after_initialize è il peso previsto:

>> w = Weight.new 
/Users/pupeno/Projects/sano/app/models/weight.rb:9 
self.measured_on ||= Date.today 
(rdb:1) p self 
#<Weight id: nil, user_id: nil, weight: nil, measured_on: nil, created_at: nil, updated_at: nil> 
(rdb:1) c 
=> #<Weight id: nil, user_id: nil, weight: nil, measured_on: "2009-11-22", created_at: nil, updated_at: nil> 

Quando eseguo w.valid? diventa strano after_initialize è chiamato ancora una volta, io non so perché, e l'oggetto sé è niente che mi aspettavo:

>> w.valid? 
/Users/pupeno/Projects/sano/app/models/weight.rb:9 
self.measured_on ||= Date.today 
(rdb:1) p self 
#<Weight id: 1> 
(rdb:1) p self.inspect 
"#<Weight id: 1>" 
(rdb:1) p self.class 
Weight(id: integer, user_id: integer, weight: float, measured_on: date, created_at: datetime, updated_at: datetime) 
(rdb:1) p self.measured_on 
ActiveRecord::MissingAttributeError Exception: missing attribute: measured_on 
(rdb:1) 

Sembra che un altro oggetto peso è stato creato senza alcun attributo ma il set id. Tutte le idee perché? È un bug o il comportamento previsto? Sto facendo qualcosa di sbagliato impostando measur_on su after_initialize?

mia soluzione corrente, nel caso in cui qualcuno sta avendo lo stesso problema, è

class Weight < ActiveRecord::Base 
    belongs_to :user 
    validates_presence_of :weight, :measured_on 
    validates_uniqueness_of :measured_on, :scope => :user_id 
    attr_accessible :weight, :measured_on 

    def after_initialize 
    if self.has_attribute? :measured_on 
     self.measured_on ||= Date.today 
    end 
    end 
end 

ma mi piacerebbe avere una soluzione adeguata.

risposta

6

Penso che si sta colpendo un bug rotaie di recente ho lottato con. Vedi il collegamento This blog entry al relativo bug del faro.

La mia comprensione è che ciò che sta accadendo è che un pezzo di codice di barre precedente fa un "select id from tablename" per vedere se esiste una voce o corrisponde. L'oggetto quindi memorizza nella cache che l'unico campo esistente per la tabella è "id". Il tuo codice quindi viene eseguito e il valore "attributes" è quindi errato, riflettendo solo il campo id.

Da quello che ho potuto trovare, questo è successo solo quando questo particolare percorso di codice è stato colpito, e in genere non ha turbato le cose, tranne quando si fanno convalide.

Quello che ho fatto per ovviare è stato avvolgere il codice after_initialise in un blocco ActiveRecord :: MissingAttributeError di inizio/salvataggio. Poi ho scritto una nota importante nell'applicazione e sopra ogni voce che indica quando è stata rilasciata una nuova versione di rails, potremmo rimuovere questo.

Sì, sono sicuro che ci sono soluzioni più eleganti.

def after_initialize 
    begin 
    # ... updates here 
    # self.unique_reference = UUIDTools::UUID.random_create.to_s 
    rescue ActiveRecord::MissingAttributeError 
    end 
end 

o si potrebbe anche fare:

def after_initialize 
    if self.has_attribute? :measured_on 
    self.measured_on ||= Date.today 
    end 
end 
+0

L'OP ha modificato la mia risposta e creato la sezione "has_attribute?: Measured_on" - Non sono sicuro al 100% che sia d'accordo con esso dopo aver esaminato la fonte dei binari - sospetto che funzioni a causa di un effetto collaterale, piuttosto che di -design). Non sto ripristinando quella parte della risposta, però, come chissà, potrebbe aiutare qualcuno. – oskarpearson

+0

Sono stato morso da questo oggi. Questo post e la tua risposta mi hanno salvato la pancetta. Avevo dimenticato che uno degli svantaggi dei linguaggi dinamici è che non si hanno sempre gli stessi attributi in un oggetto ... il che potrebbe essere buono ma anche sbagliato :) –

+0

È stato morso da questo oggi. Wow, questo è stato pubblicato qui nel 2009? Sono impressionato dal fatto che sia rimasto non fissato per così tanto tempo. – Trejkaz

0

validates_uniqueness_of deve eseguire la scansione del database per le voci. ActiveRecord sta caricando tutte le altre voci come istanze del modello. Ma per ridurre il processore/memoria usarlo non aggiunge metodi per gli attributi, perché non dovrebbe averne bisogno per un controllo rapido dell'esistenza. Tuttavia, continua a creare un'istanza di tutti i record esistenti come istanze del modello in modo che venga chiamato after_initialize.

Il lavoro attorno è di modificare il @attributes hash direttamente invece di basarsi su di accesso:

class Weight < ActiveRecord::Base 
    belongs_to :user 
    validates_presence_of :weight, :measured_on 
    validates_uniqueness_of :measured_on, :scope => :user_id 
    attr_accessible :weight, :measured_on 

    def after_initialize 
    @attributes["measured_on"] ||= Date.today 
    end 
end 
+0

Sono abbastanza sicuro che validates_uniqueness genera semplicemente un'istruzione sql per verificare l'esistenza. Non causa l'istanziazione di tutte le voci nella tabella. – Mike

+0

È così che sembra, ma esiste? alla fine del blocco \ _each validato nell'origine dei validati \ _uniqueness \ _of continua a creare un'istanza di record attiva. – EmFi

2

Questo problema è legato alla rotaie biglietto # 3165. Leggi lì, se sei interessato al come e al perché questo si verifica

Ho passato solo mezza giornata a questo, prima di aver finalmente trovato quel biglietto. Mentre io sono triste che è stato quasi esattamente un anno da quando questo è stato segnalato, e non è stato ancora fissato, ecco la mia semplice lavoro in giro per il mio scenario:

validates_uniqueness_of :email, :scope => :library_id 

def after_initialize 
    self.status ||= "Invited" 
end 

Ciò causerà un 'MissingAttributeError' a essere gettato, se ci sono record restituiti dal validates_uniqueness_of query. La mia soluzione semplice è questa:

def after_initialize 
    self.status ||= "Invited" if new_record? 
end 

Mentre altre persone stanno avendo problemi più complessi, questo dovrebbe risolvere il caso semplice, fino a quando una soluzione reale è impegnata nelle guide.

+0

Nota che se hai bisogno anche di questo per i record che non sono nuovi, questa soluzione non va bene (nel mio caso ha rotto i test). – Trejkaz

Problemi correlati