1

我有一个Question&Tag模型。我想用另一个集合中的一组标签来更新现有问题的标签。

这是我Question模型上的方法:

  def self.update_tags(tag_list)
    tags.each do |t|

    end
  end

我知道我可以在每个循环中执行每个循环,但这似乎不是最好的方法(甚至不是最 DRY/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的当前问题,并根据 .AR 对象的 id 检查它tag_list

说一个以前的问题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].

这就是我想要做的。

4

4 回答 4

3

Rails 为此提供了一个名为replace ..

blog.tags.replace(tag_list)

旧答案

我会保持逻辑简单。在内部,rails 将关联记录保存在一个事务中。这和手卷多插入语句的性能应该是可比的。此外,使用 rails 层可以让您避免处理新的和保存的父对象的复杂性。

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
于 2013-03-22T18:02:09.727 回答
0

可能你需要accepts_nested_attributes_for文档

于 2013-03-20T13:30:29.170 回答
0

注意:我进行了多次更新,您可能对UPDATE 2UPDATE 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 个查询中仅选择标签 ID,而不是实例化整个标签对象

2)在单个 SQL 语句中插入多个值INSERT INTO "questions_tags" ("question_id", "tag_id") VALUES ( <question_id>, <id1> ), ( <question_id>, <id2> ),但您可能需要为此使用原始 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 中有标签名称,而不是标签对象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

仍然只有两个查询...

于 2013-03-22T15:54:37.617 回答
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 – 检查同名记录是否已经存在

于 2013-03-22T16:13:32.357 回答