How do I count unread comments using "Unread" Ruby gem? - ruby-on-rails

comments_helper.rb:
module CommentsHelper
def unread_comments_count
#comments_count = Comment.unread_by(user).count
end
end
In my ApplicationController, I defined user:
def user
#user = User.find_by(params[:user_id])
end
I could have used current_user, but the result I got (with that) is total number of comments in my database. So, I thought I should use user. But it didn't work.
view:
<%= unread_comments_count %>
However, the result is still total number of all the comments made on all the posts by all users. I can't actually figure out what I am doing wrong because I have carefully done everything right in my Models as guided by the gem.
I would appreciate any clue to fix this.
Update
The query it generates is:
SELECT COUNT(*) FROM "comments" 'LEFT JOIN read_marks ON read_marks.readable_type = "Message" AND read_marks.readable_id = comments.id AND read_marks.reader_id = 1 AND read_marks.reader_type = 'User' AND read_marks.timestamp >= comments.created_at WHERE read_marks.id IS NULL

Related

How to Sort Record in Ruby on Rails based on Last Record Timestamp from References Table

I need to create a live chat app and now I have three models :
ChatRoom
ChatRoomMember
ChatRoomMessage
From this three models, I use includes and references to get the list of chat_rooms for current login user. Here is the code that I have wrote.
#chat_rooms = ChatRoom.includes(:members).references(:members)
#chat_rooms = #chat_rooms.includes(:messages).references(:messages)
#chat_rooms = #chat_rooms.where 'chat_room_members.user_id = ?', #current_user.id
#chat_rooms = #chat_rooms.order 'chat_room_messages.created_at DESC'
#chat_rooms = #chat_rooms.limit(limit).offset(offset)
However, the order didn't work as I expected. What I want is that the chat_rooms are sorted by created_at column from the last message in that room. How can I do this ?
Here is the database structure :
Use association to avoid where 'chat_room_members.user_id = ?', #current_user.id
Here is my suggestion, assuming User has associations looking like:
class User
has_many :chat_room_members
has_many :chat_rooms, through: :chat_room_members
end
# list only rooms with at least on message
#chat_rooms = #current_user.chat_rooms.joins(:messages).order('chat_room_messages.created_at DESC').limit(limit).offset(offset)
# list all rooms even if there is no message attached
#chat_rooms = #current_user.chat_rooms.distinct.left_joins(:messages).order('chat_room_messages.created_at DESC').limit(limit).offset(offset)
Try this:
ChatRoom.includes(:messages).order('chat_room_messages.created_at DESC')
Thanks for everyone has help me to solve this probel. I have my own answer. Here is it.
SELECT chat_rooms.*, (SELECT chat_room_messages.created_at FROM chat_room_messages WHERE chat_room_messages.chat_room_id = chat_rooms.id ORDER BY chat_room_messages.id DESC LIMIT 1) AS last_message_at FROM chat_rooms INNER JOIN chat_room_members ON chat_room_members.chat_room_id = chat_rooms.id WHERE chat_room_members.user_id = #{#current_user.id} ORDER BY last_message_at DESC
I use raw query for solve this problem. Hope this can help anyone who need it !

Rails active record where OR clause with grandparent property

