How do I specify this `WHERE` condition without using SQL? - ruby-on-rails

I'm using the following raw Postgres SQL in my where() call to find records where some_date is two days from now:
SomeModel.where('date(some_date) - date(NOW()) = 2')
Can it be written using pure ActiveRecord syntax without raw SQL?

This should work, haven't tested it though.
SomeModel.where('date(some_date) = ?', 2.days.ago.to_date)
#or
SomeModel.where('some_date::date = ?', 2.days.ago.to_date)
If you want it to be any purer, without the date sql function, I'm not seeing it, at least not without creating a db view.

Related

How to pass array as bind variable to Rails/ActiveRecord raw SQL queries?

I need to pass an array of ids into my raw sql query like this:
select offers.* from offers where id in (1,2,3,4,5)
The real query includes a lot of joins and aggregation functions and can't be written using Arel expressions or ActiveRecord model methods like Offer.where(id: [...]). I'm looking exactly for how to use bind variables in raw queries.
Instead of interpolating ids into string I want to use bind variables like this (pseudo-code):
ActiveRecord::Base.connection.select_all("select offers.* from offers where id in (:ids)", {ids: [1,2,3,4,5]})
However, I can't find any solution to perform this. From this ticket I've got a comment with related test-case in ActiveRecord code with the following example:
sub = Arel::Nodes::BindParam.new
binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
sql = "select * from topics where id = #{sub.to_sql}"
#connection.exec_query(sql, "SQL", binds)
I've tried this approach, but it didn't worked at all, my "?" was not replaced by actual values.
I'm using Rails 5.1.6 and MariaDB database.
You could do this in a much simpler fashion purely with arel. (Also it makes the code far more maintainable than SQL strings)
offers = Arel::Table.new('offers')
ids = [1,2,3,4,5]
query = offers.project(Arel.star).where(offers[:id].in(ids))
ActiveRecord::Base.connection.exec_query(query.to_sql)
This will result in the following SQL
SELECT
[offers].*
FROM
[offers]
WHERE
[offers].[id] IN (1,2,3,4,5)
When executed you will receive an ActiveRecord::Result object with is usually easiest to deal with by calling to_hash and each resulting row will be turned into a Hash of {column_name => value}
However if you are using rails and Offer is a true model then:
Offer.where(id: ids)
Will result in the same query and will return an ActiveRecord::Relation collection of Offer objects which is generally more preferable.
Update
Seems like you need to enable prepared_statements in mysql2 (mariadb) in order to use the bind params, which can be done like this:
default: &default
adapter: mysql2
encoding: utf8
prepared_statements: true # <- here we go!
Please note the following pieces of code:
https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L115
https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L40
https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L630
https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb#L30
As you can see in the last code exec_query ignores bind_params if prepared_statements is turned off (which appears to be the default for the mysql2 adapter).

Combining queries into an ActiveRecord_AssociationRelation

Hi I'm working on a project and I need to take result of two database queries and combine them into one ActiveRecord_AssociationRelation, at the moment I have:
results.where(pos_or_neg: "neg").order("value DESC") + (results.where(pos_or_neg: "pos").order("value ASC"))
However this returns an array which doesn't work as I need to do more processing afterwards. I've tried:
results.where(pos_or_neg: "neg").order("value DESC").merge(results.where(pos_or_neg: "pos").order("value ASC"))
but this only seems to return the half of the results.
Thanks
results.order("pos_or_neg ASC,case when pos_or_neg="neg" then value else -1*value end DESC")
I believe using merge is the equivalent of an AND query in SQL. What you are looking for is an OR query.
Since Rails 5 this is one of the Active Record query methods that you can use!
http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-or
Try to replace your .merge with .or and see if that works better

tricky union query using ruby on rails/active record

I have
a = Profile.last
a.mailbox.inbox
a.mailbox.sentbox
active_conversations = [IDS OF ACTIVE CONVERSATIONS]
a.mailbox.inbox & active_conversations
returns part of what I need
I want
(a.mailbox.inbox & active_conversations) AND a.mailbox.sentbox
but I need it as SQL, so that I can order it efficiently. I want to order it by ('updated_at')
I have tried joins and other things but they don't work. The classes of (a.mailbox.inboxa and the sentbox are
ActiveRecord::Relation::ActiveRecord_Relation_Conversation
but
(a.mailbox.inbox & active_conversations)
is an array
edit
Something as simple as a.mailbox.inbox JOINS SOMEHOW a.mailbox.sentbox I should be able to work with, but I also can't seem to figure out.
Instead of doing
(a.mailbox.inbox & active_conversations)
you should be able to do
a.mailbox.inbux.where('conversations.id IN (?)', active_conversations)
I believe the Conversation class (and its underlying conversations table) should be right according to the mailboxer code.
However this gives you an ActiveRelation object instead of an array. You can transform this to pure SQL using to_sql. So I think something like this should work:
# get the SQL of both statements
inbox_sql = a.mailbox.inbux.where('conversations.id IN (?)', active_conversations).to_sql
sentbox_sql = a.mailbox.sentbox.to_sql
# use both statements in a UNION SQL statement issued on the Conversation class
Conversation.from("#{inbox_sql} UNION #{sentbox_sql} ORDER BY id AS conversations")

Rails 4, raw query using ActiveRecord

Is there currently a way to do a raw SQL select query using ActiveRecord in Rails 4.0.0.beta1? I see ActiveRecord::Base.execute no longer exists. What's the correct way of going about this?
Here try this, select example.. :
query = "select ...."
results = ActiveRecord::Base.connection.execute(query)
Just to add my ten pence, a raw query using Model.connection.execute won't return an ActiveRecord model - it'll return a raw data set.
The following will return ActiveRecord models:
MyModel.find_by_sql(query)
edit: assuming of course that you're running a select.
In Rails 4 (perhaps previous versions as well), if you're going with a custom query for speed, you can add a :skip_logging argument to avoid writing to the log:
query = "SELECT ..."
results = MyModel.connection.execute(query, :skip_logging)
(Note: If I'm reading the sources correctly, this might not hold true in PostgreSQL.)

Ruby On Rails Querying From a model but returning another tables fields

active_courses_past_week = CourseEnrollment.select("courses.*").
joins(:course).
where("date(course_enrollments.created_at) BETWEEN ? and ?", Date.parse(start_date.strftime('%Y-%m-%d')), Date.parse(end_date.strftime('%Y-%m-%d'))).
group("courses.id")
The above query seems odd because I am querying from course enrollments, but only care about the course data where they are enrolled between two dates. It just seems weird because I am not using any of the fields in the CourseErnollment model. Any suggestions?
This is how I would write it using a range instead of "raw" SQL.
active_courses_past_week = CourseEnrollment.where(:created_at => start_date..end_date))
.joins(:course)
.group(courses.id)

Resources