Ruby on Rails query by datetime range ( last 24, 48, etc... hours ) - ruby-on-rails

I'm trying to build a query that will search for recent entries based on column 'last_login_at'. This is a datetime field with time zone (i.e. Time.zone.now)
When I execute
User.find(:all, :conditions => ["last_login_at < ?", 24.hours.ago])
I get nothing.
Alternatively I can define today as Time.zone.today and yesterday as Time.zone.today - 1.day
and run
User.find(:all, :conditions => ["last_login_at between ? and ?", today, yesterday])
and it still returns 0 results. I know there are some entries that fall into this category.

Old question, but here's newer syntax:
User.where(last_login_at: 3.days.ago..DateTime.now)

Ensure that the timezones you have recorded within the database and those your rails app is outputting are equal. This can sometimes cause an issue. Otherwise try this named scope:
class User < ActiveRecord::Base
named_scope :last_loggedin_before, lambda { |time_ago| { :conditions => ['last_login_at < ?', time_ago] } }
named_scope :last_loggedin_within, lambda { |time_ago| { :conditions => ['last_login_at > ?', time_ago] } }
end
allowing you to do:
User.last_loggedin_before 24.hours.ago
or
User.last_loggedin_within 24.hours.ago

Related

How do date comparisons work in ActiveRecord?

I am new to Rail and trying to make a query in ActiveRecord. I am trying to get all of the records with the status of 'Landed', that are over 60 days old. My query works up to the point of getting all of the projects with the status of 'Landed'. When I add in the last condition of "created_at < ? ", then I always get an empty relation. I know that I have projects that fit that description, so I am doing something wrong in my query and dont understand. I believe my error is in the date comparison, but I am not sure.
1. Projects
belongs_to :status
has_many :project_status_histories
2. Status
has_many :projects
has_many :project_status_histories
3. Project_Status_Histories
belongs_to :status
belongs_to :project
Project.find(:all, :joins => [:project_status_histories, :status], :conditions => {:projects => {:status_id => Status.where(:name => 'Landed').first.id }, :project_status_histories => {:created_at => ["created_at < ?", (Date.today - 60.days)]}})
I have tried to build the query, step by step, with the dbconsole and am not having any luck. Thanks for all the help in advance.
I don't think it's the date arithmetic. One nice way to do this would be with named scopes. Add the following to project.rb:
scope :landed, joins(:status).where('statuses.name' => 'Landed')
scope :recent, lambda \
{ joins(:project_status_histories) \
.where('project_status_histories.created_at < ?', Date.today - 60.days) }
Then you can retrieve the relevant records/objects with:
Project.landed.recent
This worked for me in my test. You should also check out the rails guide, from which I stole most of this:
http://guides.rubyonrails.org/active_record_querying.html#scopes
Your query is a little bit complicated...
I would rather do it like this:
Project.where("status.name = ? AND project_status_histories.created_at < ?", "Landed", Time.now.day - 60.days)
I think this should work better. Let me know if it doesn't, maybe I've wrote something wrong, unfortunately I can't test it right now...
[Edit]
You also might want to see what is the generated SQL, use the "explain" method for that, just add it to the end of your query and print the result, for instance with your query:
Project.find(:all, :joins => [:project_status_histories, :status], :conditions => {:projects => {:status_id => Status.where(:name => 'Landed').first.id }, :project_status_histories => {:created_at => ["created_at < ?", (Date.today - 60.days)]}}).explain

Rails Quiz (find bugs in scope definition)

