1

QuestionTagモデルがあります。別のコレクションのタグのコレクションを使用して、既存の質問のタグを更新したいと思います。

これは私のQuestionモデルの方法です:

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

    end
  end

私は各ループ内で各ループを実行できることを知っていますが、それは最善のアプローチ(または最もDRY /ルビー風)のようには思えません。

基本的に私がやろうとしているのは、質問のタグが存在しない場合にそれらを更新することです。したがって、理論的には、各オブジェクトをチェックインしてtag_list、に存在するかどうかを確認したいと思いますquestion.tags。そうでない場合は、プッシュしたいと思います。含まれている場合は、無視して次の手順に進みます。

最も効率的なアプローチは何ですか?

編集1

Questionとモデルの両方の間にHABTMの関連付けがありTagます。

編集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 ..と呼ばれるこのためのネイティブ API を提供します。

blog.tags.replace(tag_list)

古い答え

ロジックをシンプルに保ちます。内部的には、Rails はアソシエーション レコードを1 回のトランザクションで保存します。これと手巻きの複数挿入ステートメントのパフォーマンスは同等である必要があります。また、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

(注:複数の更新を行いました。おそらく、 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つのクエリしかありません...

于 2013-03-22T15:54:37.617 に答える
0

必要かもしれませんaccepts_nested_attributes_for( docs )

于 2013-03-20T13:30:29.170 に答える