2008-11-24 12 views
26

¿Cómo puedo lograr lo siguiente? Tengo dos modelos (blogs y lectores) y una tabla de unión que me permite tener una relación N: M entre ellos:cómo evitar duplicados en una relación has_many: through?

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 añadir a los 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 (el mismo readerID, el 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 no están suscritos (por ejemplo, para completar una lista desplegable, que luego puede usarse para agregar el lector a otro blog) ?

Respuesta

5

¿Qué hay de:

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

rieles se encarga de la recogida de identificadores para nosotros, con métodos de asociación! :)

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

+0

Además, quería mencionar que este es probablemente el mejor método, ya que la respuesta aceptada selecciona TODOS los datos de las filas (por ejemplo, the_reader.blogs) mientras que mi respuesta selecciona solo los ID de las filas (por ejemplo, the_reader. blog_ids). ¡Este es un gran golpe de rendimiento! –

+0

esta es una solución mejor y debería ser la respuesta correcta. Gracias Josh. –

+0

thx Josh! ¡Parece más delgado de hecho! – Sebastian

32

Esto debe hacerse cargo de su primera pregunta:

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

    validates_uniqueness_of :reader_id, :scope => :blog_id 
end 
+0

que he estado tratando de resolver esto durante mucho tiempo, y esto nunca se me ocurrió! ¡Gran solución! ¡Gracias! – Arel

+1

Lea atentamente sobre Concurrencia e integridad aquí http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of –

1

Estoy pensando que alguien va a llegar con una respuesta mejor que esto.

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

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

[editar]

Por favor, véase la respuesta de Josh continuación. Es el camino a seguir. (Sabía que había una manera mejor por ahí;)

+0

, también podría hacer esto en una declaración utilizando find_by_sql. –

+0

¡Impresionante! Esto funciona perfectamente! ¡¡Muchas gracias!! – Sebastian

69

sencilla solución que se construye en los rieles:

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 añadiendo 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 opción de :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.

actualización

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 
+0

Creo que hay un problema con este enfoque si su modelo de unión tiene otros campos. Por ejemplo, un campo de posiciones para que cada niño pueda colocarse dentro de su padre. 'blog.readers << reader # blog_readers.position = 1;' 'blog.readers << reader # blog_readers.position = 2' Como el segundo blog_readers tiene una posición diferente, la configuración de uniq no la ve como una existente entrada y permite que se cree – ReggieB

+2

Si tiene un alcance predeterminado que ordena sus blogs, tendrá que desmarcar eso (o DISTINCT fallará), puede usar esto: '' 'has_many: blogs, -> {unscope (: orden) .uniq}, a través de:: blog_readers''' – marksiemers

0

La manera más fácil es para 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, cuando la asignación de valores a los lectores, se las aplica como

blog.reader_ids = [1,2,3,4] 

Al asignar relaciones de esta manera, los duplicados se eliminan automáticamente.

14

Los rieles 5,1 manera

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 
+0

Razonamiento: https://github.com/rails/rails/pull/9683 y https://github.com/rails/rails/commit/adfab2dcf4003ca564d78d4425566dd2d9cd8b4f – tmaier

+0

@pastullo Pero aún así, inserte los datos en la tabla del medio blog_readers. cómo prevenir eso? – Vishal

0

La respuesta más común actualmente dice que use uniq en el proc:

class Blog < ActiveRecord::Base 
has_many :blogs_readers, dependent: :destroy 
has_many :readers, -> { uniq }, through: :blogs_readers 
end 

Sin embargo, esto se inicia la relación en una matriz y puede romper las cosas que son esperando realizar operaciones en una relación, no en una matriz.

Si utiliza 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 
Cuestiones relacionadas