как избежать дубликатов в отношениях has_many :through?
-
11-07-2019 - |
Вопрос
Как я могу добиться следующего?У меня есть две модели (блоги и читатели) и таблица соединений, которая позволит мне иметь отношение 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
Что я хочу сделать сейчас, так это добавить читателей в разные блоги.Однако условие таково, что я могу добавить читателя в блог только ОДИН РАЗ.Таким образом, не должно быть никаких дубликатов (одинаковых readerID
, то же самое blogID
) в BlogsReaders
таблица.Как я могу этого добиться?
Второй вопрос заключается в том, как мне получить список блогов, на которые читатели еще не подписаны (напримерчтобы заполнить выпадающий список выбора, который затем можно использовать для добавления читателя в другой блог)?
Решение
Как насчет
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
Обратите внимание на добавление :uniq => true
вариант для has_many
позвони.
Также вы, возможно, захотите рассмотреть has_and_belongs_to_many
между Blog и Reader, если только у вас нет каких-либо других атрибутов, которые вы хотели бы иметь в модели объединения (которых у вас в настоящее время нет).Этот метод также имеет :uniq
опитон.
Обратите внимание, что это не мешает вам создавать записи в таблице, но гарантирует, что при запросе коллекции вы получите только по одной из каждого объекта.
Обновить
В Rails 4 это можно сделать с помощью блока scope.Вышеуказанное изменяется на.
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
Ответ по этой ссылке показывает, как переопределить " < < " метод для достижения того, что вы ищете, без привлечения исключений или создания отдельного метода: Rails идиома, чтобы избежать дубликатов в has_many: через
Я думаю, что кто-нибудь найдет ответ получше, чем этот.
the_reader = Reader.find(:first, :include => :blogs)
Blog.find(:all,
:conditions => ['id NOT IN (?)', the_reader.blogs.map(&:id)])
[править]
Пожалуйста, ознакомьтесь с ответом Джоша ниже.Это правильный путь.(Я знал, что есть способ получше ;)
В верхнем ответе в настоящее время написано, что нужно использовать 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]
При таком назначении отношений дубликаты удаляются автоматически.