how do I write SQL in a ruby method? - sql

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

Related

How to write an SQL NOT EXISTS query/scope in the Rails way?

I have a database scope to filter only latest ProxyConfig version for particular Proxy and environment.
This is the raw SQL that works very well with MySQL, PostgreSQL and Oracle:
class ProxyConfig < ApplicationRecord
...
scope :current_versions, -> do
where %(NOT EXISTS (
SELECT 1 FROM proxy_configs pc
WHERE proxy_configs.environment = environment
AND proxy_configs.proxy_id = proxy_id
AND proxy_configs.version < version
))
end
...
end
You can find a simple test case in my baby_squeel issue.
But I find it nicer not to use SQL directly. I have spent a lot of time trying out different approaches to write it in the Rails way to no avail. I found generic Rails and baby_squeel examples but they always involved different tables.
PS The previous version used joins but it was super slow and it messed up some queries. For example #count produced an SQL syntax error. So I'm not very open on using other approaches. Rather I prefer to know how to implement this query exactly. Although I'm at least curious to see other simple solutions.
PPS About the question that direct SQL is fine. In this case, mostly yes. Maybe all RDBMS can understand this quoting. If one needs to compare text fields though that requires special functions on Oracle. On Postgres the case-insensitive LIKE is ILIKE. It can be handled automatically by Arel. In raw SQL it would require different string for the different RDBMS.
This isn't actually a query that you can build with the ActiveRecord Query Interface alone. It can be done with a light sprinkling of Arel though:
class ProxyConfig < ApplicationRecord
def self.current_versions
pc = arel_table.alias("pc")
where(
unscoped.select(1)
.where(pc[:environment].eq(arel_table[:environment]))
.where(pc[:proxy_id].eq(arel_table[:proxy_id]))
.where(pc[:version].gt(arel_table[:version]))
.from(pc)
.arel.exists.not
)
end
end
The generated SQL isn't identical but I think it should be functionally equivilent.
SELECT "proxy_configs".* FROM "proxy_configs"
WHERE NOT (
EXISTS (
SELECT 1 FROM "proxy_configs" "pc"
WHERE "pc"."environment" = "proxy_configs"."environment"
AND "pc"."proxy_id" = "proxy_configs"."proxy_id"
AND "pc"."version" > "proxy_configs"."version"
)
)

Pure SQL queries in Rails view?

I am asked to display some sort of data in my Rails App view with pure SQL query without help of ActiveRecord. This is done for the Application owner to be able to implement some third-party reporting tool (Pentaho or something).
I am bad in SQL and I am not really sure if that is even possible to do something similar. Does anyone have any suggestions?
If you must drop down to pure SQL, you can make life more pleasant by using 'find by sql':
bundy = MyModel.find_by_sql("SELECT my_models.id as id, my_articles.title as title from my_models, my_articles WHERE foo = 3 AND ... ...")
or similar. This will give you familiar objects which you can access using dot notation as you'd expect. The elements in the SELECT clause are available, as long as you use 'as' with compound parameters to give them a handle:
puts bundy.id
puts bundy.title
To find out what all the results are for a row, you can use 'attributes':
bundy.first.attributes
Also you can construct your queries using ActiveRecord, then call 'to_sql' to give you the raw SQL you're using.
sql = MyModel.joins(:my_article).where(id: [1,2,3,4,5]).to_sql
for example.
Then you could call:
MyModel.find_by_sql(sql)
Better, though, may be to just use ActiveRecord, then pass the result of 'to_sql' into whatever the reporting tool is that you need to use with it. Then your code maintains its portability and maintainability.

sql count filtering - rails way

Suppose I have Posts and posts' Comments. I want to filter all the Posts that have more than 10 comments. I began writing something like Posts.includes(:comments).group("post.id").count("comments.id"), to obtain a hash of posts and their counts, and I can extract the information from there, but I want some one-line straightforward way to do that
Sure I can use some pure sql syntax statements, but I want it in a pure rails way. Any idea ?
Assuming the models are named in the more typical singular form of Post and Comment and have the usual association relationship, then the following should work:
Post.joins(:comments).group('posts.id').having('count(comments.id) > 10')

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.

not on a query in RoR

In Ruby on rails 3 I want to query on a has_many field of a model as follows:
#project.items.where(:status => 1)
The problem is I'm trying to get the exact opposite result than this. What i want is all items of #project where the status is not 1. Been looking for the answer to this for a while, anyone?
There are many ways to accomplish what you are trying to do, however, some are better than others. If you will always be searching for a hardcoded number (i.e. 1 in this case), then the following solution will work:
#project.items.where('status != 1')
However, if this value is not hard-coded, you are openly vulnerable to SQL injection as Rails will not (cannot) escape this kind of query. As a result, it is preferred among Rails developers to user the following syntax for most custom conditions (those that can't be constructed via Hash):
#project.items.where(['status != ?', 1])
This syntax is slightly confusing, so let me go over it. Basically you are providing the where clause an Array of values. The first value in the array is a String representing the query you want executed. Anywhere you want a value in that string, you place a ?. This serves as a placeholder. Next, you add an element for every question mark in you query. For example, if I had the following:
where(['first_name = ? AND last_name = ?', params[:first_name], params[:last_name]]
Rails will automatically match these up forming the query for you. In that process, it also escapes potentially unsafe characters, preventing injection.
In general, it is preferred to use the Array syntax, even for a hardcoded value. I've been told that pure string conditions in Rails 3.5 will raise a warning (unverified), so it doesn't hurt to get in the process of using the Array syntax now.