ruby-on-rails - has_one - rails has_many through example
¿Cómo evitar duplicados en una relación has_many: through? (8)
¿Cómo puedo lograr lo siguiente? Tengo dos modelos (blogs y lectores) y una tabla de JOIN que me permitirán tener una relación N: M entre ellos:
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
Lo que quiero hacer ahora es agregar lectores a diferentes blogs. La condición, sin embargo, es que solo puedo agregar un lector a un blog UNA VEZ. Por lo tanto, no debe haber ningún duplicado (mismo readerID
, mismo blogID
) en la tabla BlogsReaders
. ¿Cómo puedo conseguir esto?
La segunda pregunta es, ¿cómo obtengo una lista de blog a la que los lectores ya no están suscritos (por ejemplo, para completar una lista de selección desplegable, que luego puede usarse para agregar el lector a otro blog)?
La forma 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
Esto debería encargarse de tu primera pregunta:
class BlogsReaders < ActiveRecord::Base
belongs_to :blog
belongs_to :reader
validates_uniqueness_of :reader_id, :scope => :blog_id
end
Estoy pensando que alguien vendrá con una mejor respuesta que esta.
the_reader = Reader.find(:first, :include => :blogs)
Blog.find(:all,
:conditions => [''id NOT IN (?)'', the_reader.blogs.map(&:id)])
[editar]
Por favor, vea la respuesta de Josh a continuación. Es el camino a seguir. (Sabía que había una mejor manera de salir de allí;)
Qué pasa:
Blog.find(:all,
:conditions => [''id NOT IN (?)'', the_reader.blog_ids])
¡Rails se encarga de la recopilación de identificadores para nosotros con los métodos de asociación! :)
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Una solución más simple que está integrada en 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
Tenga en cuenta que agrega la opción :uniq => true
a la llamada has_many
.
También es posible que desee considerar has_and_belongs_to_many
entre Blog y Reader, a menos que tenga otros atributos que le gustaría tener en el modelo de unión (que no tiene actualmente). Ese método también tiene :uniq
opiton :uniq
.
Tenga en cuenta que esto no le impide crear las entradas en la tabla, pero sí garantiza que cuando consulte la colección obtendrá solo una de cada objeto.
Actualizar
En Rails 4 la forma de hacerlo es a través de un bloque de alcance. Lo anterior cambia a.
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
La forma más fácil es serializar la relación en una matriz:
class Blog < ActiveRecord::Base
has_many :blogs_readers, :dependent => :destroy
has_many :readers, :through => :blogs_readers
serialize :reader_ids, Array
end
Luego, al asignar valores a los lectores, los aplica como
blog.reader_ids = [1,2,3,4]
Al asignar relaciones de esta manera, los duplicados se eliminan automáticamente.
La respuesta en este enlace muestra cómo anular el método "<<" para lograr lo que está buscando sin generar excepciones o crear un método separado: Rails idioma para evitar duplicados en has_many: a través de
La respuesta principal actualmente dice usar uniq
en el proceso:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { uniq }, through: :blogs_readers
end
Sin embargo, esto inicia la relación en una matriz y puede romper cosas que están esperando realizar operaciones en una relación, no una matriz.
Si usa distinct
lo mantiene como una relación:
class Blog < ActiveRecord::Base
has_many :blogs_readers, dependent: :destroy
has_many :readers, -> { distinct }, through: :blogs_readers
end