Count total association number on search result - ruby-on-rails

I have following many-to-many association...
class User
has_and_belongs_to_many :starred_jobs, class_name: "Job",
join_table: "starred_jobs"
end
class Job
has_and_belongs_to_many :starred_by, join_table: "starred_jobs",
class_name: "User"
end
In a query, I get number of users between two dates like
users = User.where(created_at: start_date..end_date)
In a summary page, I want to view total number of jobs of selected users. Now, how I get the total number of jobs from searched users? For example:
#total_users_jobs = users.jobs.count
Thanks in advance.

Simplest and maybe resource heavy method would be
users.map { |u| u.jobs.count }.sum
another method
JobUser.where("user_id in (?)", users.map(&:id)).count
Another
Job.joins(:users).where(users: {id: users.map(&:id)}.uniq.count

This should give you the total count of all Jobs for all matching Users
#total_users_jobs = Job.joins(:users).where(users: {id: users.map(&:id)}.uniq.count

Related

Count distinct records while plucking from another table

Given Rails app where:
class Team < ActiveRecord::Base
belongs_to :captain, class_name: User
I'm trying to succinctly query database and get a summary of Captains and the number of Teams they belong to:
[[23, "Pat", "Smith"]] Teams: 1
[[46, "Kate", "Jones"]] Teams: 1
[[107, "John", "Doe"]] Teams: 3
...
I can get a hash with each Captain's ID and the number of teams:
> Team.group(:captain_id).count
=> {85=>3, 106=>1, 81=>1, 25=>1, 32=>1, 8=>3, 1=>1, 79=>2, 26=>1}
But then it gets ugly. The following works but seems ugly and has n+1 SQL queries:
Team.group(:captain_id).count.sort.to_h.each { |key,val| puts "#{User.where(id: key).pluck(:id, :first, :last)} Teams: #{val}" }
What's the Rails way?
Since you are trying to get detailed info about users, you may want to define a relation like this on your User model:
class User < ActiveRecord::Base
has_many :captained_teams, foreign_key: :captain_id, class_name: Team
Then you can work with User for your query:
User.joins(:captained_teams).includes(:captained_teams).each do |user|
puts "#{[user.id, user.first, user.last]} Teams: #{user.captained_teams.length}"
end
sql = "**Select count then group by then having count(...)**"
result = ActiveRecord::Base.connection.execute(sql);
You can use the raw sql to get what you want.

Rails & Active Record: Find objects whose children don't belong to current_user OR who don't have any children

User.rb
has_many :votes
Event.rb
has_many :votes
Vote.rb
belongs_to :user
belongs_to :event
I am trying to render all the Events that the current_user has not voted on.
Consider this situation:
There are 100 events Event.includes(:votes).count ==> 100
The User has voted on 5 events current_user.votes.count ==> 5
There are 75 events with at least one vote from other users
20 of the events have not received votes from any users
The result I'm looking for should render the 95 events that have not been voted on by the current_user, including events that have not been voted on by any user.
This query gets all the events that NO users have voted on:
Event.includes(:votes).where(:votes => {:id => nil}).references(:votes).count ==> 20
What query can I use to get all the events that have been voted on by users excluding those that have been voted on by current user (should return 75 in the example above)? I tried the below query but it returned 80 (it included events voted on by the current_user):
Event.includes(:votes).where.not(:votes => {:id => current_user}).references(:votes).count ==> 80
What query can I use to get events with votes, excluding ones that current user voted for
Can I combine the two queries into one?
Hacky but fun answer
class Event
has_many :votes
has_many :user_votes,
->() { where({user_id: Thread.current[:user]&.id}.compact) },
class_name: 'Vote'
def self.using_user(user)
old_user, Thread.current[:user] = Thread.current[:user], user
yeild
ensure
Thread.current[:user] = old_user
end
end
Event.using_user(current_user) do
Event.includes(:user_votes).where("votes.id IS NULL")
end
Original answer:
Event.
joins(
"LEFT OUTER JOIN votes ON votes.event_id = events.id AND " +
"votes.user_id = #{user.id}"
).
where("votes.id IS NULL")
Can I combine the two queries into one?
Use merge if you want to combine using AND:
results = frecords.merge(lrecords)
If you want to combine using OR, use or (only available in ActiveRecord 5+):
results = frecords.or(lrecords)
What query can I use to get events with votes, excluding ones that current user voted for.
events_current_user_voted_on = current_user.votes.pluck(:event_id)
events_all_users_voted_on = Vote.pluck(:event_id).uniq
events_only_others_voted_on = events_all_users_voted_on - events_current_user_voted_on
# 75 events with at least one vote from other users
Event.where(id: events_only_others_voted_on)
Answer for your first question:
I have modified your tried query a little bit so try this
Event.includes(:votes).where.not(:votes => {:user_id => current_user}).references(:votes).count
notice the where condition. You need to check user_id column of votes
or you can try string syntax like:
Event.includes(:votes).where.not("votes.user_id" => current_user).references(:votes).count
about your second question, I am not sure I understand your requirement. If you need to filter events with at least 1 vote and from other user only then just add another to where statement .
Event.includes(:votes).where.not("votes.user_id" => current_user, "votes.user_id" => nil).references(:votes).count
And BTW, I think you have a error in vote.rb you have specified a association to itself instead of association to Event i.e belongs_to :event

Joining two ActiveRecord associations on common attribute

Let's say I have a User model. User has 2 has_many associations, that is User has many pencils and has many cars. Cars and Pencils table has same attribute, :date, and separate such as :speed(car) and :length(pencil). I want to join a user's pencils and cars on their common attribute, :date, so that I have an array/relation [:date, :speed, :length]. How do I achieve that, I tried joins and merge but they were no use.
I'd definitely recommend getting this into a query rather than a loop, for efficiency's sake. I think this will work:
Car.joins(:user => :pencils).where("pencils.date = cars.date")
And if you want to reduce it to the array immediately:
Car.joins(:user => :pencils).where("pencils.date = cars.date").pluck("cars.date", "cars.speed", "pencils.length")
If you need to include matches where date is nil, you might need to add:
Car.joins(:user => :pencils).where("(pencils.date = cars.date) OR (pencils.date IS NULL AND cars.date IS NULL)")
Many more efficient options exist, but here is one possible approach:
class User < ActiveRecord::Base
def get_merged_array
dates = (cars.map(&:date) & pencils.map(&:date))
results = []
dates.each do |date|
cars.where(date: date).each do |car|
pencils.where(date: date).each do |pencil|
results << [date, car.speed, pencil.length]
end
end
end
results
end
end

How to sort by associated model within given value?

I have model Item and model Stats.
Item
has_many :stats
Stat
belongs_to :items
In the model (e.g. mysql table) Stat there is 3 fields:
rating
skin_id
item_id
So for Stat, it could be, like:
#item.stats => Array of stats for records with item_id = 1, with a differer skin_id
I need to sort all items, for a given skin_id by 'rating'.
Something like:
#items = Item.all.order('stats[currtnt_skin.id] DESC') (of course it doesn't work)
In other words i need to sort within array of:
#stats = #items.stats[current_skin.id]
#items.order (... by #stats ...)
How it could be done?
Firstly I'm presuming by belongs_to :items you mean belongs_to :item (singular) given the presence of the item_id foreign key.
Secondly, to solve your specific query you can use:
Stat.where(:skin_id => skin_id).joins(:item).order("items.rating DESC")
However, if skin_id refers to another model - i.e. Stat belongs_to :skin and Skin has_many :stats then it may make more sense to start from there:
skin = Skin.find(1)
stats = skin.stats.order("rating DESC").includes(:item)
To get the items then just loop through them:
stats = skin.stats.order("rating DESC").includes(:item)
stats.each do |stat|
stat.item
end
F
#items = Item.join(:stats).order('skin_id DESC')
I believe, though I might be mistaken that joining the table will do so on the association you've defined.
in rails 3 it will be something like:
Item.includes("stats").order("stats.skin_id desc")
Have you tried this ?
Item.includes("stats").where('stats.skin_id = ?', 1).order("stats.rating desc")

Trying to find a count of items from a two level deep association, any ideas?

I am working on an application where I need to find the count of submitted items by users that have been referred by a user.
For Example -
User1 has referred 3 people (User2, User3, User4) and each of those users has submitted 5 articles.
I am trying to find a way to get the count of submitted items in User1's tree (should be 15 in this case).
My user model looks like the following (simplified)
class User < ActiveRecord::Base
# Code for user referrals
belongs_to :referrer, :class_name => "User"
has_many :referrals, :class_name => "User", :foreign_key => "referrer_id"
has_many :items
end
I can find out the count for each user easily (User.items.size), but I am having trouble finding a solution to get the referral counts as one sum.
Any ideas?
Try this:
user = User.find(1)
total_items_size = user.referrals.map(&:items).flatten.size
You can use select_value to manually run the SQL query:
def referred_items_count
select_value("select count(*) as referred_items
from items inner join users on users.id = items.user_id
where users.referrer_id = #{self.id};", "referred_items")
end
The benefit is that it is a lot more scalable than using Ruby to count.
Get all items of User with id of 1
total = 0
Users.find(1).referrals.each do |refer|
total += refer.items.size
end

Resources