2010-02-04 18 views
13

Tengo dos tablas, con una relación HABTM en Rails. Algo así como lo siguiente:¿Cuál es la forma más rápida de crear asociaciones masivas de HABTM en Rails?

 
class Foo < ActiveRecord::Base 
    has_and_belongs_to_many :bars 
end 

class Bar < ActiveRecord::Base 
    has_and_belongs_to_many :foos 
end 

ahora tengo una nueva Foo objeto, y quieren masa asignar miles de bares a ella, que he pre-cargado:

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

¿Qué tan rápido forma de hacer esto? He intentado:

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

Y tanto correr muy lento, con una entrada como la siguiente para cada bar:

bars_foos columnas (1.1ms) MOSTRAR campos de bars_foos SQL (0,6 ms) INSERT INTO bars_foos (bar_id, foo_id) VALORES (100, 117200)

que miraba ar-extensions, pero la función import no parece funcionar sin un modelo (Model.import) que impida su uso para una tabla de unión.

¿Necesito escribir el SQL o los Rails tienen una forma más bonita?

+0

realmente? ¿Ninguno? Ustedes salten sobre los lay-ups y las preguntas de "mejores prácticas" :) – klochner

Respuesta

6

Creo que su mejor apuesta en términos de rendimiento va a ser utilizar SQL, e insertar en bloque varias filas por consulta. Si usted puede construir una instrucción INSERT que hace algo como:

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

Usted debe ser capaz de insertar miles de filas en una sola consulta. Yo no probar su método de mass_habtm, pero parece que se podría a algo como:

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}") 

Además, si usted está buscando bar por "some_attribute", asegúrese de que tiene ese campo indexadas en su base de datos.

+0

my mass_habtm solo combina las consultas en una sola búsqueda de búsqueda/inserción, que probablemente no gana * tanto * que lo que tienes aquí. Odio seleccionar el mío, así que gracias por brindarme al menos una opción viable. – klochner

1

Esto fue más rápido que el código rieles nativos equivalente en un factor de 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 

Me parece que esta es una operación bastante claro que se debe apoyar de manera eficiente en los carriles, me encanta escucha si alguien tiene una manera más limpia.

Estoy ejecutando 2.2.2, tal vez se implemente de manera más eficiente en 3.x? y encontró lo mismo en 3.0.2.

-3

Honestamente, has_and_belongs_to_many es una forma muy anticuada de hacer las cosas. Probablemente deberías mirar en has_many :through, que es la nueva forma de hacer tablas de unión, y ha sido durante bastante tiempo.

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 

También, usted debe tratar de ejecutar la misma en la producción y ver qué tipo de rendimiento que se obtiene, como una gran cantidad de almacenamiento en caché que pasa en la producción que no necesariamente se producen en el desarrollo.

+1

No sea un idiota, pero de ninguna manera abordaron la pregunta principal: la velocidad de crear las relaciones, aparte de especular que la producción podría ser mejor. Si bien el almacenamiento en caché puede ayudar, es casi seguro que no cambiará la forma en que se formula el SQL. También diría que habtm es un mejor candidato para optimizar esto, ya que has_many- through requiere una clase de modelo, lo que significa que puede haber devoluciones de llamada en el modelo de unión. – klochner

+1

Y estoy bastante seguro de que su implementación es * más lenta * que la que yo presenté como * no lo suficientemente rápida *. – klochner

+0

Sí, pero tener un modelo en la combinación le permite hacer otras cosas con los buscadores y otras opciones a las que puede que no haya tenido acceso. Al menos de esta manera, tienes otro modelo para poner tu código en lugar de repetirlo en ambos modelos si quieres ir y venir. –

5

Aún puede consultar activerecord-import. Es correcto que no funciona sin un modelo, pero podrías crear un modelo solo para la importación.

class FooBar < ActiveRecord::Base; end 

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

Puede terminar con esto en una transacción para asegurar la HABTM se llenara completamente, como aquí:

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 
Cuestiones relacionadas