How to create an association with from two columns - sql

I have two tables, users and couples. The couples table has a pair of foreign keys lead_id and follow_id which point to the users table. I would like for the User class to have a has_many association to Couple which selects all couples in which a given user was a lead or a follow. I can do this using finder_sql and SQL like so:
has_many :couples, class_name: 'Couple', finder_sql:
proc { <<-SQL
SELECT couples.* FROM users
JOIN couples ON (couples.lead_id = users.id
OR couples.follow_id = users.id)
WHERE users.id = #{self.id}
SQL
}
This works fine, but :finder_sql is apparently deprecated, so I'm looking for another way. The best I have gotten is using a custom association scope like this:
has_many :couples, -> {
joins('JOIN couples ON (couples.lead_id = users.id OR couples.follow_id = users.id)')
.select('couples.*')
}, class_name: 'User', foreign_key: 'id'
But this causes ActiveRecord to return User objects rather than Couple objects. Is there a (non-deprecated) way to do this?
Sorry if the title is unclear, I'm open to suggestions.

Looking through the docs (http://guides.rubyonrails.org/association_basics.html) you may be able to get away with just this:
Users model:
has_many: :couples, as: lead
Has_many: :couples, as: follow
Couples model:
belongs_to :users
Also, look at the self joins section if you are attempting to relate leaders and followers.
Hope this helps.

Related

Finding objects that are not associated with another model using a join table in between

Ruby on Rails app with ActiveRecord and Postgres database. I have this relation of two models with a join table in between, basically every user can like multiple movies:
class Movie
has_many :user_movie_likes
has_many :users, through: :user_movie_likes
end
class UserMovieLike
belongs_to :movie
belongs_to :user
end
class User
has_many :user_movie_likes
has_many :movies, through: :user_movie_likes
end
Considering I have the user id, I can easily fetch all the movies that are liked by this user, something like:
# movie.rb
scope :liked, ->(user_id) { joins(:user_movie_likes).where('user_movie_likes.user_id' => user_id) }
But I can't figure out how to do the inverse of this - all the movies that the given user didn't like yet. If I do where.not('user_movie_likes.user_id' => user_id), it will still return movies that the user liked, because some other user liked them, and will not return movies that have no likes at all. For now I'm just plucking the ids from the liked scope and do where.not(id: liked_ids), but I'm sure there is a good way to do it in one query. I found some other questions similar to this one on StackOverflow, but they were different cases, i.e. finding movies that have no likes etc.
Try the below:
scope :liked, lambda { |user_id|
joins("LEFT OUTER JOIN user_movie_likes ON user_movie_likes.movie_id = movies.id AND user_movie_likes.user_id = #{user_id}")
.where('user_movie_likes.id IS NULL')
}

Query an association with an alias

I have a users table and a tasks table with a model with the following association:
class Task < ActiveRecord::Base
belongs_to :owner, class_name: User
end
I'm trying to build a query that will search the task's description and its owner's name as such:
includes(:owner).where("LOWER(tasks.description) LIKE ? OR LOWER(owner.name) LIKE ?", "%#{q.downcase}%", "%#{q.downcase}%")
...but I don't know how to properly query the users table for the task owners. In place of owner.name, I've tried users.name, tasks.users.name, and probably a few other things, all to no avail. How can I do this?
Note: I do not want to add a gem for this. I'm looking for a solution that is native to rails.
EDIT:
The foreign key as it exists in my schema.rb
add_foreign_key "tasks", "users", :name => "tasks_owner_id_fk", :column => "owner_id", :dependent => :nullify
SECOND EDIT:
I also need a solution that will return an AREL. I can get this to work by returning an array of objects, but I need to add other query methods to the result, so it has to be AREL.
You need to include a foreign_key of owner_id if you want to call owner.name in your view or query.
Second, you should be able to still call user, but since it's a belongs_to, it would be user.name, not users.name
If you set up the foreign_key you can call owner.name like you did before and it will work.
This should help you set it up:
http://guides.rubyonrails.org/active_record_migrations.html#foreign-keys
After digging around all day, this post helped me out, and my solution is the following:
joins("LEFT OUTER JOIN users ON users.id = tasks.owner_id").where("LOWER(users.name) LIKE ? OR
LOWER(tasks.description) LIKE ?",
"%#{q.downcase}%", "%#{q.downcase}%")

Where conditions on ActiveRecord associations conflicting with each other in has_many through?

In my app (Rails 4.2.0.rc2), users can be either students or admins of a given institution. There's an association :admin_institutions on User that returns all the institutions the user is an admin of by checking their role in the join table. There's also an association :students on Institution that returns all the users who are students at that institution, again according to institution_users.role.
These associations work as expected, so I added an association :admin_students to User, meant to return all the students at all the institutions for which a given user is an admin.
class InstitutionUser < ActiveRecord::Base
belongs_to :institution
belongs_to :user
end
class Institution < ActiveRecord::Base
has_many :institution_users
has_many :users, :through => :institution_users
has_many :students, -> { where "institution_users.role = 'Student'" }, :through => :institution_users, source: :user
...
end
class User < ActiveRecord::Base
has_many :institution_users
has_many :admin_institutions, -> { where "institution_users.role = 'Admin'" }, through: :institution_users, source: :institution
has_many :admin_students, through: :admin_institutions, source: :students
...
end
However, :admin_students does not work as expected. It generates the following SQL:
SELECT "users".* FROM "users" INNER JOIN "institution_users" ON "users"."id" = "institution_users"."user_id" INNER JOIN "institutions" ON "institution_users"."institution_id" = "institutions"."id" INNER JOIN "institution_users" "institution_users_admin_students_join" ON "institutions"."id" = "institution_users_admin_students_join"."institution_id" WHERE "institution_users_admin_students_join"."user_id" = $1 AND (institution_users.role='Student') AND (institution_users.role = 'Admin') [["user_id", 190]]
Instead of looking for all the institutions where the user is an admin and selecting all their students, it seems to be looking for institutions where the user is BOTH a student and an admin, so it returns an empty collection.
Is there a way to write an association (as opposed to just a method) that will give me the results I want, without my conditions conflicting like this?
(Side note: Is this the expected behavior for this kind of association? If so, I'd really appreciate further insight into why ActiveRecord interprets it the way it does.)
This may not be the answer, but maybe it will lead to one.
I'm not a fan of the associations with hard-coded SQL:
-> { where "institution_users.role = 'Student'" }
They are definitely at least part of the problem because they cannot be interpreted by ActiveRecord to determine which table alias for institution_users to apply it to.
You can allow ActiveRecord that flexibility by referencing a class method of the InsitutionUser model:
def self.students
where(role: "Student")
end
This also keeps the InstitutionUser logic all in one place.
Then the association becomes:
has_many :students, -> {merge(InstitutionUser.students)}, :through => :institution_users, source: :user
Perhaps try it with this and see if that sorts it out for you, and if not it might get things going in the right direction.

Rails Active Record Query for Double Nested Joins with a Select Call

I have the following schema:
Photos has many Groups has many Users.
I am using this Rails server as a backend to an iOS application and constantly need to push out notifications to all involved users of a group when a photo is added.
I problem is finding the least expensive query to resolve only the User.ids affected.
So far I have
Photo.find(1).groups.joins(:users)
I know that I have to put a select argument after this, but can't figure out the syntax for the life of me.
Given a photo, I am looking for the most efficient way to find a collection of the affected user id's.
Any help would be much appreciated!!!
In your Photo model, you can have another associations called users
has_many :group_users, :through => :groups, :source => :users
Then you can find the users by the following code
#photo = Photo.includes([:group_users]).where("photos.id = ?", 1).first
#affected_users = []
#photo.group_users.map {|user| #affected_users << user.id}
Now the #affected_users contains all the user ids.
users_id = []
Group.where(photo_id: 1).users.collect{|u| users_id << u.id}
puts users_id
As Photo has_many groups, so, groups belongs_to photo and groups table has photo_id as foreign key .
So, please try this.

Problems using an id from a model inside a custom sql query in Rails

I want to do a model class which associates to itself on Rails. Basically, a user has friends, which are also users. I typed the following inside a User model class:
has_many :friends,
:class_name => "User",
:foreign_key => :user_id,
:finder_sql => %{SELECT users.*
FROM
users INNER JOIN friends
ON (users.id = friends.user_id OR users.id = friends.friend_id)
WHERE users.id <> #{id}}
But the funny fact is that it seems that this finder_sql is called twice whenever I type User.first.friends on irb. Why?
Drop the :finder_sql and refer to this:
http://guides.rubyonrails.org/association_basics.html#self-joins
I just read this post:
http://railsblaster.wordpress.com/2007/08/27/has_many-finder_sql/
I should have used single quotes to embrace the finder_sql, instead of %{}