Rails/ActiveRecord: Can I perform this query without passing the SQL string to #order? - sql

I have two models Issue and Label. They have a many to many relationship.
I have a method that returns the ten labels that point to the most issues.
class Label < ApplicationRecord
has_many :tags
has_many :issues, through: :tags
def self.top
Label.joins(:issues)
.group(:name)
.order('count_id desc')
.count(:id)
.take(10)
end
end
It does exactly what I expect it to but I want to know if it's possible to compose the query without the SQL string.
order('count_id DESC') is confusing me. Where does count_id come from? There isn’t a column named count_id.
Label.joins(:issues).group(:name).column_names
#=> ["id", "name", "created_at", "updated_at"]
I’ve found some SQL examples here. I think it’s basically the same as ORDER BY COUNT(Id):
SELECT COUNT(Id), Country
FROM Customer
GROUP BY Country
ORDER BY COUNT(Id) DESC
Is it possible to perform the same query without passing in the SQL string? Can it be done with the ActiveRecord querying interface alone?

If you look at your query log, you'll see something like:
select count(labels.id) as count_id ...
The combination of your group call (with any argument) and the count(:id) call gets ActiveRecord to add the count_id column alias to the query. I don't think this is documented or specified anywhere (at least that I can find) but you can see it happen if you're brave enough to walk through the Active Record source.
In general, if you add a GROUP BY and then count(:x), Active Record will add a count_x alias. There's no column for this so you can't say order(:count_id), order(count_id: :desc), or any of the other common non-String alternatives. AFAIK, you have to use a string but you can wrap it in an Arel.sql to prevent future deprecation issues:
Label.joins(:issues)
.group(:name)
.order(Arel.sql('count_id desc'))
.count(:id)
.take(10)
There's no guarantee about this so if you use it, you should include something in your test suite to catch any problems if the behavior changes in the future.

Related

rails sql find models related to self with attribute difference greater than threshold

