如何使用其他项目集合更新项目集合?

时间:2013-03-20 13:19:39

标签: ruby-on-rails ruby-on-rails-3

我有Question& Tag模型。我想更新现有问题的标签,并在另一个集合中添加一组标签。

这是我Question模型上的方法:

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

    end
  end

我知道我可以在每个循环中执行每个循环,但这似乎不是最好的方法(甚至是最干的/ Ruby-esque)。

基本上我要做的是更新问题上的标签(如果它们不存在)。因此,理论上,我想检查tag_list中的每个对象,看它是否存在于question.tags中。如果没有,那么我想推动它。如果是,则忽略它并移动到下一个。

什么是最有效的方法?

修改1

我在QuestionTag模型之间建立了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]

这就是我想要做的事。

4 个答案:

答案 0 :(得分:3)

Rails为此称为replace ..

提供了一个原生api
blog.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_fordocs

答案 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