Pregunta

¿Cómo puedo lograr lo siguiente? Tengo dos modelos (blogs y lectores) y una tabla JOIN que me permitirá 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 duplicados (mismo readerID, mismo blogID) en la tabla BlogsReaders. ¿Cómo puedo lograr 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 se puede usar para agregar el lector a otro blog)?

¿Fue útil?

Solución

¿Qué pasa con:

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

¡Rails se encarga de la recopilación de identificadores para nosotros con métodos de asociación! :)

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

Otros consejos

Solución más simple 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

Nota agregar 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 combinación (que actualmente no tiene). Ese método también tiene una :uniq opción.

Tenga en cuenta que esto no le impide crear las entradas en la tabla, pero asegura que cuando consulta la colección obtiene solo uno de cada objeto.

Update

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

Actualización para Rails 5

El uso de uniq en el bloque de alcance provocará un error NoMethodError: undefined method 'extensions' for []:Array. Utilice distinct en su lugar:

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 su primera pregunta:

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

  validates_uniqueness_of :reader_id, :scope => :blog_id
end

La vía 5.1 de Rails

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

La respuesta en este enlace muestra cómo anular " < < " método para lograr lo que está buscando sin generar excepciones o crear un método separado: Rails modismos para evitar duplicados en has_many: a través de

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;)

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 patea la relación en una matriz y puede romper cosas que esperan realizar operaciones en una relación, no en 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

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top