Find user who has no post in Rails - ruby-on-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)

Related

Retrieve all 'has_many' related objects filtered by one instance in ruby

I have two ActiveRecords Users and Posts:
class User < ActiveRecord::Base
has_many :post
end
class Post < MyBase
belongs_to :user
end
I'm trying to get list of all users and all their posts.
However when running the following code, I get the users who has posts with id 1 or 2, but only these posts. I want to get these users with all their posts.
Any ideas how to do that?
users = User.sorted.references([:posts]).includes([:posts]).where('posts.id=1' OR posts.id=2).all
This is one way to do it, first get the user ids of posts with id 1 or 2, and then get all those users. There are other ways of course, but this at least should work for you.
user_ids = Post.where('id = 1 OR id = 2').pluck(:user_id)
users = User.where(id: user_ids)

Rails: Order a model by the last element of a has_many association

I have two models: User and Message that are connected by a has_many relationship. I want to get a list of users sorted by the timestamp on their last message.
class User < ActiveRecord::Base
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
When I do this:
#users = User.includes(:messages).order('messages.created_at DESC').limit(5)
It seems to order the messages, grab the newest 5 messages, and then return the users associated with those. So the number of users can be less than 5. I want to make sure I get the 5 users.
I want the query to get the newest message for each request, order the last messages, and then return the users with the newest messages. So I want something like:
#users = User.includes(:messages).order( <messages.last.created_at DESC> )
Ideally, the solution would do the sorting on the database, because I want to use paginate. If it's relevant I'm using Postgres.
I would probably be preferential to the solution mentioned by phoet of adding an attribute to User such as last_message_posted_at and using touch: true to update that on message creation. That simplifies the query that has to be performed when you need to pull your list of users. This also allows a much more readable (IMO) chain for your application:
#users = User.all.order(:last_message_posted_at)
=> "SELECT \"users\".* FROM \"users\" ORDER BY \"users\".\"last_message_posted_at\" ASC"
This also allows you to add a nice and simple scope to your User model
scope: :by_recent_message, ->{ order(:last_message_posted_at) }
User.by_recent_message.limit(5)
It also depends when and how often this #users scope is being used. Adding a few ms to the message post time is preferable, to me, than a complicated SQL query each time the list of users is pulled.
-- Edit for those who aren't familiar with the touch syntax --
Documentation for touch: http://apidock.com/rails/v4.2.1/ActiveRecord/Persistence/touch
class User < ActiveRecord::Base
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user, touch: true
end
And then make my query (to the user with most recent last message):
#user = User.includes(:messages).order(updated_at: :desc )
You can try something along the lines of
Message.group(:user_id).joins(:users).order('max(messages.created_at) desc')
you can use left join instead of includes
#users = User.joins("LEFT JOIN messages on messages.user_id = users.id").order('messages.created_at').limit(5)

ruby on rails how to get latest records from has many relationship

I am new to Ruby on Rails. I have a problem- how to get only latest records from has many relationship in model. My code is like this:
class User < ActiveRecord::Base
has_many :books
end
class Book < ActiveRecord::Base
belongs_to :user
scope :rating, -> { where(rating: 5) }
end
What I would like to achieve is: I'd like to get all latest (last) book for each user and than filter this books using scope rating. I can do this using for each loop (Looping through User.books and get book.last)- but this is very inefficient because I have a lot of data. Do you have any ideas how I can do this? maybe some kind of scope chaining with each other?
You can do this if you want to avoid writing SQL:
class Book < ActiveRecord::Base
scope :latest, -> do
book_ids_hash = Book.group(:user_id).maximum(:id)
book_ids = book_ids_hash.values
where(id: book_ids)
end
end
Then all you have to do is:
Book.rating.latest
Try this:
User.find_each do |user|
puts 'User top 10 books:'
user.books.rating.order('id DESC').limit(10).each do |book|
puts book.title
end
end
I'd like to get all latest (last) book for each user
In the example above, note the .order('id DESC').limit(10). This will sort books by ID in descending order.
One book for each user, then limit the result to only those who have rating 5, one query no loops or any thing.
Book.order(created_at: :desc).group(:user_id).rating
Just use last method and pass the number of records you want to the parameter :
user.books.last(5).rating
this will give you the 5 last books and apply the rating to it.
see http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-last

rails how to reference current object in Model

This is a continuation of another question, but as it's different, I though I had better repost it as a new question:
Old Question
I'm adding quiz functionality to the twitter app from the Hartl tutorial and have these Models:
User is nearly the same as the tutorial:
class User < ActiveRecord::Base
has_many :followed_users, through: :relationships, source: :followed
has_many :takens, dependent: :destroy
has_many :questions, through: :takens
end
Taken is a table of Question ids to User ids:
class Taken < ActiveRecord::Base
belongs_to :user
belongs_to :question
end
nothing interesting in Question:
class Question < ActiveRecord::Base
attr_accessible :category, :correct, :option1, :option2, :option3, :qn
end
I want to be able to show followed_users and followers in order of the number of tests they have taken. In the console this can be had through:
User.find_by_id(1).question_ids.count
Then I can do something like:
User.find_by_id(1).followers.first.question_ids.count
in the console to get the count for a single follower.
I feel like I'm almost there.
How do I sort the followers and followed_users through their 'takens' count? (I was also looking at cache_count, which at first seemed promising, but might not be what I need...)
End Old Question
This is the answer from the other question: rails order through count on other table
and I went with a method like this in User.rb:
def users_sort_by_taken
User.find_by_sql("SELECT users.*
SELECT users.*
FROM users INNER JOIN takens
ON users.id = takens.user_id
GROUP BY users.id
ORDER BY count(takens.user_id) DESC")
end
which gets called in the users_controller.rb like so:
def following
require 'will_paginate/array'
#title = "Following"
#user = User.find(params[:id])
##users = #user.followed_users.paginate(page: params[:page])
#users = #user.users_sort_by_taken.paginate(page: params[:page])
render 'show_follow'
end
(For reference, the commented out line is from the Hartl tutorial)
all well and good, but now the current user is contained in the list of following (because of the above SQL). I need a way to eliminate the current user from the users_sort_by_taken.
I thought this might work:
WHERE (#{#current_user.id})
in the method,but I get this error:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
I suppose I could pass it as an argument...
but don't I already have the user as #user in the following line?
#users = #user.users_sort_by_taken.paginate(page: params[:page])
Why can't I reference the current user from a method in the User.rb model?
Or another way, can I pass the current_user (or #user or user) to the SQL to exclude the current_user from the SQL results?
Any help appreciated.
Every object has its own set of instance variables - the fact that #user or #current_user is set in one object means nothing to another object.
The receiver of a method (in this case your user) is always available as self, so self.id gets you the user's id
The self is actually implicit - most of the time you won't need it and just writing id would result in the same thing (as long as you're in an instance method of that user)
To reference #user's id in the model, you can simply use:
self.id

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|
...

Resources