How to create a scope for current_user.following? - ruby-on-rails

Here are my User and Relationships models
class User < ActiveRecord::Base
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :followers, through: passive_relationships, source: :follower
has_many :following, through: :active_relationships, source: :followed
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User", counter_cache: :followeds_count
belongs_to :followed, class_name: "User", counter_cache: :followers_count
validates :follower_id, presence: true
validates :followed_id, presence: true
validates :followed, uniqueness: { scope: [:follower, :followed] }
end
In Users Controller I can do:
#users = current_user.following
However I would like to turn this into a scope in my User model.

There are 2 things you may approach:
Find all users who are following someone
class User < ActiveRecord::Base
scope :following_to, -> (user_id) {
where(
"id IN ( SELECT followed_id
FROM relationships
WHERE follower_id = ?
)",
user_id
)
}
end
Find all users who are following anyone, that means they are a follower
class User < ActiveRecord::Base
scope :follower, -> {
where("id IN ( SELECT followed_id FROM relationships)")
}
end
Finally, you can use these scope as your expectation:
# Find all users who are following to User (id = 1)
User.following_to(1)
# Find all users who are following someone,
# aka they are a follower
User.follower

By using the Instance Method you can make a method For User Model
like this :
class User < ActiveRecord::Base
def following?
self.following.present?
end
end
By Using Scope you can call only the activerecord based query into the scope of model.

