I'll write out the code so the setup is clear.
class Invitation < ApplicationRecord
belongs_to :event
belongs_to :profile
end
class Event < ApplicationRecord
has_many :invitations
end
class Profile < ApplicationRecord
has_many :invitations
belongs_to :user
end
class User < ApplicationRecord
has_many :profiles
end
If you want to know an Event's users, use has_many :through
to access an association through another one. First we set up Profile and Events to have each other through their Invitations.
class Event < ApplicationRecord
has_many :invitations
has_many :profiles, through: :invitations
end
class Profile < ApplicationRecord
belongs_to :user
has_many :invitations
has_many :events, through: :invitations
end
And now we can set up User to have Invitations through Profiles and Events through Invitations.
class User < ApplicationRecord
has_many :profiles
has_many :invitations, through: :profiles
has_many :events, through: :invitations
end
And we do the same for Event to find its Users through Profiles.
class Event < ApplicationRecord
has_many :invitations
has_many :profiles, through: :invitations
has_many :users, through: :profiles
end
Now events can find their users and users can find their events.
users = event.users
events = users.events
Rails now knows how to join from events to users making your task, and many others, much easier.
What you want to avoid is having to load every Event into memory. You also want to avoid an N+1 query where you query the list of Events, and then query if each Event matches your user. That will get very slow. We want to get all events and check if they've been applied in a single query.
First, we'll add a fake column attribute to the Event called applied. This lets us store some extra information from a query.
class Event < ApplicationRecord
has_many :invitations
has_many :profiles, through: :invitations
has_many :users, through: :profiles
attribute :applied, :boolean
end
And then write a query which populates it. We can take advantage of the Event -> User relationship we just established to join Event directly with User.
# We can't use bind parameters in select, quoting will have to do.
user_id = ActiveRecord::Base.connection.quote(user_id)
@events = Event
# A left outer join ensures we get all events, even ones without users.
.left_outer_joins(:users)
.select(
# Grab all of event's columns plus...
"events.*",
# Populate applied.
"(users.id = #{user.id}) as applied"
)
And now you can efficiently iterate through the events in a single query and without having to load them all into memory.
@events.find_each { |event|
do_something if event.applied
}
The finishing touch is to package this up as a scope for ease of use.
class Event < ApplicationRecord
has_many :invitations
has_many :profiles, through: :invitations
has_many :users, through: :profiles
attribute :applied, :boolean
scope :applied_to_user, ->(user_id) {
# We can't use bind parameters in select, quoting will have to do.
user_id = ActiveRecord::Base.connection.quote(user_id)
left_outer_joins(:users)
.select(
# Grab all of event's columns plus...
"events.*",
# Populate applied.
"(users.id = #{user.id}) as applied"
)
}
end
Event.applied_to_user(user_id).find_each { |event|
...
}