モデルのかなり単純なHABTMセットがあります
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
def tags= (tag_list)
self.tags.clear
tag_list.strip.split(' ').each do
self.tags.build(:name => tag)
end
end
end
これで、タグテーブルに大量の重複が表示されることを除いて、すべて正常に機能します。
タグテーブルの重複(名前に基づく)を回避するために何をする必要がありますか?
私はこれを回避するために、問題を修正するbefore_saveフィルターを作成しました。
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
before_save :fix_tags
def tag_list= (tag_list)
self.tags.clear
tag_list.strip.split(' ').each do
self.tags.build(:name => tag)
end
end
def fix_tags
if self.tags.loaded?
new_tags = []
self.tags.each do |tag|
if existing = Tag.find_by_name(tag.name)
new_tags << existing
else
new_tags << tag
end
end
self.tags = new_tags
end
end
end
タグを使用してバッチで動作するようにわずかに最適化することもできますが、わずかに優れたトランザクションサポートが必要になる場合もあります。
次のは、データベースへの重複する関係の書き込みを防止しません。これは、find
メソッドが重複を無視することを保証するだけです。
In Rails 5:
_has_and_belongs_to_many :tags, -> { distinct }
_
注:_Relation#uniq
_はRails 5( commit )
In Rails 4
_has_and_belongs_to_many :tags, -> { uniq }
_
オプション1:コントローラーからの重複を防止します。
_post.tags << tag unless post.tags.include?(tag)
_
ただし、複数のユーザーが同時にpost.tags.include?(tag)
を試行できるため、これは競合状態の影響を受けます。これについては here で説明します。
堅牢性のために、これをPostモデル(post.rb)に追加することもできます
_def tag=(tag)
tags << tag unless tags.include?(tag)
end
_
オプション2:一意のインデックスを作成します
重複を防止する最も確実な方法は、データベース層に重複制約を設けることです。これは、テーブル自体に_unique index
_を追加することで実現できます。
_Rails g migration add_index_to_posts
# migration file
add_index :posts_tags, [:post_id, :tag_id], :unique => true
add_index :posts_tags, :tag_id
_
一意のインデックスを取得したら、重複するレコードを追加しようとすると、_ActiveRecord::RecordNotUnique
_エラーが発生します。これを処理することは、この質問の範囲外です。表示 これSO質問 。
_rescue_from ActiveRecord::RecordNotUnique, :with => :some_method
_
さらに、上記の提案:
:uniq
からhas_and_belongs_to_many
関連付け私は、関係がすでに存在するかどうかを判断するために明示的なチェックを行います。例えば:
post = Post.find(1)
tag = Tag.find(2)
post.tags << tag unless post.tags.include?(tag)
Rails4の場合:
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags, -> { uniq }
(注意、-> { uniq }
は、リレーション名の直後、他のパラメータの前にある必要があります)
:uniq
オプションとして ドキュメントに記載 。また、:uniq
オプションは、重複する関係の作成を防止しません。それは、アクセサ/検索メソッドがそれらを一度だけ選択することを保証するだけです。
関連テーブルの重複を防ぎたい場合は、一意のインデックスを作成して例外を処理する必要があります。また、validates_uniqueness_ofは期待どおりに動作しません。これは、最初のリクエストが重複をチェックしてからデータベースに書き込むまでの間に、2番目のリクエストがデータベースに書き込んでいるケースに陥る可能性があるためです。
Uniqオプションを設定します。
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts , :uniq => true
end
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags , :uniq => true
このようにモデルを調整してクラスを作成したいと思います。
class Tag < ActiveRecord::Base
has_many :taggings
has_many :posts, :through => :taggings
end
class Post < ActiveRecord::Base
has_many :taggings
has_many :tags, :through => :taggings
end
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :post
end
次に、作成をロジックでラップして、タグモデルがすでに存在する場合は再利用できるようにします。タグ名に一意の制約を課して、それを強制することもできます。結合テーブルのインデックスを使用するだけで特定のタグのすべての投稿と特定の投稿のすべてのタグを見つけることができるため、どちらの方法でもより効率的に検索できます。
唯一の問題は、タグ名を変更するとそのタグのすべての使用に影響するため、タグの名前を変更できないことです。ユーザーにタグを削除させ、代わりに新しいタグを作成させます。
これは本当に古いですが、自分のやり方を共有したいと思いました。
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
end
投稿にタグを追加する必要があるコードでは、次のようにします。
new_tag = Tag.find_by(name: 'cool')
post.tag_ids = (post.tag_ids + [new_tag.id]).uniq
これには、必要に応じてタグを自動的に追加/削除するか、そうであれば何もしないという効果があります。
私に働く
リレーションの<<メソッドをオーバーライドする
has_and_belongs_to_many :groups do
def << (group)
group -= self if group.respond_to?(:to_a)
super group unless include?(group)
end
end
セキュリティのためにタグ名を抽出します。タグテーブルにタグが存在するかどうかを確認し、存在しない場合は作成します。
name = params[:tag][:name]
@new_tag = Tag.where(name: name).first_or_create
次に、それがこの特定のコレクション内に存在するかどうかを確認し、存在しない場合はプッシュします。
@taggable.tags << @new_tag unless @taggable.tags.exists?(@new_tag)
Tag:nameプロパティにインデックスを追加し、Tags#createメソッドでfind_or_createメソッドを使用する必要があります
レコードを追加する前に、コントローラーにチェックを追加するだけです。含まれている場合は何もせず、含まれていない場合は新しいものを追加します。
u = current_user
a = @article
if u.articles.exists?(a)
else
u.articles << a
end
詳細:「4.4.1.14 collection.exists?(...)」 http://edgeguides.rubyonrails.org/association_basics.html#scopes-for-has-and-belongs-to-many