Domanda

Come posso ottenere quanto segue? Ho due modelli (blog e lettori) e una tabella JOIN che mi permetterà di avere una relazione N: M tra di loro:

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

Quello che voglio fare ora è aggiungere lettori a blog diversi. La condizione, tuttavia, è che posso solo aggiungere un lettore a un blog UNA VOLTA. Quindi non ci devono essere duplicati (stesso readerID, stesso blogID) nella tabella BlogsReaders. Come posso raggiungere questo obiettivo?

La seconda domanda è: come posso ottenere un elenco di blog a cui i lettori non sono già abbonati (ad es. per compilare un elenco di selezione a discesa, che può quindi essere utilizzato per aggiungere il lettore a un altro blog)?

È stato utile?

Soluzione

Che dire di:

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

Rails si occupa della raccolta di ID per noi con metodi di associazione! :)

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

Altri suggerimenti

Soluzione più semplice integrata in 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 aggiungendo l'opzione :uniq => true alla has_many chiamata.

Inoltre, potresti prendere in considerazione has_and_belongs_to_many tra Blog e Reader, a meno che tu non abbia alcuni altri attributi che vorresti avere sul modello di join (cosa che al momento no). Questo metodo ha anche un :uniq opiton.

Nota che questo non ti impedisce di creare le voci nella tabella, ma ti assicura che quando esegui una query sulla raccolta ottieni solo uno di ogni oggetto.

Aggiorna

In Rails 4 il modo per farlo è tramite un blocco dell'ambito. Quanto sopra diventa.

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

Aggiornamento per Rails 5

L'uso di uniq nel blocco dell'ambito provocherà un errore NoMethodError: undefined method 'extensions' for []:Array. Usa invece distinct:

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

Questo dovrebbe occuparsi della tua prima domanda:

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

  validates_uniqueness_of :reader_id, :scope => :blog_id
end

La via di 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

La risposta a questo link mostra come sovrascrivere " < < " metodo per ottenere ciò che stai cercando senza sollevare eccezioni o creare un metodo separato: Rails idioma per evitare duplicati in has_many: attraverso

Penso che qualcuno troverà una risposta migliore di questa.

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

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

[modifica]

Vedi la risposta di Josh qui sotto. È la strada da percorrere. (Sapevo che c'era una via migliore là fuori;)

La risposta principale attualmente dice di usare uniq nel proc:

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

Questo però dà il via alla relazione in un array e può spezzare cose che si aspettano di eseguire operazioni su una relazione, non su un array.

Se si utilizza distinct lo mantiene come una relazione:

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

Il modo più semplice è serializzare la relazione in un array:

class Blog < ActiveRecord::Base
  has_many :blogs_readers, :dependent => :destroy
  has_many :readers, :through => :blogs_readers
  serialize :reader_ids, Array
end

Quindi, quando si assegnano valori ai lettori, li si applica come

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

Quando si assegnano le relazioni in questo modo, i duplicati vengono rimossi automaticamente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top