The following scope definition contains two bugs, which causes it not to work as expected. Can you find them?
named_scope :articles_to_display,
:conditions => ["articles.publish_at < (?)", Time.now]
(the column publish_at contains time/date, when the article should be published). The bugs are fundamental ones, not just a typos.
I will either accept the first correct answer or post the solution in few days.
The first problems is that Time.now is evaluated at the class level (when the file is read by Ruby) and not evaluated when the scope is used (which is what you most likely expect). In that case you need to wrap the conditions generation in a lambda/proc.
named_scope :articles_to_display, lambda {
:conditions => ["articles.publish_at < (?)", Time.now]
}
The second issue is likely that you're want to use Time.zone.now instead of Time.now to respect the localized time of the current request rather than the system time on the server.
The following is what you want to end up with:
named_scope :articles_to_display, lambda {
:conditions => ["articles.publish_at < (?)", Time.zone.now]
}
named_scope :articles_to_display, :conditions => ["articles.publish_at < (?)", DateTime.now]
Or try proc
named_scope :articles_to_display, proc{ :conditions => ["articles.publish_at < (?)", DateTime.now]}
Here was a picture of a superman, that was deleted :D
For additional reference, the accepted answer, converted to rails 3, is:
scope :articles_to_display, lambda {
where("articles.publish_at < (?)", Time.zone.now)
}
named_scope :articles_to_display,
:conditions => ["articles.publish_at IS NOT NULL AND articles.publish_at <= (?)", Time.now]

Next, Previous Records Using Named Scope

I have a model for which I want to retrieve the next record(s) and previous record(s). I want to do this via a named_scope on the model, and also pass in as an argument the X number of next/previous records to return.
For example, let's say I have 5 records:
Record1
Record2
Record3
Record4
Record5
I want to be able to call Model.previous or Model.previous(1) to return Record2. Similarly, I want to be able to call Model.next or Model.next(1) to return Record4. As another example I want to be able to call Model.previous(2) to return Record3. I think you get the idea.
How can I accomplish this?
To implement something like 'Model.previous', the class itself would have to have a 'current' state. That would make sense if the 'current' record (perhaps in a publishing scheduling system?) was Record3 in your example, but your example doesn't suggest this.
If you want to take an instance of a model and get the next or previous record, the following is a simple example:
class Page < ActiveRecord::Base
def previous(offset = 0)
self.class.first(:conditions => ['id < ?', self.id], :limit => 1, :offset => offset, :order => "id DESC")
end
def next(offset = 0)
self.class.first(:conditions => ['id > ?', self.id], :limit => 1, :offset => offset, :order => "id ASC")
end
end
If so you could do something like:
#page = Page.find(4)
#page.previous
Also working would be:
#page.previous(1)
#page.next
#page.next(1)
Obviously, this assumes that the idea of 'next' and 'previous' is by the 'id' field, which probably wouldn't extend very well over the life of an application.
If you did want to use this on the class, you could perhaps extend this into a named scope that takes the 'current' record as an argument. Something like this:
named_scope :previous, lambda { |current, offset| { :conditions => ['id < ?', current], :limit => 1, :offset => offset, :order => "id DESC" }}
Which means you could call:
Page.previous(4,1)
Where '4' is the id of the record you want to start on, and 1 is the number you want to navigate back.
I added this feature into my gem by_star last month. It may have some other features you're interested in, so check it out.
It works by you having an existing record and then calling previous or next on that:
Page.find(:last).previous
I wrote a gem for doing this for arbitrary order conditions, order_query:
class Issue < ActiveRecord::Base
include OrderQuery
order_query :order_display, [
[:priority, %w(high medium low)],
[:valid_votes_count, :desc, sql: '(votes - suspicious_votes)'],
[:updated_at, :desc],
[:id, :desc]
]
def valid_votes_count
votes - suspicious_votes
end
end
Issue.order_display #=> ActiveRecord::Relation<...>
Issue.reverse_order_display #=> ActiveRecord::Relation<...>
# get the order object, scope default: Post.all
p = Issue.find(31).order_list(scope) #=> OrderQuery::RelativeOrder<...>
p.before #=> ActiveRecord::Relation<...>
p.previous #=> Issue<...>
p.position #=> 5
p.next #=> Issue<...>
p.after #=> ActiveRecord::Relation<...>

How to find a record created on the previous month?

