Rails 4 ActiveRecord::first not working - ruby-on-rails

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.

Related

How to get records created at the current month?

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

Querying a model based on its association using a class method in Rails

I am trying to create an ActiveRecord query in Rails using multiple class methods corresponding to multiple associated models. The code I am using is as follows
#cohort.rb
def self.cohort_within_times (from_date, to_date)
where("start_date >= ? AND end_date <= ?", from_date, to_date)
end
#enrollment.rb
def self.enrollment_within_times (from_date, to_date)
joins(:cohort) & Cohort.cohort_within_times(from_date, to_date)
end
Cohort has many Enrollments.
When I call Cohort.cohort_within_times(<valid dates>) I get a valid response. However, when I call Enrollments.enrollments_within_times(<same valid dates>) I get an empty array as a response. Full output below:
Enrollment.enrollment_within_times("Jan 1st 2013".to_date, "May 31st 2014".to_date)
Enrollment Load (0.3ms) SELECT "enrollments".* FROM "enrollments" INNER JOIN "cohorts" ON "cohorts"."id" = "enrollments"."cohort_id"
Cohort Load (0.3ms) SELECT "cohorts".* FROM "cohorts" WHERE (start_date >= '2013-01-01' AND end_date <= '2014-05-31')
=> []
How can I get the class method on Enrollment to return the same objects as the Cohort class method?
This should work as you expect:
def self.enrollment_within_times(from_date, to_date)
where(cohort_id: Cohort.cohort_within_times(from_date, to_date).map(&:id))
end
or, you could use joins method:
def self.enrollment_within_times(from_date, to_date)
joins(:cohort).where('cohorts.start_date >= ? AND cohorts.end_date <= ?', from_date, to_date)
end

Dates and scopes are inconsistent

I have the following scope:
scope :this_month, :conditions => ["created_at >= ?", Date.today.beginning_of_month]
Which makes the SQL output of a.response_sets.this_month.to_sql:
SELECT "response_sets".* FROM "response_sets" WHERE created_at >= '2012-05-01'
But since today is actually June 1, that date seems wrong. So, I tried bypassing the scope and just doing a condition directly, like so:
a.response_sets.where(["created_at >= ?", Date.today.beginning_of_month]).to_sql
Which then, outputs:
SELECT "response_sets".* FROM "response_sets" WHERE created_at >= '2012-06-01'
Which is correct. So why is there a difference between doing Date.today.beginning_of_month in a scope and doing it directly in where?
When working with dates in scopes you should use a lambda so the scope gets evaluated every time it is called:
scope :this_month, -> { where("created_at >= ?", Date.today.beginning_of_month) }

Find and display nearest date in RoR

I am new to ruby on rails and I'm not sure where to start with this. I have a model for users, and one for projects. Users have many projects, and projects have one user. There is an end_date column in the projects table (as well as a name column).
What I want to do is find the project with the nearest end_date and display it's name and end date on the user's show page.
I tried putting this code in the projects controller, but I do not know if it is working, because I don't know how to access it and display the project name in the view.
def next_deadline(after = DateTime.now, limit = 1)
find(:all, :conditions => ['end_date > ?', after], :limit => limit)
end
Any help would be appreciated. Let me know if more information is needed.
As #Dan mentioned, you do need the :order clause to get the first one, but you should add it to your query and not replace the :conditions (otherwise you'll get the project with the earliest end_date irrespective of your after argument). The way you're defining this method is a bit off though. It should be defined in your Project model (and definitely not the controller) as a class method, or, what I think is a better approach, as a scope. In Rails < 3 (which it seems that you're using):
class Project < ActiveRecord::Base
named_scope :next_deadline, Proc.new { |after = DateTime.now, limit = 1| {:conditions => ['end_date > ?', after], :order => "end_date ASC", :limit => limit} }
...
end
Or in Rails >= 3:
class Project < ActiveRecord::Base
scope :next_deadline, Proc.new { |after = DateTime.now, limit = 1| where('end_date > ?', after).order("end_date ASC").limit(limit) }
...
end
Also, you can always test this kind of code using the Rails console: script/console in Rails < 3, rails c in Rails >= 3.
#projects = Project.find_by_sql("SELECT projects.* FROM projects
JOIN users ON users.id = projects.user_id AND projects.user_id = " + #user.id.to_s + "
WHERE projects.end_date > now()
ORDER BY projects.end_date ASC
LIMIT " + limit)
or
#projects = Project.where(:user_id => #user.id)
.where("end_date > ?", DateTime.now)
.order("end_date ASC")
You want to use :order, not :conditions.
Model.find(:all , :order => "end_date ASC")
Then the first result will be the item with the closest end_date
As Dan said, the condition you wrote won't get the nearest end date, but the dates that are greater than today, or the date passed in as a parameter.
In your User model you could write
def next_deadline_project
self.projects.first
end
as long as you give projects a default scope that orders records by end_date
In order to show information on the view you must set it in an instance variable in the User's controller show method. Instance variables are passed to views and you can access them to display the data.
#project = next_deadline_project
And in your show.html.erb you can use something like:
<%= #project.name %> - <%= #project.end_date %>

Rails 3: How to merge queries or scopes for complex query?

I'm building an events app that is very simple, it has a title and start_date and end_date. I would like to filter my query by mixing some of the values, like: if the start_date has passed but the end_date has not, the event is active and should be displayed. If both dates have passed, it should be omitted, too. I think that scopes is the aswer, but I only was able to filter the records within the view using some methods shown below.
I really would like to filter the query that is passed to the controller (#events). I want to show all events that are active, have a future start_date, or a past start_date but are still in progress (Today's date is in range between start_date and end_date)
EDITED
I have made some scopes which return each part of the query. Chaining them actually substracts the results instead of merging them. So i have used this code and actually works do I do not know how solid or DRY this is. Looks kind of ugly to me... is this a decent way to merge queries in rails 3?
scope :active, where("active = ?", true)
scope :not_over_or_in_progress, lambda { where("start_date < ? AND end_date > ? OR end_date IS NULL AND start_date > ? OR end_date IS NOT NULL AND start_date > ?", Date.today, Date.today, Date.today, Date.today) }
scope :valid, not_over_or_in_progress.active.order("start_date DESC")
Try using scopes:
class Event < AR::Base
scope :active, lambda { |date| where("start_date < ? AND end_date > ?", date) }
scope :future, lambda { |date| where("end_date < ?", date }
...
end
# Console
> #active_events = Event.active(Date.today)
> #future_events = Event.future(Date.today)
See http://guides.rubyonrails.org/active_record_querying.html

Resources