Rails 4 order by count of has_many through - sql

In my case, users are related to other users through friendships. I want to order my users by the number of friends/friendships they have.
class User < ActiveRecord::Base
has_many :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
end
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User"
end
NB In my case I am using postgres
I am currently using
a = User.all.collect {|x| [x.id, x.friends.length]};
a = a.sort {|x,y| y[-1] <=> x[-1]}
Where x.friends is a method which returns all friends via Friendship.where('friend_id = ? OR user_id = ?', self, self). I believe this is suboptimal as it makes a request per user.

I am not sure that my request will work properly, because I can't test on on a real DB now. But you should do it like this:
User.select(users.*, SUM(friendships.id)).joins("INNER JOIN users as friendships ON users.id = friendships.friend_id").group(users.id).order("SUM(friendships.id)")
User.select(users.*, SUM(inverse_friendships.id)).joins("INNER JOIN users as inverse_friendships ON users.friend_id = inverse_friendships.id").group(users.id).order("SUM(inverse_friendships.id)")
It will work only with PostgreSQL 9.1 and higher.

Related

Adding INNER JOIN with has_many association in rails 3.1

I have association in rails 3.1
has_many :likes, :dependent => :destroy
I wanted to add a join with a condition in the above association
likes = user.likes.joins('INNER JOIN posts ON posts.id = likes.likable_id and likes.likable_type = "Post"').where("posts.is_published", true)
So instead of using user.likes.joins(). I only wanted to use user.likes. Scope is also one option but is it possible to add that in association.
Try something like this:
class User < ActiveRecord::Base
has_many :likes, conditions: {"likes.likable_type = 'Post'", "posts.is_published IS true"}, dependent: :destroy
end

What is the most efficent way generate the following has_many through query

I have following models:
Account
class Account < ActiveRecord::Base
has_many :members
has_many :users, through: :members
end
Member
class Member < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
User
class User < ActiveRecord::Base
has_many :accounts, through: :members
end
Query:
Given a User.id, find the account. currently I do this:
> u = User.take
User Load (1.5ms) SELECT "users".* FROM "users" LIMIT 1
> id = Member.where(user_id: u.id).pluck(:account_id)
(0.8ms) SELECT "members"."account_id" FROM "members" WHERE "members"."user_id" = 1
> a = Account.find(id)
Account Load (8.9ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
Wondering if there is a better or faster way to find Account, given a user?
I don't know the details of your models, but based on the error, it looks like you need the members association in your User model:
class User < ActiveRecord::Base
has_many :members
has_many :accounts, through: :members
end
Why not do
u = User.take
a = u.accounts.find(id)

Rails calling User record from Friends model

I've built a simple Friend model, which allows Users to have multiple friends. Here's what that looks like:
class Friend < ActiveRecord::Base
belongs_to :user
class User < ActiveRecord::Base
has_many :friends
Each friend record just has an id, user_id and friend_id. The user_id is the id of the user it belongs to and the friend_id is the id of user they are befriending.
Here's my problem
I'm not quite sure how to display a list of a particular user's friends. #user.friends will give me a list of all the friend records they have, but not the user accounts of those friends.
For instance, I am trying to build a show page for the friends controller:
class FriendsController < ApplicationController
def show
#user = current_user
end
SHOW.HTML.ERB
<% if #user.friends.count > 0 %>
<% #user.friends.each do |friend| %>
<div class="entry">
<%= friend.username %>
This does not work because friend in this case does not have username. I need to do something like this in my controller:
#friend = User.find_by_id(friend.friend_id)
But I'm not sure how I would call that in my view in the #user.friends loop. Any thoughts appreciated. Let me know if I need to be more clear.
UPDATE
I've updated my User model like so:
has_many :friends, :include => :user
has_many :friended_users, :through => :friends, :source => :user, :uniq => true
However, when I run #user.friended_users it's giving me the user_ids (which is the same as #user) rather than friend_ids.
How can I tweak that relationship so it's linking to the friend_id rather than user_id?
The more I think about it, I think I may not have set up the relationship properly in the first place. Maybe a User should has_many :users, through => 'friends', but that doesn't really make sense...
UPDATE
I've updated my models based on #twooface's input:
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, :through => :friendships
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => 'User'
class Friend < ActiveRecord::Base
has_many :friendships
has_many :users
I'm just not sure what my Friends table should look like. I assume it should have a primary key and a user_id? If I create a friendship and friend record, I can do friendship.user and friendship.friend and get the correct results, but user.friends gives me an empty hash...
I think your relations are built a bit wrong. Try something like this:
class User < ActiveRecord::Base
has_many :friends
has_many :friendships
has_many :friends, :through => :friendships
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => 'User'
# This class has :user_id and :friend_id
Then everything will be simpler. Every user will have an array of friends that will be just users.
User.first.friends
Will return an array of Users that this User friends.
Hope this helps.

rails complex many-to-many query

I've got 3 models: User, Team, and Membership -
class Team < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships, :source => :user
end
class User < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :teams, :through => :memberships
def team_mates
teams = Team.where(:members => id)
team_mates = teams.members
end
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :team
validates :user_id, :presence => true
validates :team_id, :presence => true
end
And, I can't quite figure out how to write the team_mates method in the User model. It should return an array of the other users that are in a team with the current_user. My thought is that I should be useing a scope to limit Team to only include teams where the current user is a member but I can't quite figure out the syntax. Any help on this would be greatly appreciated.
Thanks!
Use the membership table to find users who share any team with the user you are calling the method on. Then to filter out duplicate users who share more than 1 team with current user, use distinct.
I haven't tested the below code, but hopefully it will get you on the right path:
def team_mates
m = Membership.scoped.table
Users.join(m).where(m[:team_id].in(team_ids)).project('distinct users.*')
end
UPDATE
Looks like some of the Arel methods in that answer don't have an easy mapping back to ActiveRecord land. (If someone knows how, I'd love to know!) And also it returns the 'current user'. Try this instead:
def team_mates
User.joins(:memberships).where('memberships.team_id' => team_ids).where(['users.id != ?', self.id]).select('distinct users.*')
end
How about?
User.joins(:memberships).joins(:teams).where("teams.id" => id).uniq
Maybe something like this
scope team_mates, lambda {|user_id, teams| joins(:memberships).where(:team_id => teams, :user_id => user_id)}
def team_mates
User.team_mates(self.id, self.teams.collect {|t| t.id})
end
Here is more info:
http://api.rubyonrails.org/classes/ActiveRecord/NamedScope/ClassMethods.html

I need help with a Rails ActiveRecord Join - I know how to do it in SQL

I have 2 tables I need to join but the user_id column value is not the same in both tables. So I want to do something like this:
In my controller 4 will be substituted with current_user.id
select * from sites join pickups on sites.id = pickups.site_id where sites.user_id = '4'
But using an ActiveRecord Find.
Here are my associations:
class Site < ActiveRecord::Base
belongs_to :user
has_many :pickups
class Pickup < ActiveRecord::Base
belongs_to :site
belongs_to :user
class User < ActiveRecord::Base
has_one :profile
has_many :pickups
has_many :sites
Thanks in advance!
If you add this to your user model:
has_many :site_pickups, :through => :sites, :source => :pickups
You can do
current_user.site_pickups
try this:
sites = current_user.sites.find(:all, :include => [:pickup])