(注:複数の更新を行いました。おそらく、 UPDATE 2またはUPDATE 3で提供されるコードに最も関心があるでしょう。)
Question モデルに次のものを配置できると思います。
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 つのクエリでタグ ID のみを選択し、タグ オブジェクト全体をインスタンス化しない
2) のように単一の SQL ステートメントで複数の値を INSERT しますが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
更新 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
これは実際のアプリで試していないので、小さなタイプミスがある場合は申し訳ありません.
更新 3: tag_list にタグオブジェクトではなく、タグ名がある場合は、次のように更新します (タグ モデルに属性があると仮定します:name
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
まだ2つのクエリしかありません...