How to get records created at the current month? - ruby-on-rails

I have a candidate which has_many votes.
I am trying to get the votes of a candidate that were created in the current month?
#candidate.votes.from_this_month
scope :from_this_month, where("created_at > ? AND created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)
That gives me a PG error:
PG::Error: ERROR: column reference \"created_at\" is ambiguous
If I try
scope :from_this_month, where("vote.created_at > ? AND vote.created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)
I get the following error
PG::Error: ERROR: missing FROM-clause entry for table "vote"

Correct scope
scope :from_this_month, lambda {where("votes.created_at > ? AND votes.created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)}
This is because in rails the model names are singular(i.e Vote) and tables created are pural (e.g. votes) by convection
EDIT
This can be written simpler with lambda {where(created_at: Time.now.beginning_of_month..(Time.now.end_of_month))} and we need to use lambda due to the reason given in below comments.
Thanx BroiSatse for reminding :D

You need to enclose the where in a lamda as well.
scope :from_this_month, lambda { where("votes.created_at > ? AND votes.created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month) }
Otherwise it may appear to work and your tests will all pass, but if your app runs for more than a month you will start to get incorrect results because Time.now is evaluated when the class loads, not when the method is called.

From Rails 5 you have a much cleaner syntax. On your votes model add a scope this_month
scope :this_month, -> { where(created_at: Date.today.all_month) }
You can now query #candidates.votes.this_month

scope :this_month, -> { where(created_at: Time.zone.now.beginning_of_month..Time.zone.now.end_of_month) }
and you can call the scope:
Model.this_month

You could use an ActiveRecord Association Extension:
#app/models/Candidate.rb
Class Candidate < ActiveRecord::Base
has_many :votes do
def from_this_month
where("created_at > ? AND created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)
end
end
end
This should allow you to call #candidate.votes.from_this_month to return the required conditional data

Related

rails joins query over has_many objects independently

I have parent model Project & children model ToDo.
A project has many ToDos with column starts_at & ends_at.
I would like to search for projects that have 'any' todos in the time range.
Here I wrote some codes, however, it is not as expected.
class Project
has_many :todos
scope :active, -> {joins(:todos).where("todos.starts_at < '#{Time.now}' AND todos.ends_at > '#{Time.now}'").distinct}
scope :waiting, -> {joins(:todos).where.not("todos.starts_at < '#{Time.now}' AND todos.ends_at > '#{Time.now}'").distinct}
scope :done, -> {where("project_due > ?", Time.now)}
end
Active one seems right, but waiting scope also contains the projects that have more than one todos.
I wonder if there any solution to compare starts_at & ends_at per each todo. Not like above.
Thanks ahead.
*# Update *
This is what I wanna achieve. but in one query.
scope :waiting, -> { joins(:todos).where.not(id: active.ids).where('finishing > ?', Time.now).distinct }
Try to the following
Update
For waiting you mean starts_at greater than NOW right? then it will be
scope :waiting, -> {joins(:todos).where("todos.starts_at >= ?", Time.now).distinct}
If the match with the first condition then you don't need to match the second condition, you can write with the second condition like this
scope :waiting, -> {joins(:todos).where("todos.starts_at > '#{Time.now}' AND todos.ends_at > '#{Time.now}'").distinct}
but don't need.
Update 2
Remove the not from where, here not means active
scope :waiting, -> {joins(:todos).where("todos.starts_at >= ?", Time.now).distinct}
Update 3 after Update 2 worked
scope :waiting, -> {joins(:todos).where("todos.starts_at >= ?", Time.now).distinct}
scope :finished, -> {where("finishing > ?", Time.now).distinct}
scope :waiting_n_progress, -> {where.not(id: active.ids).finished.waiting}
the waiting_n_progress scope, I think you will get your goal, remember that's not tested.
Hope it should work.
Your query for waiting basically translate from !(start < now && end > now) to this start >= now || end <= now which would most likely return a lot more projects than you wanted. See if that is the logic that you wanted.
Also, for best Rails practices, you should write query like the user fool-dev suggested in the other answer using question mark. This is to prevent SQL injection as explained in more details here
EDIT
So I think you mean waiting means projects that does not have any Todo or those that have awaiting Todo (start_at > now). I think this should work:
scope(:waiting) { includes(:todos).where('todos.project_id IS NULL OR todos.start_at > ?', Time.now).distinct }
The first part is to select projects that doesn't have any Todo and the second part is self explanatory.

Rails 4 ActiveRecord::first not working

I'm debugging some legacy code in a Rails 4.1 app and I'm seeing some confusing results from this:
#order.rb
# get the most recent order from the same year we are in
scope :last_from_this_year, -> { where("created_at >= ?", Time.mktime(Time.now.year, 1)).where("created_at < ?", Time.mktime(Time.now.year, 12, 31)).order('payment_id DESC').first }
# orders_controller.rb
prev_payment = Order.last_from_this_year
For reasons I cannot explain, this scope is returning an ActiveRecord_Relation containing ALL the order records, despite the fact that it's calling the .first method. I'm expecting first to return a single ActiveRecord object.
Why is this happening?
Update
When I run this in the console, it works as expected:
o = Order.where("created_at >= ?", Time.mktime(Time.now.year, 1)).where("created_at < ?", Time.mktime(Time.now.year, 12, 31)).order('payment_id DESC').first
I literally copy/pasted exactly what's in the scope and it works. So it's confusing why it doesn't behave as expected as a scope.
$ Order.last_from_this_year.to_sql
=> SELECT "orders".* FROM "orders" WHERE (created_at >= '2017-01-01 08:00:00.000000') AND (created_at < '2017-12-31 08:00:00.000000') ORDER BY payment_id DESC LIMIT 1
Seems right... very strange.
The concept behind that scope is wrong. The idea of a scope is to always return an instance of ActiveRecord::Relation (a collection of objects rather than one) to allow further chaining with other methods such as where, includes, joins, etc.
If you need to retrieve just one object from that scope, you need to remove .first and use it as: Order.last_from_this_year.first.
Other solution is to move that code to a class method:
def self.last_from_this_year
where("created_at >= ?", Time.mktime(Time.now.year, 1))
.where("created_at < ?", Time.mktime(Time.now.year, 12, 31))
.order('payment_id DESC')
.first
end
Then Order.last_from_this_year should work as it does on the console.

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')

has_many and No method error issue

I have two tables: Stores and products. The store model has a has_many :products and the products has a belongs_to :store
I'm trying to do this though in the rails console:
Store.where(open: true).products.where("created_at <= ?", 1.month.ago)
and get the error (paraphrased a bit): NoMethodError: undefined method products for #<Store
Not a very easy thing to do - products is a method defined on an instance of Store and you are calling it on the relation. I would probably go with:
Product.where(store_id: Store.where(open:true).pluck(:id)).where("created_at <= ?", 1.month.ago)
which would generate two db calls, but also returns a clean and easy to query scope. Another approach would be to use join:
Product.joins(:store).where(store: { open: true }).where("created_at <= ?", 1.month.ago)
This will do the work with one query, but due to the join it won't be that easy to manipulate the resulting scope that easily.
You have it backwards. Since you can have many stores, Rails will not return all the products where open: true.
You need to join and lookup the products where the store is open.
Product.joins(:store).where(store: {open: true}).where("created_at <= ?", 1.month.ago)

Scope in where clause

You can see the last lines of these two models use the same code:
class Position < ActiveRecord::Base
scope :last_day,
where('positions.created_at > ? AND positions.hidden = ?',
DateTime.now.in_time_zone("Sofia").yesterday.beginning_of_day + 10.hours, false)
end
class Subscriber < ActiveRecord::Base
scope :notify_today,
joins(:tags => :positions).
where('positions.created_at > subscribers.created_at
AND positions.created_at > ? AND positions.hidden = ?',
DateTime.now.in_time_zone("Sofia").yesterday.beginning_of_day + 10.hours, false)
end
Is it possible to reuse the 'last_day' scope somehow in the second model?
where() and joins() both return ActiveRecord::Relation objects, and they have a method called merge() which can merge other ActiveRecord::Relation objects. Therefore, you can simply do this
scope :notify_today, joins(:tags => :positions).merge(Position.last_day)
Also, & is an alias to merge(), so you should also be able to do this
scope :notify_today, joins(:tags => :positions) & Position.last_day

Resources