has_many:through関係で重複を避ける方法は?
-
11-07-2019 - |
質問
以下を達成するにはどうすればよいですか? 2つのモデル(ブログとリーダー)と、それらの間にN:Mの関係を持たせることができるJOINテーブルがあります:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :blogs, :through => :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
今、私がしたいことは、読者をさまざまなブログに追加することです。ただし、条件は、一度だけブログにリーダーを追加できることです。したがって、readerID
テーブルに重複(同じblogID
、同じBlogsReaders
)があってはなりません。どうすればこれを達成できますか?
2番目の質問は、読者がまだ購読していないブログのリストを取得する方法です(たとえば、ドロップダウン選択リストに入力して、読者を別のブログに追加するために使用できます)。
解決
概要:
Blog.find(:all,
:conditions => ['id NOT IN (?)', the_reader.blog_ids])
Railsは、関連付けメソッドを使用してIDのコレクションを処理します! :)
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ ClassMethods.html
他のヒント
Railsに組み込まれているシンプルなソリューション:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers, :uniq => true
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :blogs, :through => :blogs_readers, :uniq => true
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
:uniq => true
呼び出しにhas_many
オプションを追加することに注意してください。
また、ブログとリーダーの間でhas_and_belongs_to_many
を検討することもできます。ただし、結合モデルにその他の属性を持たせたい場合(現在はそうではありません)。このメソッドには:uniq
opitonもあります。
これは、テーブルにエントリを作成することを妨げるわけではありませんが、コレクションをクエリすると、各オブジェクトが1つだけ取得されることに注意してください。
更新
Rails 4では、スコープブロックを使用する方法があります。上記が変更されます。
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { uniq }, through: :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :blogs, -> { uniq }, through: :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
Rails 5の更新
スコープブロックでuniq
を使用すると、エラーNoMethodError: undefined method 'extensions' for []:Array
が発生します。代わりにdistinct
を使用します:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :blogs, -> { distinct }, through: :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
これで最初の質問が処理されます:
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
validates_uniqueness_of :reader_id, :scope => :blog_id
end
Rails 5.1の方法
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end
class Reader < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :blogs, -> { distinct }, through: :blogs_readers
end
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
end
このリンクの回答は、<!> quot; <!> lt; <!> lt; <!> quot;をオーバーライドする方法を示しています。例外を発生させたり、別のメソッドを作成したりすることなく、探しているものを実現する方法: hails_manyでの重複を避けるためのレールイディオム:through
誰かがこれよりも良い答えを出すと思います。
the_reader = Reader.find(:first, :include => :blogs)
Blog.find(:all,
:conditions => ['id NOT IN (?)', the_reader.blogs.map(&:id)])
[編集]
以下のジョシュの答えをご覧ください。それは行く方法です。 (そこにもっと良い方法があることは知っていました;)
現在、一番の答えは、procでuniq
を使用することです:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { uniq }, through: :blogs_readers
end
ただし、これはリレーションを配列にキックし、配列ではなく、リレーションで操作を実行することを期待しているものを壊す可能性があります。
distinct
を使用すると、リレーションとして保持されます:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end
最も簡単な方法は、関係を配列にシリアル化することです:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers
serialize :reader_ids, Array
end
次に、リーダーに値を割り当てるときに、それらを次のように適用します
blog.reader_ids = [1,2,3,4]
この方法で関係を割り当てると、重複は自動的に削除されます。