どうすれば次のことを達成できますか? 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
私が今やりたいのは、さまざまなブログに読者を追加することです。ただし、条件は、ブログに読者を追加できるのは1回だけであるということです。したがって、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
オプションもあります。
これにより、テーブルにエントリを作成できなくなるわけではありませんが、コレクションをクエリすると、各オブジェクトが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
このリンクの答えは、「<<」メソッドをオーバーライドして、例外を発生させたり、別のメソッドを作成したりせずに、探しているものを実現する方法を示しています。 has_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