How can you use distinct in rails while still using ActiveRecord's - ruby-on-rails-3

I am struggling with the following problem:
I want to have two different tabs, one that displays all recent chugs (Done), and one that displays the chugs that are the fastest per person.
However, this needs to remain an ActiveRecord, since I need to use it with link_to and gravatar, thus restraining me from group_by, as far as I understand it.
AKA: If there are three users who each have three chugs, I want to show 1 chug per user, which contains the fastest time of that particular user.
The current code looks like this, where chugs_unique should be edited:
def show
#pagy, #chugs_all_newest = pagy(#chugtype.chugs.order('created_at DESC'), items: 10, page: params[:page])
#chugs_unique = #chugtype.chugs.order('secs ASC, milis ASC, created_at DESC').uniq
breadcrumb #chugtype.name, chugtypes_path(#chugtype)
end
In this case, a chug belongs to both a chugtype and user, and the chugtype has multiple chugs.
Thanks in advance!

Related

Rails advance ordering with SQL

I have User model where it has some attributes like is_admin, is_verified, and also has association with other model such as Badges and Activities.
I have table design in HTML where in default, the user will be ordered by is_verified, then is_admin, then number of badges, then number of activities respectively. But I don't know how to create one.
I have tried sample code like this:
users = User.all.limit(10)
users.order(is_verified: :true).order(is_admin: :true).order(users.map{|user| user.badges.count}).order(users.map{|user| user.activites.count})
But this will not work since order only accept :asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]
Do you have any new methods to do this, I'm new to query? Thank you very much for your help.
I have sample design like this:
order accepts multiple args, so you could use something like the following:
users.order(is_verified: :desc, is_admin: :desc)
.order('badges_count DESC', 'activities_count DESC')
For the boolean columns, they're typically stored in the db as 1 for true, 0 for false, hence the desc direction. If you're not defaulting these columns to one or the other (presumably false), you might want to adjust to (i.e.) order("is_verified DESC NULLS LAST", ...).
This would require a bit of a refactor to run efficiently, relying on counter_caches for badges and activities. If you're going to be querying this regularly, I'd highly advise adding this in. There's a good guide here.
All that will be required is:
new columns for activities_count and badges_count
counter_cache: true set on the belongs_to side of the association
the counters reseting using this method, i.e. User.reset_counters(:badges, :activities)
Otherwise, as is, you could use:
User.left_joins(:badges, :activities)
.order(is_verified: :desc, is_admin: :desc)
.group("badges.id", "activities.id")
.order('COUNT(badges.id) DESC', 'COUNT(activities.id) DESC')
If you're curious as to it's output, you can call to_sql to find how it's actually calling the data from the db.
Have a go at that and let me know how you get on - happy to help if you have any questions!
You could try ordering with database itself, which is more fast than perform sorting on loaded records array. By default if you order any boolean field in descending order, all records having boolean value as true will come first. The final query may look like,
User.order(is_verified: :desc).order(is_admin: :desc).
left_joins(:badges).
group(:id).
order("count(badges.id) desc")
left_joins(:activities)
group(:id).
order("count(activities.id) desc")
Left joins (Left Outer Joins) include documents having zero or more associated records. You can find the details in the documentation page.
Hope it helps !

Showing data from multiple tables in Rails

I'm working on a system that tracks illnesses and symptoms associated with them through Ruby on Rails.
I have a "illnesses" table and a "symptoms" table and they have a many_to_many relationship through the "symptoms_illnesses" table.
For now, I'm working on a page that displays a single entry of the "symptoms_illnesses" table. This table has two columns: illness_id and symptom_id.
I need a way to display the illness and symptom that match the ID.
Example:
"illness" table has "Common Cold" under id = 1
"symptom" table has "Fever" under id = 1
"symptoms_illness" table has illness_id = 1 and symptom_id = 1.
I want the "symptoms_illness/1" page to display "Common Cold" and "Fever", but I see no obvious way to do it.
Other topics in this site don't seem to address the problem in a satisfactory way.
Edit 1:
I didn't add anything to the show action besides the "default"
def show
#symptoms_illness = Symptoms_illness.find(params[:id])
end
def show
#symptoms_illness = Symptoms_illness.includes(:illness,:symptom).where(id: params[:id])
end
in your view
<%= #symptoms_illness.illness.name %>
<%= #symptoms_illness.symptom.name %>
PS: not tested
After doing some research I found out how to do it:
First, I created #illness and #symptom instances to the symptom_illness_controller. Then, in the show action, I added two find_by_id.
Finally, in the Show view, I changed #symptomillness.illness_id to #symptomillness.illness.name, now it shows up fine.
This may not be the cleanest way to do it but it works for what I wanted to accomplish.

Rails Order by frequency of a column in another table

I have a table KmRelationship which associates Keywords and Movies
In keyword index I would like to list all keywords that appear most frequently in the KmRelationships table and only take(20)
.order doesn't seem to work no matter how I use it and where I put it and same for sort_by
It sounds relatively straight forward but i just can't seem to get it to work
Any ideas?
Assuming your KmRelationship table has keyword_id:
top_keywords = KmRelationship.select('keyword_id, count(keyword_id) as frequency').
order('frequency desc').
group('keyword_id').
take(20)
This may not look right in your console output, but that's because rails doesn't build out an object attribute for the calculated frequency column.
You can see the results like this:
top_keywords.each {|k| puts "#{k.keyword_id} : #{k.freqency}" }
To put this to good use, you can then map out your actual Keyword objects:
class Keyword < ActiveRecord::Base
# other stuff
def self.most_popular
KmRelationship.
select('keyword_id, count(keyword_id) as frequency').
order('frequency desc').
group('keyword_id').
take(20).
map(&:keyword)
end
end
And call with:
Keyword.most_popular
#posts = Post.select([:id, :title]).order("created_at desc").limit(6)
I have this listed in my controller index method which allows the the order to show the last post with a limit of 6. It might be something similar to what you are trying to do. This code actually reflects a most recent post on my home page.

