web-dev-qa-db-ja.com

has_many:through関係での重複を回避する方法は?

どうすれば次のことを達成できますか? 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番目の質問は、読者がまだ購読していないブログのリストを取得するにはどうすればよいですか(たとえば、ドロップダウン選択リストに入力して、読者を別のブログに追加するために使用できます)。

30
Sebastian

どうですか:

Blog.find(:all,
          :conditions => ['id NOT IN (?)', the_reader.blog_ids])

Railsは、関連付けメソッドを使用してIDの収集を処理します。 :)

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

5
Josh Delsman

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
82
Otto

これで最初の質問に対処できます。

class BlogsReaders < ActiveRecord::Base
  belongs_to :blog
  belongs_to :reader

  validates_uniqueness_of :reader_id, :scope => :blog_id
end
36
Mike Breen

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
17
pastullo

このリンクの答えは、「<<」メソッドをオーバーライドして、例外を発生させたり、別のメソッドを作成したりせずに、探しているものを実現する方法を示しています。 has_many:throughでの重複を避けるための慣用句

2
Hollownest

誰かがこれよりも良い答えを持ってくると思います。

the_reader = Reader.find(:first, :include => :blogs)

Blog.find(:all, 
          :conditions => ['id NOT IN (?)', the_reader.blogs.map(&:id)])

[編集]

以下のジョシュの答えをご覧ください。それが進むべき道です。 (私はそこにもっと良い方法があることを知っていました;)

1
Mike Breen

一番上の答えは現在、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
1
JD Isaacks