2010-02-04 5 views
13

Ho due tabelle, con una relazione HABTM in Rails. Qualcosa di simile a quanto segue:Qual è il modo più veloce per creare associazioni di massa HABTM in Rails?

 
class Foo < ActiveRecord::Base 
    has_and_belongs_to_many :bars 
end 

class Bar < ActiveRecord::Base 
    has_and_belongs_to_many :foos 
end 

Ora ho una nuova Foo oggetto, e vogliono mass-assegnare migliaia di bar ad esso, che ho pre-caricati:

 
@foo = Foo.create 
@bars = Bar.find_all_by_some_attribute(:a) 

Qual è il più veloce modo di fare questo? Ho provato:

 
@foo.bars = @bars 
@foo.bars << @bars 

Ed entrambi corro molto lento, con una voce simile al seguente per ogni bar:

bars_foos colonne (1.1ms) Mostra CAMPI DA bars_foos SQL (0.6ms) INSERT INTO bars_foos (bar_id, foo_id) VALUES (100, 117200)

ho guardato ar-extensions, ma la funzione import non sembra funzionare senza un modello (Model.import) che preclude il suo utilizzo per una tabella di join.

Ho bisogno di scrivere l'SQL o Rails ha un modo più carino?

+0

davvero? nessuno? Voi ragazzi saltate su tutti i lay-up e le domande sulle "migliori pratiche" :) – klochner

risposta

6

Penso che la vostra migliore scommessa in termini di prestazioni sarà l'utilizzo di SQL e l'inserimento collettivo di più righe per query. Se si può costruire un'istruzione INSERT che fa qualcosa di simile:

INSERT INTO foos_bars (foo_id,bar_id) VALUES (1,1),(1,2),(1,3).... 

Si dovrebbe essere in grado di inserire migliaia di righe in una singola query. Non ho provato il tuo metodo mass_habtm, ma sembra che si potrebbe a qualcosa di simile:

bars = Bar.find_all_by_some_attribute(:a) 
foo = Foo.create 
values = bars.map {|bar| "(#{foo.id},#{bar.id})"}.join(",") 
connection.execute("INSERT INTO foos_bars (foo_id, bar_id) VALUES #{values}") 

Inoltre, se siete alla ricerca Bar da "some_attribute", assicurarsi di avere quel campo indicizzato nel database.

+0

il mio mass_habtm combina semplicemente le query in una singola query di ricerca/inserimento, che probabilmente non guadagna * quello * molto più di quello che hai qui. Odio selezionare il mio, quindi grazie per avermi dato almeno un'opzione praticabile. – klochner

1

Questo è stato più veloce del codice di rotaie nativo equivalente di un fattore 7:

 
class << Foo 
    def mass_habtm(attr_array) 
    attr_str = attr_array.map{|a| %Q{'#{a}'} }.uniq.join(",") 
    self.connection.execute(%Q{insert into foos_bars (foo_id,bar_id) 
        select distinct foos.id,bars.id from foos,bars 
        where foos.id = #{self.id} 
        and bars.some_attribute in (#{attr_str})}) 
    end 
end 

Mi sembra che questo sia un sufficiente un'operazione semplice che dovrebbe essere sostenuto in modo efficiente in Rails, mi piacerebbe sentire se qualcuno ha un modo più pulito.

Sono in esecuzione 2.2.2, forse è implementato in modo più efficiente in 3.x? e ha trovato lo stesso su 3.0.2.

-3

Onestamente, has_and_belongs_to_many è un modo molto antiquato di fare le cose. Probabilmente dovresti cercare in has_many :through, che è il nuovo modo di fare tabelle di join, ed è stato per un bel po 'di tempo.

class Foo < ActiveRecord::Base 
    has_many :foobars 
    has_many :bars, :through => :foobars 

    def add_many_bars(bars) 
    bars.each do |bar| 
     self.bars << bar 
    end 
    end 
end 

class Bar < ActiveRecord::Base 
    has_many :foobars 
    has_many :foos, :through => :foobars 
end 

class FooBar < ActiveRecord::Base 
    belongs_to :foo 
    belongs_to :bar 
end 

Inoltre, si dovrebbe provare a eseguire lo stesso in produzione e vedere che tipo di prestazioni si ottiene, come un sacco di caching prosegue nella produzione che non si verifica necessariamente in fase di sviluppo.

+1

Non essere un idiota, ma non avete in alcun modo affrontato la domanda principale: la velocità della creazione delle relazioni, oltre a ipotizzare che la produzione potrebbe essere migliore. Mentre il caching può essere d'aiuto, quasi certamente non cambierà il modo in cui è stato formulato l'SQL. Direi anche che habtm è un candidato migliore per l'ottimizzazione di questa roba, dal momento che has_many-> through richiede una classe modello, il che significa che potrebbero esserci callback nel modello di join. – klochner

+1

E sono abbastanza sicuro che la tua implementazione sia * più lenta * di quella che avevo presentato come * non abbastanza veloce *. – klochner

+0

Sì, ma avere un modello sul join ti consente di fare altre cose con i finder e altre opzioni a cui potresti non aver avuto accesso. Almeno in questo modo, hai un altro modello per inserire il tuo codice piuttosto che ripeterlo in entrambi i modelli se vuoi andare avanti e indietro. –

5

Si potrebbe ancora dare un'occhiata a activerecord-import. È giusto che non funzioni senza un modello, ma potresti creare un modello solo per l'importazione.

class FooBar < ActiveRecord::Base; end 

FooBar.import [:foo_id, :bar_id], [[1,2], [1,3]] 

Puoi avvolgere questo in una transazione per garantire la HABTM viene completamente popolato, come qui:

ActiveRecord::Base.transaction do 
    imported_foo = Foo.import(foo_names, foo_values) 
    imported_bar = Bar.import(bar_names, bar_values) 
    FooBar.import([:foo_id, :bar_id], imported_foo.ids.zip(imported_bar.ids) 
end