2010-10-15 8 views
9

Credo che questo sia un bug in Rails 3. Spero che qualcuno qui possa guidarmi nella direzione corretta. Il codice pubblicato di seguito, è puramente a scopo illustrativo di questo problema. Speriamo che questo non confonda il problema.interruzioni default_scope (aggiornamento | elimina | distruggi) _all in alcuni casi

Dato che ho un modello Post e un modello di commento. Pubblica has_many Comments e Comment appartiene_al post.

Con un default_scope impostato sul modello Post, definendo le relazioni joins() e where(). In questo caso, dove() dipende da joins().

Normalmente i post non dipendono dai commenti. Ancora una volta, voglio solo dare un semplice esempio. Questo potrebbe essere in ogni caso quando where() dipende da joins().

class Post < ActiveRecord::Base 
    has_many :comments, :dependent => :destroy 

    default_scope joins(:comments).where("comments.id < 999") 
end 

class Comment < ActiveRecord::Base 
    belongs_to :post, :counter_cache => true 
end 

eseguendo il comando seguente:

Post.update_all(:title => Time.now) 

Produce la seguente query, e, infine, getta ActiveRecord :: StatementInvalid:

UPDATE `posts` SET `title` = '2010-10-15 15:59:27' WHERE (comments.id < 999) 

Anche in questo caso, update_all, delete_all, destroy_all comportarsi allo stesso modo . Ho scoperto questo comportamento quando la mia richiesta si è lamentata quando ho provato ad aggiornare il counter_cache. Che alla fine scava in update_all.

risposta

4

I ran into this as well.

Se hai

class Topic < ActiveRecord::Base 
    default_scope :conditions => "forums.preferences > 1", :include => [:forum] 
end 

e si fa un

Topic.update_all(...) 

che sarà fallire con

Mysql::Error: Unknown column 'forums.preferences' in 'where clause' 

Il lavoro in giro per questo è:

Topic.send(:with_exclusive_scope) { Topic.update_all(...) } 

Puoi scimmia Patch Questa utilizzando questo codice (e che richiedono in environment.rb o altrove)

module ActiveRecordMixins 
    class ActiveRecord::Base 
    def self.update_all!(*args) 
     self.send(:with_exclusive_scope) { self.update_all(*args) } 
    end 
    def self.delete_all!(*args) 
     self.send(:with_exclusive_scope) { self.delete_all(*args) } 
    end 
    end 
end 

fine

Poi basta si update_all! o delete_all! quando ha un ambito predefinito.

+0

Hey Ben, hai aperto un problema a riguardo? –

1

È possibile farlo anche a livello di classe, senza creare nuovi metodi, in questo modo:

def self.update_all(*args) 
    self.send(:with_exclusive_scope) { super(*args) } 
end 

def self.delete_all(*args) 
    self.send(:with_exclusive_scope) { super(*args) } 
end 
7

Ho avuto questo problema anche, ma abbiamo davvero bisogno di essere in grado di utilizzare update_all con condizioni complesse in default_scope (ad esempio, senza l'ambito predefinito il caricamento di eager è impossibile e incollare letteralmente un ambito con nome non è affatto divertente).Ho aperto una richiesta di pull qui con il mio fix:

https://github.com/rails/rails/pull/8449

Per delete_all ho sollevato un errore se c'è una condizione di join per renderlo più evidente quello che hai da fare (invece di lanciare il join condizione e eseguendo delete_all su tutto, si ottiene un errore).

Non sono sicuro di quello che i ragazzi di rotaie faranno con la mia richiesta di pull, ma ho pensato che fosse rilevante per questa discussione. (Inoltre, se hai bisogno di questo bug corretto, potresti provare la mia filiale e postare un commento sulla richiesta di pull.)

0

Non penso che lo definirei un bug. Il comportamento mi sembra abbastanza logico, anche se non immediatamente ovvio. Ma ho elaborato una soluzione SQL che sembra funzionare bene. Usando il tuo esempio, sarebbe:

class Post < ActiveRecord::Base 
    has_many :comments, :dependent => :destroy 

    default_scope do 
    with_scope :find => {:readonly => false} do 
     joins("INNER JOIN comments ON comments.post_id = posts.id AND comments.id < 999") 
    end 
    end 
end 

In realtà sto usando la riflessione per renderlo più robusto, ma quanto sopra ottiene l'idea croce. Spostando la logica WHERE in JOIN si assicura che non venga applicata in luoghi inappropriati. L'opzione :readonly serve a neutralizzare il comportamento predefinito di Rails di rendere gli oggetti joins in sola lettura.

Inoltre, so che alcune persone deride l'uso di default_scope. Ma per le app multi-tenant, è perfetta.

Problemi correlati