我有Question
& Tag
模型。我想更新现有问题的标签,并在另一个集合中添加一组标签。
这是我Question
模型上的方法:
def self.update_tags(tag_list)
tags.each do |t|
end
end
我知道我可以在每个循环中执行每个循环,但这似乎不是最好的方法(甚至是最干的/ Ruby-esque)。
基本上我要做的是更新问题上的标签(如果它们不存在)。因此,理论上,我想检查tag_list
中的每个对象,看它是否存在于question.tags
中。如果没有,那么我想推动它。如果是,则忽略它并移动到下一个。
什么是最有效的方法?
修改1
我在Question
和Tag
模型之间建立了HABTM关联。
修改2
我知道这是一个典型的N + 1查询问题,所以我试图找出以最有效的方式完成此任务的最佳方法。
编辑3
这是对我正在努力实现的目标和结果的解释 - 以有效的方式。
tag_list
正在构建如下:
tags.each do |tag|
tag_list << Tag.where(:name => tag.name).first_or_create(:num_questions => tag.count)
end
tags
是从先前调用外部API返回的对象集合。
我需要查看当前问题的所有现有question.tags
,并根据tag_list
中AR对象的ID进行检查。
以前提问过tag_ids
[5, 7, 8, 10]
{我想要发生的事情现在是tag_list = [5, 6, 7, 8, 9]
,我想更新question.tag_ids = [5, 6, 7, 8, 9]
。
因此,这会删除tag_id=10
,并添加tag_id=[6, 9]
。
这就是我想要做的事。
答案 0 :(得分:3)
Rails为此称为replace ..
提供了一个原生apiblog.tags.replace(tag_list)
旧答案
我会保持逻辑简单。在内部,rails将关联记录保存在one transaction中。这个和手工多插入声明的性能应该是可比较的。此外,使用rails层可以防止处理新的vs保存父对象的复杂性。
def self.update_tags(tag_list)
# Add new tags
current_tags = self.tags.dup
new_tags = tag_list - current_tags
tags.concat(new_tags) if new_tags.present?
# Remove defunct tags
old_tags = current_tags - tag_list
tags.delete(old_tags) if old_tags.present?
end
答案 1 :(得分:0)
您可能需要accepts_nested_attributes_for
(docs)
答案 2 :(得分:0)
(注意:我做过多次更新,您可能对 UPDATE 2 或更新3 中提供的代码最感兴趣。)< / p>
我想你可以在你的问题模型中放置以下内容:
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)在INSERT INTO "questions_tags" ("question_id", "tag_id") VALUES ( <question_id>, <id1> ), ( <question_id>, <id2> )
之类的单个SQL语句中插入多个值,但您可能需要使用原始sql。
更新:这是优化版本:
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 :刚刚意识到您不需要阅读第二个问题中的标记,您已经在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中有名称,而不是标记对象,那么这里是更新(假设你有{标记模型中的{1}}属性:
name
仍然只有两个问题......
答案 3 :(得分:0)
您可以使用以下方法检查问题中是否存在标记:
@question.tags.where(:id => tag_id).present? #check if the tag_id is inside the question.
但看着你的需要:
def tag_names
# Get all related Tags as comma-separated list
tag_list = []
tags.each do |tag|
tag_list << tag.name
end
tag_list.join(', ')
end
def tag_names=(names)
# Delete tag-relations
self.tags.delete_all
# Split comma-separated list
names = names.split(', ')
# Run through each tag
names.each do |name|
tag = Tag.find_by_name(name)
if tag
# If the tag already exists, create only join-model
self.tags << tag
else
# New tag, save it and create join-model
tag = self.tags.new(:name => name)
if tag.save
self.tags << tag
end
end
end
end
从此处提取代码:Rails HABTM fields_for – check if record with same name already exists