I have a Person model in rails that is related to itself:
class Person < ApplicationRecord
has_many :children,
class_name: "Person",
foreign_key: :person_id
belongs_to :mother,
class_name: "Person",
foreign_key: :person_id
end
People also have an age attribute representing their ages in years. I would like an ActiveRecord or SQL query to find all people with child-to-mother age discrepancies that are greater than or equal to 50 years.
(The model name here is not what I'm truly using in my app. I renamed it to simplify the problem description. However, for my actual query I will need to know if the absolute value of the age difference is greater than or equal to 50 years. Obviously children can't be older than their mothers so the analogy doesn't hold there.)
I have tried the following and several variants, which are all crashing:
Person
.includes(:mother)
.where("ABS(persons.age - (SELECT age FROM persons WHERE (id = persons.person_id))) >= 50")
This one in particular results with a hint:
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
At first I thought that maybe the latest SQL querie's where clause was returning some sort of list of ages rather than just the mother's age, so I tried subscripting with a [1] after it to get the first element of (what should be) a single element array (if my hypothesis was correct). It was not correct - the SQL error then updated to let me know I couldn't subscript something that wasn't an array.
I'm looking for the simplest solution. Any help is appreciated. Thanks!
Not in a position to test this but I believe reails will generate aliases for you on the fly.
Try
Person
.includes(:mother)
.where("ABS(persons.age - (SELECT age FROM mothers_persons)) >= 50")
Generally it's association name pluralized, underscore, table name (so if users have employees the associated table (alias) of user's employees is called employees_users
If the above doesn't work, you would need to create aliases manually in your SQL call.

ActiveRecord: can't use `pluck` after `where` clause with eager-loaded associations

I have an app that has a number of Post models, each of which belongs_to a User model. When these posts are published, a PublishedPost model is created that belongs_to the relevant Post model.
I'm trying to build an ActiveRecord query to find published posts that match a user name, then get the ids of those published posts, but I'm getting an error when I try to use the pluck method after eager-loading my associations and searching them with the where method.
Here's (part of) my controller:
class PublishedPostsController < ApplicationController
def index
ar_query = PublishedPost.order("published_posts.created_at DESC")
if params[:searchQuery].present?
search_query = params[:searchQuery]
ar_query = ar_query.includes(:post => :user)
.where("users.name like ?", "%#{search_query}%")
end
#found_ids = ar_query.pluck(:id)
...
end
end
When the pluck method is called, I get this:
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'users.name' in 'where clause': SELECT id FROM `published_posts` WHERE (users.name like '%Andrew%') ORDER BY published_posts.created_at DESC
I can get the results I'm looking for with
#found_ids = ar_query.select(:id).map{|r| r.id}
but I'd rather use pluck as it seems like the cleaner way to go. I can't figure out why it's not working, though. Any ideas?
You need to and should do joins instead of includes here.
The two functions are pretty similar except that the data from joins is not returned in the result of the query whereas the data in an includes is.
In that respect, includes and pluck are kind of antithetical. One says to return me all the data you possibly can, whereas the other says to only give me only this one little bit.
Since you only want a small amount of the data, you want to do joins. (Strangely select which also seems somewhat antithetical still works, but you would need to remove the ambiguity over id in this case.)
Try it out in the console and you'll see that includes causes a query that looks kind of like this: SELECT "posts"."id" as t0_ro, "posts"."text" as t0_r1, "users"."id" as t1_r0, "users"."name" as t1_r1 ... When you tack on a pluck statement all those crazy tx_ry columns go away and are replaced by whatever you specified.
I hope that helps, but if not maybe this RailsCast can. It is explained around the 5 minute mark.
http://railscasts.com/episodes/181-include-vs-joins
If you got here by searching "rails pluck ambiguous column", you may want to know you can just replace query.pluck(:id) with:
query.pluck("table_name.id")
Your query wouldn't work as it is written, even without the pluck call.
Reason being, your WHERE clause includes literal SQL referencing the users table which Rails doesn't notice and decides to use multiple queries and join in memory ( .preload() ) instead of joining in the database level ( .eager_load() ):
SELECT * from published_posts WHERE users.name like "pattern" ORDER BY published_posts.created_at DESC
SELECT * from posts WHERE id IN ( a_list_of_all_post_ids_in_publised_posts_returned_above )
SELECT * from users WHERE id IN ( a_list_of_all_user_ids_in_posts_returned_above )
The first of the 3 queries fails and it is the error you get.
To force Rails use a JOIN here, you should either use the explicit .eager_load() instead of .includes(), or add a .references() clause.
Other than that, what #Geoff answered stands, you don't really need to .includes() here, but rather a .joins().

Rails Query Issue

I have photos which have_many comments.
I want to select whatever photos have recent comments and display those photos in a kind of "timeline" where the most recently commented photo is at the top and other photos fall below.
I tried this, and it worked on SQLite:
#photos = Photo.select('DISTINCT photos.*').joins(:comments).order('comments.created_at DESC')
However testing on PostgreSQL raises this error:
PGError: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
\n: SELECT DISTINCT photos.* FROM \"photos\" INNER JOIN \"comments\" ON \...
So, the problem is, I'm selecting Photos but ordering by recency of comments... and Postgre doesn't like that.
Can anyone suggest either:
A: How I can fix this query...
or
B: A different way to retrieve photos by the recency of their comments?
The important reason I'm doing it this way instead of through the comments model is I want to show each photo once with any recent comments beside it, not show each comment by itself with the same photos appearing multiple times.
Thanks!
Check out the :touch parameter of of the belongs_to association:
:touch
If true, the associated object will be
touched (the updated_at/on attributes
set to now) when this record is either
saved or destroyed. If you specify a
symbol, that attribute will be updated
with the current time instead of the
updated_at/on attribute.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to
In your Comment model, therefore, you would have:
belongs_to :photo, :touch => :comments_updated_at
Now, in order to create a time line of photos with recently updated comments all you need to do is:
Photo.order('comments_updated_at DESC').all
Just be sure to add the "comments_updated_at" datetime field to your Photo model.
Make sense?
Just for the future readers of this question, the real answer to your SQL issue in SQlite vs Postgresql is that in the SQL "standard", every selected column needs to be in the GROUP BY or be an aggregate function.
https://www.techonthenet.com/sql/group_by.php (or whatever SQL ref you want to take a look at)
Your SQLite query used SELECT * instead of specific columns. That would have blown up with a similar error on most databases like Postgresql (MySQL, Maria, probably MSSQL Server). It's definitely invalid SQL grammar for a lot of good reasons.
Under the hood, I have no clue what SQlite does -- maybe it expands the * into fields and adds them to the GROUP BY under the hood? But its not a good SQL statement which is which it threw the error.

Rails 3 get sql from scope

Is there a way to get the sql for JUST a scope? So I want to do something like:
class Presentation < ActiveRecord::Base
has_many :calls
has_many :recordings, :through => :calls
scope :with_recordings, joins(:calls).joins(:recordings)
end
And then be able to get the sql for that scope.
Presentations.with_recordings.sql returns the entire sql statement, including the SELECT statement. All I want is the sql added by the scope. Figure there ought to be a way to do this.
I agree with ctcherry about this not being very useful, but having said that, I needed to do this for a project I was working on. We needed to duplicate the sql in the scopes to allow us to reuse the sql across different types of searches. Rather that have to maintain the same sql in two different places, I choose to extract the sql from the scope.
The code below is what I came up with. It's ugly, but works under Rails 3.0
def extract_named_scope_clause(scope, args)
# where_clauses will return an array of clauses for an entire relationship.
# As this is only run a single scope, we only ever care about the first.....
clause, *bind_vars = self.send(scope, args).where_clauses.first
# prefix 'and ' to the string, add some spaces and append any bind variables
if clause
[" and #{clause} ", bind_vars]
else
nil
end
end
This wouldn't really make sense, as there is no standard way to represent SQL "fragments".
The different kinds of SQL "fragments" that can be added and manipulated by a scope don't really have a clean way to be represented by themselves without being part of a complete SQL statement. A fragment could be "JOIN users ON users.id = orders.user_id" or it could be "WHERE active = 1". How would you return these without them being part of a complete SQL statement? This is most likely why there is no mechanism to retrieve them other than the one you have already discovered that just returns the complete SQL statement.

SQL UNION with Rails ActiveRecord

I have two models in my app, notes and highlights. They are defined like:
class Note < ActiveRecord::Base
belongs_to :user
end
class Highlight < ActiveRecord::Base
has_and_belongs_to_many :users
end
Now, I want both accessible in the same stream, sorted by creation date. I couldn't wrap my head around how to do this in ActiveRecord, so I put together this query:
SELECT book_id, page, content, created_at FROM `highlights`
INNER JOIN `highlights_users` ON `highlights`.id = `highlights_users`.highlight_id
WHERE (`highlights_users`.user_id = 1 )
UNION SELECT book_id, page, content, created_at FROM notes
ORDER BY created_at DESC
Add of course, it works, but doesn't feel very Rails-y. Plus, I don't know how to tell which items are notes and which are highlights. The other option is to get the note stream and highlight streams separately, then merge the arrays. That also seems clumsy, and I feel like there must an abstraction somewhere for what I'm trying to do.
Is there some key ActiveRecord functionality I'm missing here? What's the most efficient way to go about this?
Single table inheritance is likely the abstraction that you are looking for.
http://ar.rubyonrails.org/classes/ActiveRecord/Base.html
http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
http://code.alexreisner.com/articles/single-table-inheritance-in-rails.html