RAILS : how to define scope with date range from column values? - ruby-on-rails

I am trying to create a named scope in which I would like to receive records which are only updated within the first week after they are created.
As you can see I receive an error message for the column name created_at.
scope :shortlived, where("updated_at < ?", (created_at+7.days))
undefined local variable or method `created_at' for #<Class:0xb184725c>
I will appreciate if you can inform me what might be the problem.

You could use something database specific. For example for MySQL:
scope :shortlived, -> { where('updated_at < created_at + interval 3 day') }

this is similar to the answers posted here. if you're using postgreSQL, you can just compare the difference with '7 days' interval
scope :shortlived, -> { where("(updated_at - created_at) < '7 days'") }

Related

Constructing Query with Rails - GROUP, MAXIMUM, UNION

I have two models:
Student
-> has_many :enrollments
Enrollment
-> belongs_to :student
The tables goes something like this:
https://i.stack.imgur.com/ilIxL.png
Now my goal is to retrieve those students which latest enrollment end date is lesser than the current date, or if the student has no enrollment yet. So given the sample table on the link above, and current date is 11/20/2019, the query should return:
John Doe (because latest end date is 11/01/2019 which is lesser than 11/20/2019)
Casper Gost (because this student has no enrollment record yet)
How do I create that query in Ruby on Rails? (Sorry, I'm still noob in rails).
I visualize the sql query is this:
select students.*
from students s
left join
( select student_id, end_date
from enrollments e1
where end_date = (select max(e2.end_date)
from enrollments e2
group by e2.student_id
having e2.student_id = e1.student_id
)
) e on e.student_id = s.id
where e.end_date < Date.today OR e.student_id IS NULL
but I can't seem to build it using Rails ActiveRecord's methods.
EDIT
I tried Roc khalil's/Catmal's solution, but the "or" method is giving an incompatible error:
(Relation passed to #or must be structurally compatible. Incompatible
values: [:includes])
Also, I revised the code a little bit to fit my needs:
This will retrieve all students that have no records in enrollments table
Student.where.not(id: Enrollment.pluck(:student_id))
And this code will retrieve all students whose last enrollment's end date is lesser than today
Student.joins(:enrollments).where('end_date IN (?)',Enrollment.all.group(:student_id).maximum(:end_date).values).where('end_date < ?', Date.today) When I executed them separately, I get no error. However if I combine them with the "or" method I get the same incompatible error:
ArgumentError (Relation passed to #or must be structurally compatible.
Incompatible values: [:joins])
I've searched it and it looks like it's a bug. :(
So close though :(
UPDATE
I have implemented both #nathanvda and #Catmal suggestion, and came up with this:
Student model:
scope :no_enrollments, -> { where.not(id: Enrollment.pluck(:student_id)) }
scope :with_expired_enrollments, -> { joins(:enrollments).merge(Enrollment.expired_enrollments) }
scope :unenrolled, -> { find_by_sql("#{no_enrollments.to_sql} UNION #{with_expired_enrollments.to_sql}") }
Enrollment model:
scope :expired_enrollments, -> { where('end_date IN (?)',Enrollment.all.group(:student_id).maximum(:end_date).values).where('end_date < ?', Date.today) }
So in my controller, to get the unenrolled students, I just have to call:
Student.unenrolled
Maybe not the optimum solution, but it works on my app. Thanks #nathanvda and #Catmal.
Try this:
#selected_students = Student.where.not(
id: Enrollment.pluck(:student_id)
).or(
Student.includes(:enrollments).where(
enrollments: {
date_end < Date.today
}
)
)

Rails scope filter by date range

There are many questions relate to rails date range problem but mine is a little more complicated.
I have two models: house and booking. A House has_many bookings. A Booking has two attributes in date format: check_in and check_out.
What I want to achieve: Giving a valid date range, show all houses that are available during this range. In detail:
The start date of the range should not be in any booking.
The end date of the range should not be in any booking.
There should not be any booking between the start and the end.
Can this be done using the rails scope?
UPDATE:
I found the code below that can check scope date interval that overlaps.
named_scope :overlapping, lambda { |interval| {
:conditions => ["id <> ? AND (DATEDIFF(start_date, ?) * DATEDIFF(?, end_date)) >= 0", interval.id, interval.end_date, interval.start_date]
}}
How can I transfer this to my problem?
scope :overlapping, (lambda do |start_date, end_date|
House.includes(:bookings).where("bookings.check_in < ? AND bookings.check_out > ?",
start_date, end_date).references(:bookings).uniq
end)
I went ahead and deleted the >= and <= operators in favor of > and < to explicitly show these bookings being outside of the given range, but you can adjust them per your needs!
Update
Changed query to use #includes instead of #joins, since we're querying the attached table.
Yes it is possible to have this query through scope. Put this scope in house model.
scope :overlapping, -> (start_date, end_date) {
includes(:bookings).where('bookings.check_in < ? AND bookings.check_out > ?',
start_date.to_date, end_date.to_date)
}
And call as House.overlapping('2015-07-01', '2015-07-09')

Finding records made 1 month ago

I'm using a named scope to try and find records made 1 month ago, here's the code I have:
scope :six, -> {:conditions["records.created_at > ?", 1.month.ago]}
And the call:
#sixmonths = human.human_logins.six
Though I seem to be getting the following error:
`can't convert ActiveSupport::TimeWithZone into Intege`r
This is occurring on the scope line.
I'm new to scopes so not sure how I should go about this, any ideas would be awesome.
I am not familiar with :conditions method, but the syntax, :conditions[] and the exception suggests that it is trying to evaluate the expression as an Array.
If you want only records created exactly 1 month ago, use the following:
scope :six, -> { where(created_at: 1.month.ago) }
If you want records created more than a month ago (which your query syntax suggests), use:
scope :six, -> { where('created_at < ?', 1.month.ago) }

