class Users < ActiveRecord::Base
has_many :meetings, :through => :meeting_participations
has_many :meeting_participations
end
class Meetings < ActiveRecord::Base
has_many :users, :through => :meeting_participations
has_many :meeting_participations
end
class MeetingParticipations < ActiveRecord::Base
belongs_to :user
belongs_to :meeting
scope :hidden, where(:hidden => true)
scope :visible, where(:hidden => false)
end
hidden
is an extra boolean column within the m2m association table. Given some Users
instance current_user
, I want to do
current_user.meetings.visible
which will retrieve a collection of Meetings
for which the user is a participant where the hidden
column is false
. The closest I have gotten is adding the following scope to the Meetings
class
scope :visible, joins(:meeting_participations) & MeetingParticipation.visible
The scope
does filter the Meetings
against the MeetingParticipations
table, however there is no join/condition against the MeetingParticipations
table related to current_user
.
The issue with this is, if current_user
and another_user
are both participants for some Meetings
instance, a Meetings
record in the result set will be returned for each participant that has hidden
set to false
. If current_user
has true
set for hidden
for all Meetings
, if another_user
is a participant in any of those same Meetings with hidden
set to false
, those Meetings
will appear in the Meetings.visible
result set.
Is it possible to have a scope as I've mentioned above which will properly join on the User
instance? If not, can someone recommend a solution to this?
This is my solution for your problem:
class User < ActiveRecord::Base
has_many :meeting_participations
has_many :meetings, :through => :meeting_participations do
def visible
where("meeting_participations.visible = ?", true)
end
end
end
@user.meetings.visible
In Rails 4, you can specify the scope originally defined in the child object in the association itself. Short: you don't have to know the internals of the MeetingParticipation model within the User model.
class User < ActiveRecord::Base
has_many :meeting_participations
has_many :meetings, :through => :meeting_participations
has_many :visible_participations, -> { visible }, :class_name => 'MeetingParticipation'
has_many :visible_meetings, :source => :meeting, :through => :visible_participations
end
class Meeting < ActiveRecord::Base
has_many :meeting_participations
has_many :users, :through => :meeting_participations
end
class MeetingParticipation < ActiveRecord::Base
belongs_to :user
belongs_to :meeting
scope :hidden, -> { where(:hidden => true) }
scope :visible, -> { where(:hidden => false) }
end
This would allow you to do: user1.visible_meetings
and user2.visible_meetings
with different result sets
current_user.meetings.merge(MeetingParticipations.visible)
Here's a one liner:
Meeting.joins(:meeting_participations).where(meeting_participation: { hidden: false, user_id: current_user.id })
This is great because you can make a scope out of it, a function out of it, or simply call it anywhere. You can also add any more restrictions you want to the hash.
It would seem to me that it is not sensible to use a scope on Meeting for your purpose. A meeting itself has no visibility, but the participation has. So I would suggest an extension on the association within User:
class User < ActiveRecord::Base
has_many :meetings, :through => :meeting_participations do
def visible
ids = MeetingParticipation.
select(:meeting_id).
where(:user_id => proxy_owner.id, :visible => true).
map{|p| p.meeting_id}
proxy_target.where("id IN (?)", ids)
end
end
...
end
I hope, this helps.
You could also do:
current_user.meeting_participations.visible.map(&:meeting)