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

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top