(Примечание: Я несколько обновлений, вы, вероятно, наиболее заинтересованы в коде, представленной в UPDATE 2 или UPDATE 3.)
Я думаю, вы можете поместить следующее в вашем вопросе модели:
def diff_tags(other_q)
other_q.tags - tags
end
def add_tags(other_q)
tags << diff_tags(other_q)
end
Затем делаем следующее:
q1 = Question.find(1)
q2 = Question.find(2)
q1.add_tags(q2)
приводит к (Postgres в моем случае):
SELECT "tags".* FROM "tags" INNER JOIN "questions_tags" ON "tags"."id" = "questions_tags"."tag_id" WHERE "questions_tags"."question_id" = ? [["question_id", 2]]
SELECT "tags".* FROM "tags" INNER JOIN "questions_tags" ON "tags"."id" = "questions_tags"."tag_id" WHERE "questions_tags"."question_id" = ? [["question_id", 1]]
begin transaction
INSERT INTO "questions_tags" ("question_id", "tag_id") VALUES (1, <missing tag id 1>)
INSERT INTO "questions_tags" ("question_id", "tag_id") VALUES (1, <missing tag id 2>)
... and all other missing tags ...
commit transaction
Вы можете продолжить работу над запросами по адресу:
1) выбрать только идентификаторы тегов в первых 2-х запросов, не инстанцирует целые теги объектов
2) ВСТАВИТЬ несколько значений в одном операторе SQL, как INSERT INTO "questions_tags" ("question_id", "tag_id") VALUES (<question_id>, <id1>), (<question_id>, <id2>)
, но вы, вероятно, необходимо с помощью сырой SQL для этого.
UPDATE: а вот оптимизированная версия:
def diff_tags_ids(other_q)
(other_q.tags.select(:id) - tags.select(:id)).map(&:id)
end
def add_tags_ids(tag_ids)
query_head = 'INSERT INTO "questions_tags" ("question_id", "tag_id") VALUES '
query_values = []
tag_ids.each do |tag_id|
query_values << "(#{self.id},#{tag_id})"
end
query = query_head + query_values.join(", ")
ActiveRecord::Base.connection.execute(query)
end
def add_tags_from(other_q)
add_tags_ids(diff_tags_ids(other_q))
end
Теперь следующий
q1 = Question.find(1)
q2 = Question.find(2)
q1.add_tags_from(q2)
приводит к всего 3 запросов:
SELECT id FROM "tags" INNER JOIN "questions_tags" ON "tags"."id" = "questions_tags"."tag_id" WHERE "questions_tags"."question_id" = ? [["question_id", 3]]
SELECT id FROM "tags" INNER JOIN "questions_tags" ON "tags"."id" = "questions_tags"."tag_id" WHERE "questions_tags"."question_id" = ? [["question_id", 1]]
INSERT INTO "questions_tags" ("question_id", "tag_id") VALUES (1,5), (1,6) # or whatever values are missing in question 1 compared to question 2
UPDATE 2: просто понял, что ты не нужно читать теги из 2-го вопроса, вы уже имеете их в tag_list. Ну, это еще проще, то:
def diff_tags_ids(tag_list)
(tag_list - tags.select(:id)).map(&:id)
end
def add_tags_ids(tag_ids)
query_head = 'INSERT INTO "questions_tags" ("question_id", "tag_id") VALUES '
query_values = []
tag_ids.each do |tag_id|
query_values << "(#{self.id},#{tag_id})"
end
query = query_head + query_values.join(", ")
ActiveRecord::Base.connection.execute(query)
end
def update_tags(tag_list)
add_tags_ids(diff_tags_ids(tag_list))
end
Это один я не пробовал на фактическое приложение, так что извините, если есть некоторые небольшие опечатки.
UPDATE 3: и если у вас есть тег имена, не помечать объекты в вашем tag_list, то вот обновление (если у вас есть name
атрибут в модели Tag:
def diff_tags_names(tag_list)
tag_list - tags.select(:name).map(&:name)
end
def find_tags_ids_by_names(tag_list)
Tag.where(:name => tag_list).select(:id).map(&:id)
# That leads to SELECT "tags"."id" FROM "tags" WHERE "tags"."name" IN ('tag1', 'tag2', ...)
end
def add_tags_ids(tag_ids)
query_head = 'INSERT INTO "questions_tags" ("question_id", "tag_id") VALUES '
query_values = []
tag_ids.each do |tag_id|
query_values << "(#{self.id},#{tag_id})"
end
query = query_head + query_values.join(", ")
ActiveRecord::Base.connection.execute(query)
end
def update_tags(tag_list)
tags_ids_to_add = find_tags_ids_by_names(diff_tags_names(tag_list))
add_tags_ids(tags_ids_to_add)
end
Еще только два запроса ...
его проблему п + 1 запросов, вы можете использовать включает в себя в зависимости от вашего отношения –
Я знаю о проблеме п + 1 запроса - именно поэтому я прошу наилучшим образом подойти к нему :) – marcamillion