Ambiguous column name in Ruby on Rails with SQLite database?

I am getting this error in my Rails app:
ActiveRecord::StatementInvalid in PaymentsController#index
SQLite3::SQLException: ambiguous column name: date: SELECT COUNT(*) FROM "payments" INNER JOIN "invoices" ON "payments"."invoice_id" = "invoices"."id" WHERE "invoices"."user_id" = 1 AND (date >= '2013-01-01' and date <= '2013-12-31')
The problem seems to be that I have a date field in my invoices as well as my payments table.
Yet I still don't know how to fix this error.
class User < ActiveRecord::Base
def number_of_payments_in(year)
payments.where("payments.date >= ? and payments.date <= ?", "#{year}-01-01", "#{year}-12-31").count
end
end
class Payment < ActiveRecord::Base
def self.search(year)
if year
where("date >= ? and date <= ?", "#{year}-01-01", "#{year}-12-31")
end
end
end
Can anybody help?
This answer may be a bit vague as it's not clear from your question which class the self.search(year) method is in, but let me know if this doesn't help and we can try and go further.
My guess is that, unlike in the number_of_payments_in method, in the self.search method you haven't specified an appropriate table in the where call.
In number_of_payments_in, you specify payments.date, but in self.search you just use date. You said that you have a date field in your invoices as well as your payments table, so a join call across both tables will need every reference to date to be scoped by table.
Adding the appropriate table in front of date in self.search (as you have done in number_of_payments_in) may solve the problem.

Get records where date.year == some_year in Rails

I have a date column in my database in a Rails 3.1 app, and I want to be able to get the records where the date's year matches a specific year.
I tried where(:date.year == year) but of course I got NoMethodError: undefined method 'year' for :date:Symbol. Is it possible to do this type of query?
You can use a scope to build something like:
scope :for_year, lambda {|date| where("date >= ? and date <= ?", "#{date.year}0101", "#{date.year}1231")}
In your Model:
scope :by_year, lambda { |year| where('extract(year from created_at) = ?', year) }
In your Controller:
#courses = Course.by_year(params[:year])
Jesse gave you, I think, the idea for the actual solution, but to explain why this failed - it's because it tried to evaluate ".year" as a method on the symbol you passed it: ":date".
The word :date is just a parameter to tell "where" which value it will later use to construct the SQL query to pass to the db.
It doesn't turn into the actual date of the record. But the ".year" will evaluate as you're passing it as a parameter, before anything has been done with the ":date" symbol.
Assuming your date format is: YYYY-MM-DD HH:MM:SS
Try this:
where(Date.strptime(:date, "%Y-%m-%d %H:%M:%S").year == year)
OR
where(["YEAR(?) = ?", :date, year])

Resources