I've got a Rails Pundit Policy scope which I'm trying to figure out.
If I have these 3 models:
User - has many stories
Story - belongs to a user and has many chapters
Chapter - belongs to a story
I want any user to be able to view any published chapters or any chapters which were written by the user (even non-published ones).
Obviously if I am an author who wrote a story, I should be able to view my own stories as well as all my chapters for my stories, even if they are not published. Everyone else though, can only view my published stories and published chapters within those published stories.
I have tried this:
class ChapterPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(published: true).or(scope.where("story.user.id = ?", #user.id))
end
end
end
Supposedly we need to use JOINS I read, so I have also tried this:
scope.where(published: true).or(scope.joins(:story).where(stories: { user: #user }))
but I get error:
Relation passed to #or must be structurally compatible. Incompatible values: [:joins, :references]
Any ideas how I can write the query to do what I wanted?
You can use Arel expressions to write this:
Chapter.joins(story: :user)
.where(Chapter.arel_table[:published].eq(true))
.where(Story.arel_table[:published].eq(true))
.or(Chapter.joins(story: :user).where(Story.arel_table[:user_id].eq(user.id)))
That expression produces this query:
SELECT "chapters".* FROM "chapters" INNER JOIN "stories" ON "stories"."id" = "chapters"."story_id" INNER JOIN "users" ON "users"."id" = "stories"."user_id" WHERE ("chapters"."published" = 't' AND "stories"."published" = 't' OR "stories"."user_id" = 1)
I think I finally got it:
scope.joins(story: :user).where("(stories.published = ? AND chapters.published = ?) OR stories.user_id = ?", true, true, #user.id)
Was hoping for a more non SQL statement way, but oh well, guess this will do unless someone know's the non-sql way.
Did you have a look at Arel ?
You could have something like this:
chapters = Arel::Table.new(:chapter)
stories = Arel::Table.new(:story)
rel = chapters.joins(stories)
.where(chapters[:published].eq(true)
.where(stories[:published].eq(true))
.or(stories[:user_id].eq(#user.id))
scope.where(rel)

Rails query with condition in count

I'm having a little trouble with a query in Rails.
Actually my problem is:
I want to select all users which do not have any user_plans AND his role.name is equals to default... OR has user_plans and all user_plans.expire_date are lower than today
user has_many roles
user has_many user_plans
users = User.where(gym_id: current_user.id).order(:id)
#users = []
for u in users
if u.roles.pluck(:name).include?('default')
add = true
for up in u.user_plans
if up.end_date > DateTime.now.to_date
add = false
end
end
if add
#users << u
end
end
end
This code up here, is doing exactly what I need, but with multiple queries.
I don't know how to build this in just one query.
I was wondering if it is possible to do something like
where COUNT(user_plans.expire_date < TODAY) == 0
User.joins(:user_plans, :roles).where("roles.name = 'default' OR user_plans.expire_date < ?", Date.today)
Should work, not tested, but should give you some idea you can play with (calling .distinct at the end may be necessary)
There is also where OR in Rails 5:
User.joins(:user_plans, :roles).where(roles: { name: 'default' }).or(
User.joins(:user_plans).where('user_plans.expire_date < ?', Date.today)
)
FYI: Calling .joins on User will only fetch those users who have at least one user_plan (in other words: will not fetch those who have no plans)

Why does ActiveRecord query return ambiguous column reference?

In a Rails app I'm setting up nested routes
resource Country do
resource State
resource City
resource User
resource Post
end
I now want to display users that have posted within a country at ../country/1/users
In the User controller I have:
def index
#user = User.includes(:posts, :city, :state, :country)
#user = #users.where("states.country_id = ?", params[:country_id]) if params[:country_id]
#active_users = #users.where('posts_count > ?', 0).ranked_scope
end
I'm, getting:
PG::Error: ERROR: column reference "posts_count" is ambiguous
LINE 1: ...untry_id" WHERE (states.country_id = '1') AND (posts_co...
I'm not sure if this is because:
There is a posts_count column on User, City, State, Country models and the query does not know which on to use. If so, what is the correct syntax to specify the table?
I'm stringing together two where clauses incorrectly.
Something else....?
I'd appreciate any suggestions or ideas.
Try:
#active_users = #users.where('users.posts_count > ?', 0).ranked_scope
OK, I eventually got to the bottom of this. The following query was incorrect
#users = #users.where("states.country_id = ?", params[:country_id]) if params[:country_id]
Post belongs to User, and Post has one Country through State. Without a direct relationship in one direction between USer and Country, I modified the query to
#users = #users.where(id: Country.find(params[:country_id]).users.uniq if params[:country_id]
(and double checked all my model relationships were properly configured).
Works!
The error message was not very helpful in tracking this down though!

ActiveRecord and SELECT AS SQL statements

I am developing in Rails an app where I would like to rank a list of users based on their current points. The table looks like this: user_id:string, points:integer.
Since I can't figure out how to do this "The Rails Way", I've written the following SQL code:
self.find_by_sql ['SELECT t1.user_id, t1.points, COUNT(t2.points) as user_rank FROM registrations as t1, registrations as t2 WHERE t1.points <= t2.points OR (t1.points = t2.points AND t1.user_id = t2.user_id) GROUP BY t1.user_id, t1.points ORDER BY t1.points DESC, t1.user_id DESC']
The thing is this: the only way to access the aliased column "user_rank" is by doing ranking[0].user_rank, which brinks me lots of headaches if I wanted to easily display the resulting table.
Is there a better option?
how about:
#ranked_users = User.all :order => 'users.points'
then in your view you can say
<% #ranked_users.each_with_index do |user, index| %>
<%= "User ##{index}, #{user.name} with #{user.points} points %>
<% end %>
if for some reason you need to keep that numeric index in the database, you'll need to add an after_save callback to update the full list of users whenever the # of points anyone has changes. You might look into using the acts_as_list plugin to help out with that, or that might be total overkill.
Try adding user_rank to your model.
class User < ActiveRecord::Base
def rank
#determine rank based on self.points (switch statement returning a rank name?)
end
end
Then you can access it with #user.rank.
What if you did:
SELECT t1.user_id, COUNT(t1.points)
FROM registrations t1
GROUP BY t1.user_id
ORDER BY COUNT(t1.points) DESC
If you want to get all rails-y, then do
cool_users = self.find_by_sql ['(sql above)']
cool_users.each do |cool_user|
puts "#{cool_user[0]} scores #{cool_user[1]}"
end

Resources