как избежать дубликатов в отношениях has_many: through? - PullRequest
28 голосов
/ 25 ноября 2008

Как мне добиться следующего? У меня есть две модели (блоги и читатели) и таблица JOIN, которая позволит мне иметь отношение 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

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

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

Ответы [ 8 ]

77 голосов
/ 25 ноября 2008

Более простое решение, встроенное в 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.

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

Обновление

В 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
36 голосов
/ 25 ноября 2008

Это должен позаботиться о вашем первом вопросе:

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

  validates_uniqueness_of :reader_id, :scope => :blog_id
end
17 голосов
/ 09 августа 2016

Рельсы 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
5 голосов
/ 25 ноября 2008

А как же:

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

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

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

2 голосов
/ 03 января 2011

Ответ по этой ссылке показывает, как переопределить метод «<<», чтобы достичь того, что вы ищете, без создания исключений или создания отдельного метода: <a href="https://stackoverflow.com/questions/1315109/rails-idiom-to-avoid-duplicates-in-has-many-through"> Rails идиома, чтобы избежать дубликатов в has_many: через

1 голос
/ 04 августа 2017

В верхнем ответе в настоящее время говорится, что в процедуре используется 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 голос
/ 25 ноября 2008

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

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

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

[править]

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

0 голосов
/ 03 февраля 2016

Самый простой способ - сериализовать отношения в массив:

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]

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

...