ActiveRecord: find_by_sql and includes - sql

I would like to use find_by_sql and includes at the same time.
I use find_by_sql because I write select inside from to utilize index.
Somehow, index is ignored if I use left join.
But, find_by_sql does not return ActiveRecord_Relation but returns Array objects, so I cannot write
like Model.find_by_sql("select * from (select * from table limit 10)table left join rel_table on ...").includes(:rel_table,...) .
I can run two queries and hand-includes after that.
Is there any way to solve it as one SQL?

You can't call includes on the resulting array, but you can call the Rails preloader manually to do the same thing.
See ARel mimic includes with find_by_sql.
array = Model.find_by_sql("select * from ...")
# Rails 3 and 4.0.x
ActiveRecord::Associations::Preloader.new(array, [:rel_table]).run
# Rails 4.1+
ActiveRecord::Associations::Preloader.new.preload(array, [:rel_table])

Related

Ruby on Rails: Multiple conditions in rails based in different fields using like

I have a entity in my database that have multiple fields.
I need search in this entity based if a certain input it's contained in certain fields, by example
Entity Car
model_name
owner
I want search like this (a pseudo sql idea)
Select * FROM Car WHERE (LIKE model_name = "%maz%" OR LIKE owner = "%maz%")
I've tried with the find method but it's not work
searched_string = "%maz%"
Car.find(:all, :conditions => {model_name: searched_string, owner: searched_string})
How to achieve this?
When using the conditions option in an ActiveRecord query, the resulting SQL will be generated for an exact match. Additionally, each condition you add will be joined using an AND, not an OR.
That being said, the SQL generated by your query above likely looks like this:
SELECT * FROM Car WHERE (model_name = "%maz%" AND owner = "%maz%")
To get the query you are looking for, you'll have to hand-write the SQL for the WHERE clause.
Car.where("model_name ILIKE :q OR owner ILIKE :q", q: "%maz%")
NOTE: I'm using the more modern ActiveRecord syntax above.
Some good news is that Rails 5 will include support for "or" queries.

How to parse a SQL string into rails Arel object

After writing some large and complex SQL statements I asked sometime ago how could I do it in a better way with Rails. However I ended up using Arel again, because I couldn't nest more conditions if I were calling "find_by_sql".
Questions.find_by_sql(HUGE_SQL).by_filter(:popular).order('created_at').limit(5)
My goals are:
Organize sqls in separate files
Convert those string sqls into Arel objects
Nest this arel object with common reusable conditions like "limit, order, .."
Thanks
The solution is to use the magical scuttle.io to convert complex SQL queries to Arel. Then, you can create separate methods for each part of your query, and still be able to combine them together. More detail is in this presentation.
I needed something like this so that I could take a filtered scope from a web dashboard and pass it to a background task to generate a CSV export using the same scope. Here's what I came up with:
def sql_to_scope(model_class, sql)
model_class.from("(#{sql}) as \"#{model_class.table_name}\"")
end
Given a Customer model:
sql_to_scope(Customer, Customer.where('id % 2 = 0').to_sql).find_each { |c| puts c.id }
# 2
# 4
# 6
sql_to_scope(Customer, "select * from customers where id % 2 = 0").pluck(:id)
# => [2, 6, 4]
It works be replacing the default FROM clause with a subquery containing the original SQL. I wouldn't be surprised if it fell apart in more complex cases but it works for my simple needs.

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

find_by_sql renders an array

I got some problems here, I can't make my find_by_sql request to render an ActiveRecord relation. Indeed, I need an activerecord relation to make a new request:
#searches = #searches.find_by_sql('SELECT *, COUNT( follower_id ) FROM follows GROUP BY followable_id LIMIT 0 , 3') if params[:only_famous_projects]
#project_pages = #project_pages.where(:project_id => #searches.pluck(:'followable.id')) if params[:only_famous_projects]
I can't use "pluck" without an activerecord relation. Therefore, I think I have to convert my sql request to an Activerecord request. However, as soon as I use "count" on ActiveRecord, I have an huge problem: I don't have on the end an ActiveRecord relation, but a FixNum!
I don't know where to find the answer anymore, I would be really gratefull if you could help me.
Thanks
find_by_sql will return an ActiveRecord object only if you call it with YourModel.find_by_sql.
Why not use the ActiveRecord query interface. It does a good job with calculations.
UPDATED
#searches = #searches.group(:followable_id).limit(3).offset(0).count(:follower_id) if params[:only_famous_projects]
Notice that it will give you a Hash containing the count for each followable_id.
Isn't LIMIT 0, 3 equivalent to LIMIT 3 ?
COUNT will always return a FixNUM, because you asked the database to count the number of rows.
You should really try to use find_by_sql as a last resort as it is only meant to bypass ActiveRecord for things that it can not do. And even for things that ActiveRecord doesn't support, you can always see if you can use the Squeel or Valium gems to handle edge-cases.
Another reason not to use find_by_sql is that, for example, using MySQL specific terms will lock you out of using other databases in the future.

how do I write SQL in a ruby method?

I would like to have a method called feed in my User model that returns all the entries from two tables (discussions, exchanges).
In User.rb
def feed
SELECT * FROM discussions, exchanges GROUP BY created_at
end
This doesn't work, i get a problem in rails console
syntax error, unexpected ';', expecting '='
Can anyone show me how to write SQL in here? Basically I want to return and sort entries from two different tables..
if you want actual ActiveRecord objects you can try the following
def feed
exchanges = Exchange.all
discussions = Discussion.all
(exchanges + discussions).sort! { |a, b| a.created_at <=> b.created_at }
end
this is quite ineffective, as the sorting could be done in sql, but ActiveRecord cannot instantiate records selected from different tables (you can somehow override this by using STI)
Firstly - you can't just write plain SQL in your ruby code and expect it to work.
It's ruby, not SQL. They are different languages.
If you can - use the ruby-way with associations instead (as per the other example).
However - if you desperately need to use raw SQL (eg you have legavy tables that don't match to models or have some complex combination-logic in teh SQL that doesn't easily map to assocations); then you need to pass SQL to the database... which means using a connection via Active Record.
Try:
def feed
ActiveRecord::Base.connection.execute("SELECT * FROM discussions, exchanges GROUP BY created_at")
end
It will not return ruby models for you - just a raw results-object.
I'd recommend trying this in script/console and then doing a "puts my_user.feed.inspect" to have a look at the kind of thing it returns so you know how to use it.
Note: the presence of this kind of thing is considered a strong code smell - only use it where you really need it