web-dev-qa-db-fra.com

Rails has_many: via Find by Extra Attributes in Join Model

Nouveau pour les deux Ruby et Rails mais je suis éduqué maintenant (ce qui ne signifie apparemment rien, haha).

J'ai deux modèles, Event et User joints via une table EventUser

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

Ce projet est un calendrier dans lequel je dois garder une trace des personnes qui s'inscrivent et rayent leur nom pour un événement donné. Je pense que le plusieurs à plusieurs est une bonne approche, mais je ne peux pas faire quelque chose comme ça:

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

Étant donné que les événements n'ont PAS réellement ces données supplémentaires, le modèle EventUser en a. Et pendant que je pouvais faire:

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

Cela semble être contraire à "la Rails façon". Quelqu'un peut-il m'éclairer, cela me dérange depuis longtemps ce soir (ce matin)?

70
Stefan Mai

Que diriez-vous d'ajouter quelque chose comme ça dans votre modèle d'utilisateur?

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

Après cela, vous devriez pouvoir obtenir des événements actifs pour un utilisateur simplement en appelant:

User.first.active_events
123
Milan Novota

Milan Novota a une bonne solution - mais :conditions est désormais obsolète et le :conditions => ['event_users.active = ?',true] bit ne semble tout simplement pas très Rails de toute façon. Je préfère quelque chose comme ça:

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

Après cela, vous devriez toujours pouvoir obtenir des événements actifs pour un utilisateur simplement en appelant:

User.first.active_events
22
msanteler

Même si votre u.events n'appelle pas explicitement la table user_events, cette table est toujours incluse dans le SQL implicitement en raison des jointures nécessaires. Ainsi, vous pouvez toujours utiliser ce tableau dans vos conditions de recherche:

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

Bien sûr, si vous prévoyez de faire beaucoup cette recherche, assurez-vous de lui donner une association distincte comme le suggère Milan Novota, mais il n'y a pas exigence pour vous de le faire de cette façon

12
Gareth

Eh bien, plus de responsabilités sont placées dans le modèle User que nécessaire, et il n'y a aucune bonne raison de le faire.

Nous pouvons d'abord définir la portée dans le modèle EventUser parce que où elle appartient réellement, comme:

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

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

Maintenant, un utilisateur peut avoir les deux types d'événements: les événements actifs ainsi que les événements inactifs, nous pouvons donc définir la relation dans le modèle User comme suit:

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

La beauté de cette technique est que la fonctionnalité d'être un événement actif ou inactif appartient au modèle EventUser, et si à l'avenir la fonctionnalité doit être modifiée, elle ne sera modifiée qu'en un seul endroit: EventUser model, et les changements seront reflétés dans tous les autres modèles.

7
Arslan Ali