How to perform a conditional count in a Rails active record query?

In a Rails 3.2 app I'm trying to construct a query that will return an array with two calculated counts. The app has three models: a User has many Events, and a Location has many Events. I need to return an array for a user that contains the number of events they have at each location, as well as the number of active events.
e.g., [#<Location id: 1, name: "Location Name", total_events_count: 4, active_event_count: 4>]>
I can get the total_event_count
user = User.find(params[:id])
user.locations
.select("locations.id, locations.name, count(events.id) AS total_events_count")
.joins(:events)
.group("locations.id")
Given that my Event model has a string status field that can take a value active, how would I also include an active_events_count in this query?
EDIT
After some useful suggestions from xdazz and mu, I'm still struggling with this. My problem appears to be that the count is counting all events, rather than events that belong to the location AND the user.
I'm going to try to rephrase my question, hopefully someone can help me understand this.
Events belong to both a User and a Location (i.e. User has_many :locations, through: :events)
Events have several fields, including status (a string) and participants (an integer).
In a User show view, I'm trying to generate a list of a User's locations. For each location, I want to display a "success rate". The success rate is the total number of a User;s events with participants, divided by the total number of that User's events. i.e., if User1 has 4 events at LocationA, but only two of those events had participants, User1's success rate at LocationA is 0.5 (or 50%).
The way I though to achieve this is via a select query that also includes calculated counts for total_events_count and successful_events_count. (there may be a better way?)
So I do something like:
user = User.find(params[:id])
user.locations
.select("locations.id, locations.name, count(events.id) AS total_events_count, sum(case events.marked when NOT 0 then 1 else 0 end) AS successful_events_count")
.joins(:events)
.group("locations.id")
This is returning an array with the correct keys, but the values are not correct. I am getting the total number of all events (and all successful events) for that location, not just a count of those events that belong to the current user.
I have been looking at this problem for so long that I'm getting myself very confused. Would be very grateful for a fresh perspective and ideas!!
EDIT2
After a break and fresh eyes, I have managed to get the result I need using the following code. It seems quite convoluted. If there is a better way, please let me know. Otherwise I will tidy up this question in case anyone else runs into the same problem.
class User
def location_ratings
events = self.events
successful_events = events.where('events.participants > 0')
user_events_by_location = Event.
select("events.location_id AS l_id, count(events.id) AS location_event_count").
where( id: events.pluck(:id) ).
group("l_id").
to_sql
user_successful_events_by_location = Event.
select("events.location_id AS l_id, count(events.id) AS location_successful_events_count").
where( id: successful_events.pluck(:id) ).
group("l_id").
to_sql
Location.
joins("JOIN (#{user_events_by_location}) AS user_events ON user_events.l_id = location.id").
joins("JOIN (#{user_successful_events_by_location}) AS successful_user_events ON successful_user_events.l_id = location.id").
select('location.id, location.name, user_events.location_events_count, successful_user_events.location_successful_events_count').
order("user_events.location_events_count DESC")
end
You could use sum(events.marked='active') to get it:
user.locations
.select("locations.id, locations.name, count(events.id) AS total_events_count, sum(events.marked='active') AS marked_event_count")
.joins(:events)
.group("locations.id")
Update:
If you are using postgresql, then you have to case boolean to int before using SUM function.
user.locations
.select("locations.id, locations.name, count(events.id) AS total_events_count, sum((events.marked='active')::int) AS marked_event_count")
.joins(:events)
.group("locations.id")

Update more record in one query with Active Record in Rails

Is there a better way to update more record in one query with different values in Ruby on Rails? I solved using CASE in SQL, but is there any Active Record solution for that?
Basically I save a new sort order when a new list arrive back from a jquery ajax post.
#List of product ids in sorted order. Get from jqueryui sortable plugin.
#product_ids = [3,1,2,4,7,6,5]
# Simple solution which generate a loads of queries. Working but slow.
#product_ids.each_with_index do |id, index|
# Product.where(id: id).update_all(sort_order: index+1)
#end
##CASE syntax example:
##Product.where(id: product_ids).update_all("sort_order = CASE id WHEN 539 THEN 1 WHEN 540 THEN 2 WHEN 542 THEN 3 END")
case_string = "sort_order = CASE id "
product_ids.each_with_index do |id, index|
case_string += "WHEN #{id} THEN #{index+1} "
end
case_string += "END"
Product.where(id: product_ids).update_all(case_string)
This solution works fast and only one query, but I create a query string like in php. :) What would be your suggestion?
You should check out the acts_as_list gem. It does everything you need and it uses 1-3 queries behind the scenes. Its a perfect match to use with jquery sortable plugin. It relies on incrementing/decrementing the position (sort_order) field directly in SQL.
This won't be a good solution for you, if your UI/UX relies on saving the order manually by the user (user sorts out the things and then clicks update/save). However I strongly discourage this kind of interface, unless there is a specific reason (for example you cannot have intermediate state in database between old and new order, because something else depends on that order).
If thats not the case, then by all means just do an asynchronous update after user moves one element (and acts_as_list will be great to help you accomplish that).
Check out:
https://github.com/swanandp/acts_as_list/blob/master/lib/acts_as_list/active_record/acts/list.rb#L324
# This has the effect of moving all the higher items down one.
def increment_positions_on_higher_items
return unless in_list?
acts_as_list_class.unscoped.where(
"#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
).update_all(
"#{position_column} = (#{position_column} + 1)"
)
end