How to create a scope based on user.photos.count - ruby-on-rails

I have a User model and a Photo model. If I go into Heroku Console, I can search the database, for example:
u = User.find(2)
u.photos.count
=> 25
I want to create a scope in my User model so that I can sort the users based on their photos.count number and then paginate the users.
Class User
scope :photocount ??????
UsersController
def index
#users = User.photocount.paginate(page: params[:page])
end

What you want is counter_cache. Add a column photos_count to user model and in Photo model:
belongs_to :user, counter_cache:true
Then you can User.order(:photos_count).page(params[:page])
See more about counter cache in rails guides here
Scope will look like:
scope :by_photos_count, ->{ order(:photos_count) }
Without counter cache it is still possible, but will be very inefficient:
User.joins('join (select user_id, count(*) as sort from photos group by user_id) as sort ON users.id=sort.user_id').order('sort.sort').page(params[:page])

Related

Which rails model do I put a multiple table query in

I have two tables called
Product (prodID: integer, prodName: string, userID: FK)
and
User(userID:integer,userName:string).
The user can have many products. I want to write a query that gets me all the products for userID=10. I don't however understand which model I should put this in- the user or the product model or does it not matter? Presumably the output of the model will be fed to the controller it relies on so I should put it in the model that relates to the view I want to show it in? Is this correct?
You can directly use association method, no need of writing model method for fetching user's products.
In user.rb:
has_many :products
In product.rb
belongs_to :user
and from controller
User.where('id = ?', params[:id]).first.try(:products)
So, above query will fetch products if user with given id is found.
In your controller:
#user = User.find(params[:id])
#products = User.of_products(params[:id])
If you don't want to use #user in your action then you can avoid calculating #user.
In user.rb:
has_many :products
def self.of_products(user_id)
User.includes(:products).where(id: user_id)
end
This will give you all products of #user

Find user who has no post in Rails

This the the database relation:
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
end
I come across a functionality where I want to query all the users who don't have any posts yet. I know that we can do this with something like this:
users = User.all
users.each do |user|
unless user.posts.any?
# do something when user don't have any post.
end
end
However, I wonder if there is any way to optimize this by using one query only.
Thanks!
This results in a single query which fetches all users who don't have posts yet:
User.includes(:posts).references(:posts).where('posts.id IS NULL')
Another solution is this:
User.where('NOT EXISTS(SELECT 1 FROM posts WHERE user_id = users.id)')
Since this is a rather complex query to use everywhere, you can place this inside a named scope in User:
class User < ActiveRecord::Base
scope :without_posts, -> { where('NOT EXISTS(SELECT 1 FROM posts WHERE user_id = users.id)') }
end
Now you can use this scope elsewhere in your application:
User.without_posts
I'd try something like
User.joins(posts).where("count(posts.id) = 0")
Which returns all users that have 0 posts.
with rails 6.1, even simpler:
User.where.missing(:posts)

rails 3 associations trying to limit return data

I fully admit this is user inexperience but here is my problem
I have 2 models that relate by
class User < ActiveRecord::Base
belongs_to :team
class Team < ActiveRecord::Base
has_many :users
What I want to do is display all users that belong to the same team as the logged in user. so basically select from user there team_id = my team_id
I was not sure if I could / should do this in the controller or the view but I could not get either to work.
in my controller I have this which returns all users for all teams
#users = User
which is SELECT users.* FROM users
I can also use my current_user method which returns info on my current user
#users = current_user.team
which is SELECT teams.* FROM teams WHERE teams.id = 3
I dont know how to get a list of all users where team_id = current_user team_id?
Also would like to know if its best to try this in the controller or out in the view?
Thanks
As suggested by #apneadiving using curent_user.team.users would return the list of users. But I just wanted to advise you to wrap that behavior in a method.
So for instance you could have in your User model something like :
class User < ActiveRecord::Base
belongs_to :team
def coworkers
team.users
end
end
And then in your controller you could do
def an_action
#coworkers = current_user.coworkers
end
Finally in your view you could loop through this list to display them
#coworkers.each do |coworker|
...

Getting an array for user ids from an array of users

I am using the amistad gem to handle friend relationships. Users have events associated with them. I would like to provide a feed of events for the a given user based on who they are friends with.
I have used the following code from http://ruby.railstutorial.org for follow relationships. However with amistad i don't have a user.friend_ids method only a user.friends method.
How can I get a similar feed type of result (that can be paged and all that) with the user.friends call that gives me a list of user objects and not just the ids?
class Micropost < ActiveRecord::Base
default_scope :order => 'microposts.created_at DESC'
# Return microposts from the users being followed by the given user.
scope :from_users_followed_by, lambda { |user| followed_by(user) }
private
# Return an SQL condition for users followed by the given user.
# We include the user's own id as well.
def self.followed_by(user)
following_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
where("user_id IN (#{following_ids}) OR user_id = :user_id",
{ :user_id => user })
end
end
This is mostly pseudocode as it doesn't work, but here's what I think I'm trying to accomplish in code:
class Event< ActiveRecord::Base
default_scope :order => 'event.created_at DESC'
# Return events from friends of a user.
scope :from_friends, lambda { |user| friends_of(user) }
private
# Return an SQL condition for users followed by the given user.
# We include the user's own id as well.
def self.friends_of(user)
friend_ids = %(SELECT friendIDs FROM friendships)
where("user_id IN (#{friend_ids})")
end
end
You can manually add friend_ids method to the User model.
def friend_ids
self.friends.map(&:id)
//Here, I'm iterating over user.friends array and getting an array of ids
end
EDIT: As per your comment, I'm assuming you have the associations between user and events built up properly.
e.g A user has many events and an event belongs to a user/multiple users (depending on your requirements.)
Once you have the associations setup correctly, you can simply lookout for events with the user_ids which you got from above friend_ids method.

Combining scoped activerecord queries in rails

I'm trying to build a facebook style feed of items for a user. The feed will contain recent notes (on books) made by a user or people the user follows combined with other notifications such as "user x that you follow started reading a new book". You get the idea.
So far I have a scope in my Note class which returns the notes I want:
class Note < ActiveRecord::Base
scope :from_users_followed_by, lambda { |user| followed_by user }
def self.followed_by(user)
followed_ids = %(SELECT followed_id FROM relationships WHERE follower_id = :user_id)
where("user_id IN (#{followed_ids}) OR user_id = :user_id", { :user_id => user })
end
end
and a similar scope in my Readings class which returns records built when user starts reading a book:
class Reading < ActiveRecord::Base
scope :from_users_followed_by, lambda { |user| followed_by(user) }
def self.followed_by(user)
# is this not at risk of sql injection??
followed_ids = %(SELECT followed_id FROM relationships WHERE follower_id = :user_id)
# return readings where user_id IN (an array of user_ids that the user follows)
where("reader_id IN (#{followed_ids}) OR reader_id = :user_id", { :user_id => user })
end
end
Now this works fine and I can get arrays of objects from these no problem. I'm struggling to combine the two queries into a feed which is correctly ordered by creation time. The best I can do at the moment is my user class with a combined feed method:
class User < ActiveRecord::Base
def combined_feed
feed = Note.from_users_followed_by(self) | Reading.from_users_followed_by(self)
feed.sort! do |a, b|
a.created_at <=> b.created_at
end
feed.reverse
end
end
Which gets me a combined feed but strikes me as being horrendously inefficient. How can I do the equivalent at the database level in rails?
I think I would probably create an entirely separate model called FeedItem. Then, when certain events occur (such as the creation of a new note), you just create a new FeedItem record. Then you only have one table to query from and it will already be in the correct order.

Resources