How do I reference the value of a different attribute in a Rails ActiveRecord Query - ruby-on-rails

I have a model, Lease, with attributes ends_on (date), and notice_days (integer) I would like to write a query that returns all records that have an ends_on date that is less than today's date plus notice_days for that record.
In straight SQL (for my Postgresql db) the query is:
SELECT * FROM leases WHERE ends_on < CURRENT_DATE + notice_days
I can easily write a query to compare against today's date + some fixed buffer
Lease.where(ends_on: ...Date.current + 30).all
But I don't want the fixed buffer, I want a buffer (notice_days) that is unique to each record.
I do realize that I could execute the sql shown above using exec_sql, but I would like to learn how to do this with a straightforward ActiveRecord query.
Edit
Stefanyuk's answer below does in fact answer my question, but I am looking for a way to write the ActiveRecord query with key value pairs as arguments to the where method in the hopes of making the query less database dependent. It seems that this should be possible but I have tried many ways of passing the attribute name without success.
This does not work, but I am looking for something similar that does:
Lease.where(ends_on: ...(Date.current + :notice_days))
Is there a way to replace the symbol :notice_days in that example with a reference to the attribute notice_days?

You can do this by using interval
SELECT * FROM leases WHERE ends_on < CURRENT_DATE + interval '1 day' * notice_days
Or with Rails
Lease.where("ends_on < CURRENT_DATE + interval '1 day' * notice_days")
Or with Arel
Lease.where(Lease.arel_table[:ends_on].lt(Arel.sql("CURRENT_DATE + notice_days")))

Related

ActiveRecord syntax to test for dates in two different columns Rails

Rails 3.23
I have a payments table with the following two columns in it:
created_at
paid_at
I need an ActiveRecord query, to select the rows, where:
paid_at >= created_at + 30.days
I can't figure out the syntax.
The following syntax works:
where = "paid_at > DATE_ADD(created_at, INTERVAL 30 DAY)"
ActiveRecord::Base.connection.exec_query("#{query}")
And from the responses, I understand that the only way to do it, to directly execute a MySQL query. The only problem here, seems that the return object, is not an ActiveRecord object, but a regular array of hashes

Summary Count by Group with ActiveRecord

I have a Rails application that holds user data (in an aptly named user_data object). I want to display a summary table that shows me the count of total users and the count of users who are still active (status = 'Active'), created each month for the past 12 months.
In SQL against my Postgres database, I can get the result I want with the following query (the date I use in there is calculated by the application, so you can ignore that aspect):
SELECT total.creation_month,
total.user_count AS total_count,
active.user_count AS active_count
FROM
(SELECT date_trunc('month',"creationDate") AS creation_month,
COUNT("userId") AS user_count
FROM user_data
WHERE "creationDate" >= to_date('2015 12 21', 'YYYY MM DD')
GROUP BY creation_month) AS total
LEFT JOIN
(SELECT date_trunc('month',"creationDate") AS creation_month,
COUNT("userId") AS user_count
FROM user_data
WHERE "creationDate" >= to_date('2015 12 21', 'YYYY MM DD')
AND status = 'Active'
GROUP BY creation_month) AS active
ON total.creation_month = active.creation_month
ORDER BY creation_month ASC
How do I write this query with ActiveRecord?
I previously had just the total user count grouped by month in my display, but I am struggling with how to add in the additional column of active user counts.
My application is on Ruby 2.1.4 and Rails 4.1.6.
I gave up on trying to do this the ActiveRecord way. Instead I just constructed my query into a string and passed the string into
ActiveRecord::Base.connection.execute(sql_string)
This had the side effect that my result set came out as a array instead of a set of objects. So getting at the values went from a syntax (where user_data is the name assigned to a single record from the result set) like
user_data.total_count
to
user_data['total_count']
But that's a minor issue. Not worth the hassle.

Compare dates from db

