как избежать дубликатов в много:через отношения?

как я могу достичь следующих? У меня есть две модели (блоги и читатели) и таблица соединений, которая позволит мне иметь отношение N:M между ними:

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

что я хочу сделать сейчас, это добавить читателей в разных блогах. Условие, однако, заключается в том, что я могу добавить читателя в блог только один раз. Поэтому не должно быть никаких дубликатов (same readerID, то же самое blogID) в BlogsReaders таблица. Как я могу достичь этого?

второй вопрос: как я могу получить список блог что читатели еще не подписаны (например, для заполнения выпадающего списка выбора, который затем можно использовать для добавления читателя в другой блог)?

8 ответов


о:

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

Rails заботится о коллекции идентификаторов для нас с методами ассоциации! :)

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

Примечание добавление до has_many звонок.

также вы можете рассмотреть has_and_belongs_to_many между блогом и читателем, если у вас нет других атрибутов, которые вы хотели бы иметь в модели join (чего в настоящее время нет). Этот метод также имеет :uniq opiton.

обратите внимание, что это не мешает вам создавать записи в таблице, но это гарантирует, что когда вы запрос коллекции вы получаете только один из каждого объекта.

обновление

в 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

рельсы 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

ответ по этой ссылке показывает, как переопределить метод "идиома Rails, чтобы избежать дубликатов в has_many: через


Я думаю, что кто-то придет с лучшим ответом, чем это.

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

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

[edit]

пожалуйста, смотрите ответ Джоша ниже. Так надо поступать. (Я знал, что там есть лучший выход ;)


верхний ответ в настоящее время говорит, чтобы использовать uniq в proc:

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]

при назначении отношений таким образом дубликаты автоматически удаляются.