Rails has_many: durch Suchen nach zusätzlichen Attributen im Join-Modell

70

Neu für Ruby und Rails, aber ich bin inzwischen in einem Buch ausgebildet (was anscheinend nichts bedeutet, haha).

Ich habe zwei Modelle, Event und User, die über eine Tabelle EventUser verbunden sind

class User < ActiveRecord::Base
  has_many :event_users
  has_many :events, :through => :event_users
end

class EventUser < ActiveRecord::Base
  belongs_to :event
  belongs_to :user

  #For clarity's sake, EventUser also has a boolean column "active", among others
end

class Event < ActiveRecord::Base
  has_many :event_users
  has_many :users, :through => :event_users
end

Dieses Projekt ist ein Kalender, in dem ich nachverfolgen muss, wie sich Leute für eine bestimmte Veranstaltung anmelden und ihren Namen auskratzen. Ich denke, dass viele zu viele ein guter Ansatz sind, aber ich kann so etwas nicht tun:

u = User.find :first
active_events = u.events.find_by_active(true)

Da Ereignisse diese zusätzlichen Daten nicht haben, ist dies beim EventUser-Modell der Fall. Und während ich tun konnte:

u = User.find :first
active_events = []
u.event_users.find_by_active(true).do |eu|
  active_events << eu.event
end

Dies scheint dem "Schienenweg" zu widersprechen. Kann mich jemand aufklären, das nervt mich heute Abend (heute Morgen) schon lange?

Stefan Mai
quelle
1
Sie können auch einen benannten Bereich mit Gareths Vorschlag erstellen.
Arjun

Antworten:

123

Wie wäre es, wenn Sie Ihrem Benutzermodell so etwas hinzufügen?

has_many  :active_events, :through => :event_users, 
          :class_name => "Event", 
          :source => :event, 
          :conditions => ['event_users.active = ?',true]

Danach sollten Sie in der Lage sein, aktive Ereignisse für einen Benutzer zu erhalten, indem Sie einfach Folgendes aufrufen:

User.first.active_events
Milan Novota
quelle
1
Ich habe dich markiert, aber wenn ich bis zum Morgen keine bessere Antwort bekomme, gehört es dir.
Stefan Mai
Siehe meine Antwort unten für eine aktuellere Methode
msanteler
9
Danke, Rails 4 Weg ist:-> { where event_users: { active: true } }
Ivan Black
Aber gibt es keine Chance, dass die Bedingung durch einen event_usersBereich definiert wird ? Wie scope :active, where(:active => true)? (mit Rails 3.2)
Augustin Riedinger
@IvanBlack könntest du erklären, wo das passen würde has_many through:? Könnten Sie das gesamte Rails 4-spezifische Äquivalent einschließen?
Damien Roche
22

Milan Novota hat eine gute Lösung - ist aber :conditionsjetzt veraltet und das :conditions => ['event_users.active = ?',true]Stück scheint sowieso nicht sehr schienen zu sein. Ich bevorzuge so etwas:

has_many :event_users
has_many :active_event_users, -> { where active: true }, class_name: 'EventUser'
has_many :active_events, :through => :active_event_users, class_name: 'Event', :source => :event

Danach sollten Sie weiterhin in der Lage sein, aktive Ereignisse für einen Benutzer abzurufen, indem Sie Folgendes aufrufen:

User.first.active_events
msanteler
quelle
1
Wenn Sie noch nicht auf Rails 4 sind, können Sie diesem hervorragenden Beispiel weiterhin folgen, müssen jedoch möglicherweise das veraltete verwenden :conditions => {active: true}. Arbeitete für mich, danke!
Monozok
12

Auch wenn Ihre u.events die Tabelle user_events nicht explizit aufrufen, ist diese Tabelle aufgrund der erforderlichen Verknüpfungen implizit in SQL enthalten . Sie können diese Tabelle also weiterhin in Ihren Suchbedingungen verwenden:

u.events.find(:all, :conditions => ["user_events.active = ?", true])

Natürlich gäbe ihm , wenn Sie viel diese Lookup zu tun , dann sicher planen, einen separaten Verein wie Milan Novota schon sagt, aber es gibt keine Anforderung für Sie , es so zu tun

Gareth
quelle
Ich möchte alle Bereiche an einem Ort definieren - im Modell, auch wenn ich sie nur einmal verwende. Es hält meine Controller dünn und das Gesamtdesign konsistenter.
Milan Novota
8

Nun, es wird mehr Verantwortung in das UserModell eingebracht, als tatsächlich benötigt wird, und es gibt keinen guten Grund dafür.

Wir können den Umfang zuerst im EventUserModell definieren, weil dort, wo er tatsächlich hingehört, wie:

class EventUser < ActiveRecord::Base
  belongs_to :event
  belongs_to :user

  scope :active,   -> { where(active: true)  }
  scope :inactive, -> { where(active: false) } 
end

Jetzt kann ein Benutzer beide Arten von Ereignissen haben: aktive Ereignisse sowie inaktive Ereignisse, sodass wir die Beziehung im UserModell wie folgt definieren können:

class User < ActiveRecord::Base
  has_many :active_event_users,   -> { active },   class_name: "EventUser"
  has_many :inactive_event_users, -> { inactive }, class_name: "EventUser"

  has_many :inactive_events, through: :inactive_event_user,
                             class_name: "Event",
                             source: :event
  has_many :active_events,   through: :active_event_users,
                             class_name: "Event",
                             source: :event
end

Das Schöne an dieser Technik ist, dass die Funktionalität, ein aktives oder ein inaktives Ereignis zu sein EventUser, zum Modell gehört. Wenn die Funktionalität in Zukunft geändert werden muss, wird sie nur an einer Stelle geändert: EventUserModell, und die Änderungen werden reflektiert alle anderen Modelle.

Arslan Ali
quelle
1
Ihre Lösung machte meinen Tag :)
Bilal Maqsood