I’m converting an app from php to rails, and I’m still learning my way around rails and AR.
Simply: I want to list the groups that the current user is not already a member of.
Failed Approach:
-
Cause.select('causes.*').joins(:users).group('causes.id').where("cause_user_memberships.user_id
NOT IN (?)", current_user.id) -
Cause.select('causes.*').joins(:users).group('causes.id').where("cause_user_memberships.user_id NOT IN (SELECT cause_user_memberships.cause_id FROM cause_user_memberships WHERE cause_user_memberships.user_id =(?))", current_user.id) - Many more…
Thanks for your help!
Some info about models
User.rb (snippet)
has_many :cause_user_memberships
has_many :causes, :through => :cause_user_memberships
Cause.rb
attr_accessible :title, :location, :description,...
has_many :cause_user_memberships
has_many :users, :through => :cause_user_memberships
Cause_User_Membership.rb (<–probably not my best model name)
# == Schema Information
#
# Table name: cause_user_memberships
#
# id :integer not null, primary key
# user_id :integer not null
# cause_id :integer not null
# created_at :datetime not null
# updated_at :datetime not null
#
class CauseUserMembership < ActiveRecord::Base
attr_accessible :cause_id, :user_id
belongs_to :user
belongs_to :cause, :counter_cache => :users_count
accepts_nested_attributes_for :cause
validates_uniqueness_of :user_id, :scope =>[:cause_id]
end
Update: Follow up
Derp, you’re right it worked! Thanks!
Small follow up, the query times seem pretty long. Does this indicate a problem? I have less than 20 records in each table. (Below are 2 query results, one that includes the geocoder gem I intend to use and another that doesn’t. Sorry if it’s a little messy.)
In Rails Console:
Cause Load (1003.0ms) SELECT "causes".* FROM "causes" LEFT JOIN cause_user_memberships ON cause_user_memberships.cause_id = causes.id AND cause_user_memberships.user_id = 1 WHERE (cause_user_memberships.id IS NULL)
EXPLAIN (34.3ms) EXPLAIN SELECT "causes".* FROM "causes" LEFT JOIN cause_user_memberships ON cause_user_memberships.cause_id = causes.id AND cause_user_memberships.user_id = 1 WHERE (cause_user_memberships.id IS NULL)
QUERY PLAN
Hash Right Join (cost=10.45..37.99 rows=1 width=3168)
Hash Cond: (cause_user_memberships.cause_id = causes.id)
Filter: (cause_user_memberships.id IS NULL)
-> Seq Scan on cause_user_memberships (cost=0.00..27.50 rows=7 width=8)
Filter: (user_id = 1)
-> Hash (cost=10.20..10.20 rows=20 width=3168)
-> Seq Scan on causes (cost=0.00..10.20 rows=20 width=3168)
(7 rows)
With Geocoder on local host:
User Load (18.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Cause Load (49.6ms) SELECT causes.*, 3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((40.714269 - causes.lat) * PI() / 180 / 2), 2) + COS(40.714269 * PI() / 180) * COS(causes.lat * PI() / 180) * POWER(SIN((-74.005972 - causes.lng) * PI() / 180 / 2), 2))) AS distance, CAST(DEGREES(ATAN2( RADIANS(causes.lng - -74.005972), RADIANS(causes.lat - 40.714269))) + 360 AS decimal) % 360 AS bearing FROM "causes" LEFT JOIN cause_user_memberships
ON cause_user_memberships.cause_id = causes.id
AND cause_user_memberships.user_id = 1 WHERE (causes.lat BETWEEN 36.37231550667456 AND 45.05622249332544 AND causes.lng BETWEEN -79.73435509229111 AND -68.27758890770889 AND 3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((40.714269 - causes.lat) * PI() / 180 / 2), 2) + COS(40.714269 * PI() / 180) * COS(causes.lat * PI() / 180) * POWER(SIN((-74.005972 - causes.lng) * PI() / 180 / 2), 2))) <= 300) AND (cause_user_memberships.id IS NULL) ORDER BY distance ASC
Completed 200 OK in 1068ms (Views: 49.1ms | ActiveRecord: 791.4ms)
In rails the
joins(:symbol)statement turns into an inner join which is not suited for finding relations that don’t exist. You can write the join by hand in order to make it a left join:UPDATED
The inner join will prevent the creation of a set of Causes that don’t have a mapping to the given user. For example:
Will return
Which you can’t perform any more logic on in order to tease out the causes the user doesn’t belong to.
Will return
In this case all the Causes will get a row whether they match a record in the
cause_user_membershipstable or not. Now you can apply additional conditions to tease out the causes the user doesn’t belong to (where cause_user_membership.id is null).