You should get also this way
scope :following?, lambda { |user|
{ user.following.present? }
And this should be call like in your controller
User.following?(current_user)

Related

Rails - loading related data on other side of many-to-many relationship

I have Beta::Groups and Beta::Users.
Users can belong to more than one Group, but each Beta::User is also a regular User.
class Beta::User < ApplicationRecord
belongs_to :beta_group, class_name: "Beta::Group"
belongs_to :user
validates :user_id, uniqueness: { scope: :beta_group_id }
end
class Beta::Group < ApplicationRecord
has_many :beta_users, dependent: :destroy, class_name: "Beta::User", foreign_key: 'beta_group_id'
validates :name, presence: true, uniqueness: true
def beta_users_count
beta_users.count
end
end
Effectively, Beta::User is just a join table between Beta:Group and User
When I load a Beta::Group, how can I retrieve the data for all the user's in that group?
#beta_group = Beta::Group.find(beta_user_params[:beta_group_id])
#beta_users = #beta_group.beta_users.all
The last line only retrieves the beta_users data obviously (just the beta_group_id and user_id). I don't want to have to then iterate through all the user_id's to get the actual User's full data.
So how I can set this up so that I can do something like #beta_group.users.all to retrieve all the data for each user that is a Beta:User?
EDIT - What I have tried
I tried adding this to the Beta::Group model:
has_many :users, through: :beta_users, source: :user
But when I call beta_group.users the query that runs is as follows, returning an empty array:
SELECT "beta_users".* FROM "beta_users" INNER JOIN "beta_users" "beta_users_users" ON "beta_users"."id" = "beta_users_users"."user_id" WHERE "beta_users_users"."beta_group_id" = $1 [["beta_group_id", 1]]
Notice that it is not joining correctly. It should be trying to join on "beta_users"."user_id" to "users.id"
I believe you can add a has_many through association:
class Beta::Group < ApplicationRecord
has_many :beta_users, dependent: :destroy, class_name: "Beta::User", foreign_key: 'beta_group_id'
# here:
has_many :users, through: :beta_users, source: :user
validates :name, presence: true, uniqueness: true
def beta_users_count
beta_users.count
end
end
Then you should be able to call the association:
#beta_group = Beta::Group.find(beta_user_params[:beta_group_id])
#beta_users = #beta_group.users
EDIT - I believe you have an issue with scopes and the class_name because you have a User inside the Beta module.
Try adding the class_name: "::User" to both associations:
class Beta::User < ApplicationRecord
belongs_to :beta_group, class_name: "Beta::Group"
belongs_to :user, class_name: "::User" # without class_name it'll try a self association
end
class Beta::Group < ApplicationRecord
has_many :beta_users, dependent: :destroy, class_name: "Beta::User", foreign_key: 'beta_group_id'
has_many :users, class_name: "::User", through: :beta_users # without a class_name it'll try to join the Beta::User
end
The problem was due to the fact that User is a table in both the top-level namespace and inside the Beta namespace.
Therefore, it is necessary to inform rails which table to use to create the SQL for, by specifying the top-level class in the relation, like this:
class_name: '::User'
Final code:
class Beta::Group < ApplicationRecord
has_many :beta_users, dependent: :destroy, class_name: 'Beta::User', foreign_key: 'beta_group_id'
has_many :users, through: :beta_users, source: :user, class_name: '::User'
validates :name, presence: true, uniqueness: true
def beta_users_count
beta_users.count
end
end
which generates the correct SQL:
SELECT "users".* FROM "users" INNER JOIN "beta_users" ON "users"."id" = "beta_users"."user_id" WHERE "users"."viewer" = $1 AND "beta_users"."beta_group_id" = $2 [["viewer", false], ["beta_group_id", 1]]

Finding all Admins using Rails Associations

How do I find all admins using Rails 6 active record associations?
I have the following classes:
class Group < ApplicationRecord
has_many :relationships, dependent: :destroy
has_many :users, through: :relationships
end
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :relationships, dependent: :destroy
has_many :groups, through: :relationships
end
class Relationship < ApplicationRecord
belongs_to :user
belongs_to :group
validates :user_id, presence: true
validates :group_id, presence: true
validates :admin, inclusion: [true, false]
I'd like to add admins as a has_many relationship in Group.
Here's my first attempt at doing so:
has_many :admins, class_name: "User", through: :relationships
How would I filter those users whose relationship to the group has the admin attribute set to true?
Associations can be scoped using the normal query syntax.
class Group < ApplicationRecord
...
has_many :admins,
-> { where({ relationships: { admin: true} }) },
through: :relationships,
source: :user
end
source is necessary because otherwise it will try to do the has_many through Relationships.admins.
group.admins is equivalent to group.users.where({ relationships: { admin: true} }).
You could've modified your Group model to something like:
class Group < ApplicationRecord
scope :admins, -> { where(relationships: { admin: true }) }
has_many :relationships, dependent: :destroy
has_many :users, through: :relationships
end
From it you can use Group.joins(:users).admins to get groups with assoiciated admins.

How to associate models?

I am building a ROR app with Users and Groups and I want to be able to link them with an associative model called Memberships.
My problem is that when I try to create a group with one member, the members array for the new group is always empty.
I tried creating a group like this:
def create (user)
#group = Group.new(create_group_params)
user.join(#group)
user.save
#group.save
end
but #group.members is empty when I print it out. How can I make the user a member of the group?
Here are my models:
Group.rb
class Group < ActiveRecord::Base
has_many :passive_memberships, class_name: "Membership",
foreign_key: "club_id",
dependent: :destroy
has_many :members, through: :passive_memberships, source: :member
end
User.rb
class User < ActiveRecord::Base
has_many :active_memberships, class_name: "Membership",
foreign_key: "member_id",
dependent: :destroy
has_many :memberships, through: :active_memberships, source: :club
def join(group)
active_memberships.create(club_id: group.id)
end
end
Membership.rb
class Membership < ActiveRecord::Base
belongs_to :member, class_name: "User"
belongs_to :club, class_name: "Group"
validates :member_id, presence: true
validates :club_id, presence: true
end
Oh. I have to save the group before the user can join it.
def create (user)
#group = Group.new(create_group_params)
#group.save
user.join(#group)
user.save
end

Rails has_many :through with the where clause

I've built easy Twitter application in Rails.
Now I would like to choose three random users that are not followed by the current user.
Here is my model:
class User < ActiveRecord::Base
has_many :tweets, dependent: :destroy
has_many :followerships, class_name: 'Followership', foreign_key: 'followed_id'
has_many :followedships, class_name: 'Followership', foreign_key: 'follower_id'
has_many :followers, through: :followerships, source: :follower
has_many :followed, through: :followedships, source: :followed
end
class Followership < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
end
class Tweet < ActiveRecord::Base
belongs_to :user
end
I tried to use the following query:
User.where.not(followers: current_user).order("RANDOM()").limit(3)
But it obviously doesn't work as I get no such column: users.follower_id error.
Is it even possible to do without sql query?
Thank you!
Try this:
already_following = current_user.followed.map(&:id)
#users = User.where.not(id: already_following).order("RANDOM()").limit(3)
Basically what I did, was got the list of users already being followed. Then you check the User table for id's not matching users already being followed.

Adding counter cache to self join table in rails

I am trying to add a counter cache on a a column in a self join association.
I have two models User and followings. User has followers and followees, who are from the user table itself.
User.rb
has_many :followings
has_many :followers, :through => :followings
has_many :followees, :through => :followings
Following.rb
class Following < ActiveRecord::Base
attr_accessible :followee_id, :follower_id
belongs_to :follower, :class_name => "User"
belongs_to :followee, :class_name => "User"
end
now i want to add counter cache on follower and followees. I have followers_count and followees_count columns in user table.
I tried
belongs_to :follower, :class_name => "User" , :counter_cache => true
But this doesn't return any data in the user table.
Any help would be appreciated.
It was long-long time ago, but
User.rb
class User < ActiveRecord::Base
has_many :followings_as_follower, class_name: 'Following', foreign_key: 'follower_id', dependent: :destroy
has_many :followings_as_followee, class_name: 'Following', foreign_key: 'followee_id', dependent: :destroy
has_many :followers, through: :followings_as_followee, source: :follower
has_many :followees, through: :followings_as_follower, source: :followee
def follow?(user)
followees.reload.include? user
end
def follow(user)
return if follow?(user)
followings_as_follower.create(followee: user)
end
def unfollow(user)
return unless follow?(user)
followings_as_follower.where(followee: user).first.destroy
end
end
Following.rb
class Following < ActiveRecord::Base
belongs_to :follower, class_name: 'User', counter_cache: :followees_count
belongs_to :followee, class_name: 'User', counter_cache: :followers_count
validates :follower, presence: true
validates :followee, presence: true
validates :followee, uniqueness: { scope: [:follower, :followee] }
end
Try this,
belongs_to :follower, foreign_key: 'the_id_of_foreign_key', class_name: 'User', counter_cache: :followers_count
You can use the column_name instead of true in counter_cache.

Resources