I have a table where new records are added daily. How would I go about finding records created in the previous month?
Set up a named scope:
named_scope :in_last_month, :conditions => [ "records.created_at > ?", 1.month.ago ]
To call it (in your controller):
Record.in_last_month
The named_scope is a rather elegant way to go, I think, but if you take that route you will want to use it with a lambda method so that the time doesn't get scoped to when the application is initially loaded.
For example, this:
named_scope :last_month, :conditions =>
['created_at > ? AND created_at < ?',
Date.today.last_month.beginning_of_month, Date.today.beginning_of_month]
will work properly the first month your application is up, but improperly the next month, unless the app gets restarted.
But this:
named_scope :last_month, lambda {
{:conditions => ['created_at > ? AND created_at < ?',
Date.today.last_month.beginning_of_month, Date.today.beginning_of_month]}}
will work every time, because the lambda method gets executed on every call, reevaluating the Date.todays.
The accepted answer and lambda improvement do not work in Rails 4.
Update for Rails 4:
scope :last_month, -> {
where( 'created_at > ? AND created_at < ?',
Date.today.last_month.beginning_of_month,
Date.today.beginning_of_month )}
Assuming your records are timestamped, you can just do something like this:
Thing.find(:all, :conditions => ["created_at > ?", Time.now - 1.month])
If they're not timestamped, you should start storing the information since it's something you'll want to look up later.
Thanks everyone, I ended up going with this:
find(:all, :conditions => ['created_at > ? AND created_at < ?', Date.today.last_month.beginning_of_month, Date.today.beginning_of_month])
In one of my projects i used this way:
Thing.where('created_at BETWEEN ? AND ? ', DateTime.now.beginning_of_month - 1.month, DateTime.now.beginning_of_month)
in Rails 3 using last_month throws an error: Date.today.last_month.beginning_of_month
NoMethodError: undefined method `last_month'
Try this for Rails 4+, note that Date.current will use your application's timezone (specified in application.rb):
scope :created_last_month, lambda {
where(created_at: Date.current.last_month.beginning_of_month..Date.current.last_month.end_of_month)
}
Do you have the "usual" fields on your table? See the RoR wiki for a list of them. That way, you can express special queries to find an answer.
Thing.find(:all, :conditions => ["created_at > ?", 1.month.ago.at_beginning_of_month])
this is a good candidate for SQL's BETWEEN syntax
named_scope :last_month, :conditions => ['created_at BETWEEN ? AND ?', Date.today.last_month.beginning_of_month, Date.today.beginning_of_month])

Mixing ActiveRecord find Conditions

I want to find records on a combination of created_on >= some date AND name IN some list of names.
For ">=" I'd have to use sql condition. For "IN" I'd have to use a hash of conditions where the key is :name and the value is the array of names.
Is there a way to combine the two?
You can use named scopes in rails 2.1 and above
Class Test < ActiveRecord::Base
named_scope :created_after_2005, :conditions => "created_on > 2005-01-01"
named_scope :named_fred, :conditions => { :name => "fred"}
end
then you can do
Test.created_after_2005.named_fred
Or you can give named_scope a lambda allowing you to pass in arguments
Class Test < ActiveRecord::Base
named_scope :created_after, lambda { |date| {:conditions => ["created_on > ?", date]} }
named_scope :named, lambda { |name| {:conditions => {:name => name}} }
end
then you can do
Test.created_after(Time.now-1.year).named("fred")
If you're using an older version Rails, Honza's query is close, but you need to add parentheses for the strings that get placed in the IN condition:
Person.find(:all, :conditions => ["created_at > ? AND name IN (?)", date, names])
Using IN can be a mixed bag: it's fastest for integers and slowest for a list of strings. If you find yourself using just one name, definitely use an equals operator:
Person.find(:all, :conditions => ["created_at > ? AND name = ?", date, name])
The cool thing about named_scopes is that they work on collections too:
class Post < ActiveRecord::Base
named_scope :published, :conditions => {:status => 'published'}
end
#post = Post.published
#posts = current_user.posts.published
For more on named_scopes see Ryan's announcement and the Railscast on named_scopes
class Person < ActiveRecord::Base
named_scope :registered, lambda { |time_ago| { :conditions => ['created_at > ?', time_ago] } }
named_scope :with_names, lambda { |names| { :conditions => { :names => names } } }
end
If you are going to pass in variables to your scopes you have to use a lambda.
You can chain the where clause:
Person.where(name: ['foo', 'bar', 'baz']).where('id >= ?', 42).first
The named scopes already proposed are pretty fine. The clasic way to do it would be:
names = ["dave", "jerry", "mike"]
date = DateTime.now
Person.find(:all, :conidtions => ["created_at > ? AND name IN ?", date, names])
I think I'm either going to use simple AR finders or Searchgasm.

Resources