Como evitar duplicatas em A Has_Many: através do relacionamento?
-
11-07-2019 - |
Pergunta
Como posso alcançar o seguinte? Eu tenho dois modelos (blogs e leitores) e uma tabela de junção que me permitirá ter um relacionamento N: M entre eles:
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
O que eu quero fazer agora é adicionar leitores a blogs diferentes. A condição, porém, é que eu só posso adicionar um leitor a um blog uma vez. Portanto, não deve haver duplicatas (o mesmo readerID
, mesmo blogID
) no BlogsReaders
tabela. Como posso conseguir isso?
A segunda pergunta é: como faço para obter uma lista de blogs que os leitores já não estão assinados (por exemplo, para preencher uma lista de seleção suspensa, que pode ser usada para adicionar o leitor a outro blog)?
Solução
A respeito:
Blog.find(:all,
:conditions => ['id NOT IN (?)', the_reader.blog_ids])
O Rails cuida da coleção de IDs para nós com métodos de associação! :)
http://api.rubyonrails.org/classes/activerecord/associations/classmethods.html
Outras dicas
Solução mais simples que está embutida em trilhos:
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
Nota Adicionando o :uniq => true
opção para o has_many
ligar.
Também você pode querer considerar has_and_belongs_to_many
Entre o blog e o leitor, a menos que você tenha outros atributos que gostaria de ter no modelo de junção (o que não tem, atualmente). Esse método também tem um :uniq
opiton.
Observe que isso não impede que você crie as entradas na tabela, mas garante que, ao consultar a coleção, obtenha apenas um de cada objeto.
Atualizar
Nos Rails 4, a maneira de fazer isso é através de um bloco de escopo. O acima muda para.
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
Atualização para Rails 5
O uso de uniq
no bloco de escopo causará um erro NoMethodError: undefined method 'extensions' for []:Array
. Usar distinct
em vez de :
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
Isso deve cuidar da sua primeira pergunta:
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
validates_uniqueness_of :reader_id, :scope => :blog_id
end
O Rails 5.1 Way
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
A resposta neste link mostra como substituir o método "<<" para alcançar o que você está procurando sem aumentar exceções ou criar um método separado: Rails Idiom para evitar duplicatas em Has_Many: através de
Estou pensando que alguém virá junto com uma resposta melhor do que isso.
the_reader = Reader.find(:first, :include => :blogs)
Blog.find(:all,
:conditions => ['id NOT IN (?)', the_reader.blogs.map(&:id)])
editar
Por favor, veja a resposta de Josh abaixo. É o caminho a seguir. (Eu sabia que havia uma maneira melhor lá fora;)
A resposta superior atualmente diz para usar uniq
no Proc:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { uniq }, through: :blogs_readers
end
No entanto, isso abre a relação em uma matriz e pode quebrar as coisas que esperam executar operações em uma relação, não em uma matriz.
Se você usar distinct
Ele mantém como uma relação:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end
A maneira mais fácil é serializar o relacionamento em uma matriz:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers
serialize :reader_ids, Array
end
Então, ao atribuir valores aos leitores, você os aplica como
blog.reader_ids = [1,2,3,4]
Ao atribuir relacionamentos dessa maneira, as duplicatas são removidas automaticamente.