I have a view which is exporting some columns, one column is called
'created_at' type:"timestamp without timezone" format: "2014-03-20 12:46:36.590253"
In rails I have a method, which is getting data from the view and is trying to filter the data by a date. I tried rapping created_at into date() but is still not working.
Any ideas?
return ActiveRecord::Base.connection.select_all("
select * from db_functions where date(created_at) >= #{date_p} AND date(created_at) <= #{date_p}")
PG::UndefinedFunction: ERROR: operator does not exist: date >= integer
LINE 2: ...select * from db_functions where date(created_at) >= 2014-03...
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
The first noticeable problem is that your time is not quoted. This is causing the time to be treated as integer. To fix this, you could simply wrap date_p with quotes or use ActiveReocrd::ConnectionAdapters#quote as:
conn = ActiveRecord::Base.connection
return conn.select_all("select * from db_functions
where date(created_at) >= #{conn.quote(date_p)}
AND date(created_at) <= #{conn.quote(date_p)}")
Another option, instead of converting each created_at to date in where clause, you could modify date_p to be beginning of the day value and remove the "date" conversion altogether. Also, instead of using values directly in the query, it's better to use prepared statements (Linked article is a couple of years old, but explains prepared statements clearly with examples).
Then there is also the task of modifying the date time parameter to beginning of day. Given that date_p is a string and not a time, here is what you can do:
date_p = Time.zone.parse("2014-03-20 12:46:36.590253").beginning_of_day.utc
return ActiveRecord::Base.connection.select_all("select * from db_functions
where created_at >= ?
AND created_at <= ?", date_p, date_p)

Get all comments by a set of users made the 24 hours prior to their last comment

I have a many-to-one relationship between the models User and Comment.
I would like to collect, preferably in a hash, all comments made by a set of users the 24 hours prior to each user's last comment (including his/her last comment).
This is what I have come up with, but I dont know how to create the hash with comments only from the time span mentioned.
Comment.order('updated_at desc').where(user_id: array_of_users_ids).group_by(&:user).each do |user, comments|
# rearrange the hash here?
end
The SQL solution for this would be a correlated subquery, made slightly tricky by non-standard date arithmetic depending on the RDBMS in use.
You'd be looking for a query such as:
select ...
from comments
where comments.user_id in (...) and
comments.updated_at >= (
select max(updated_at) - interval '1 day'
from comments c2
where c2.user_id = comments.user_id)
You ought to be able to achieve this with:
Comment.where(user_id: array_of_users_ids).
where("comments.updated_at >= (
select max(updated_at) - interval '1 day'
from comments c2
where c2.user_id = comments.user_id)").
order(... etc

Rails Where Created_at Equals specific number

I'm trying to figure out how to do a query where created_at.year == a given year, and created_at.month equals a given month.
However I can't figure out what I'm doing wrong.
Model.where("'created_at.month' = ? AND 'created_at.year' = ?", 7,2013)
results in nothing being shown.
However when I try Model.first.created_at.month ==7 and
Model.first.created_at.year ==2013 I get true for both.
Therefore theoretically my query should be at least be returning my first record.
Anyone know what I'm doing wrong or any alternative way to find records created on specific months?
Note that in my views the month / year will be parameters but for the purposes of this example I used actual values.
using ruby 1.9.3
rails 3.2.13
You can use the extract SQL function, that will extract the month and year of the timestamp:
Model.where('extract(year from created_at) = ? and extract(month from created_at) = ?', '2013','7')
This query should give you the desired result.
created_at is a timestamp; it is not a set of discrete fields in the database. created_at.year and such don't exist in your DB; it's simply a single timestamp field. When you call #model.created_at.year, Rails is loading the created_at field from the database, and creating a Time object from it, which has a #year method you can call.
What you want is to query on a range of dates:
Model.where("created_at >= ? and created_at < ?", Time.mktime(2013, 7), Time.mktime(2013, 8))
This will find any Model with a created_at timestamp in July 2013.

Resources