Какой самый быстрый способ создания массовых ассоциаций HABTM в Rails? - PullRequest
14 голосов
/ 05 февраля 2010

У меня есть две таблицы с отношением HABTM в Rails. Примерно так:

class Foo &lt ActiveRecord::Base
  has_and_belongs_to_many :bars
end

class Bar &lt ActiveRecord::Base
  has_and_belongs_to_many :foos
end

Теперь у меня есть новый Foo объект, и я хочу массово назначить ему тысячи баров, которые я предварительно загрузил:

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

Какой самый быстрый способ сделать это? Я пробовал:

@foo.bars = @bars
@foo.bars &lt&lt @bars

И оба работают очень медленно, с записью, подобной следующей для каждого bar:

bars_foos Columns (1.1ms) ПОКАЗАТЬ ПОЛЯ ОТ bars_foos SQL (0,6 мс) INSERT INTO bars_foos (bar_id, foo_id) ЗНАЧЕНИЯ (100, 117200)

Я посмотрел на ar-extensions, но функция import, похоже, не работает без модели (Model.import), что исключает ее использование для таблицы соединений.

Нужно ли писать SQL или у Rails есть более симпатичный способ?

Ответы [ 4 ]

7 голосов
/ 08 февраля 2010

Я думаю, что лучшим выбором для производительности будет использование SQL и массовая вставка нескольких строк в запросе. Если вы можете построить оператор INSERT, который делает что-то вроде:

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

Вы должны иметь возможность вставлять тысячи строк в один запрос. Я не пробовал ваш метод mass_habtm, но кажется, что вы могли бы что-то вроде:

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

Кроме того, если вы ищете Bar по параметру some_attribute, убедитесь, что это поле проиндексировано в вашей базе данных.

6 голосов
/ 13 июля 2011

Вы все еще можете взглянуть на activerecord-import . Это верно, что это не работает без модели, но вы можете создать модель только для импорта.

class FooBar < ActiveRecord::Base; end

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

Вы можете заключить это в транзакцию, чтобы обеспечить полное заполнение HABTM, как здесь:

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
1 голос
/ 05 февраля 2010

Это было быстрее, чем эквивалентный собственный код рельсов в 7 раз:

class &lt&lt 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

Мне кажется, что это достаточно простая операция, которая должна эффективно поддерживаться в Rails, я хотел бы услышать, если у кого-то есть более понятный способ.

У меня работает 2.2.2, может быть, он реализован более эффективно в 3.x? и нашел то же самое в 3.0.2.

0 голосов
/ 06 февраля 2010

Честно говоря, has_and_belongs_to_many - очень устаревший способ ведения дел. Вероятно, вам стоит взглянуть на has_many :through, который является новым способом создания таблиц соединения, и уже довольно давно.

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

Кроме того, вы должны попробовать запустить то же самое в производственной среде и посмотреть, какую производительность вы получаете, поскольку в производственной среде происходит большое кеширование, которое не обязательно происходит в процессе разработки.

...