Какой самый быстрый способ создать массовые ассоциации HABTM в рельсах?
-
18-09-2019 - |
Вопрос
У меня есть две таблицы, с отношениями HABTM в рельсах. Что -то вроде следующего:
class Foo < ActiveRecord::Base
has_and_belongs_to_many :bars
end
class Bar < 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 << @bars
И оба бегают очень медленно, с записью, подобной следующей для каждого bar
:
столбцы bars_foos (1,1 мс) Показать поля из
bars_foos
SQL (0,6 мс) вставьте вbars_foos
(bar_id
,foo_id
) Значения (100, 117200)
Я посмотрел на артистику, но import
Функция, кажется, не работает без модели (model.import), которая исключает его использование для таблицы соединений.
Нужно ли написать SQL или у рельс более красивый способ?
Решение
Я думаю, что ваша лучшая ставка будет использовать SQL, а объемный вставка несколько строк за запрос. Если вы можете создать оператор вставки, который делает что -то вроде:
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}")
Кроме того, если вы ищете планку по «some_attribute», убедитесь, что у вас есть это поле, индексированное в вашей базе данных.
Другие советы
Вы все еще можете взглянуть на 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
Это было быстрее, чем эквивалентный код местных рельсов в течение 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
Мне кажется, что это достаточно простая операция, которую она должна быть эффективно поддержана в рельсах, я хотел бы услышать, если у кого -то есть более чистый путь.
Я бегу 2.2.2, Может быть, он реализован более эффективно в 3.x? и нашел то же самое на 3.0.2.
Честно, 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
Кроме того, вы должны попробовать запустить то же самое в производстве и посмотреть, какую производительность вы получаете, поскольку в производстве продолжается много кэширования, которое не обязательно происходит в разработке.