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]
Related
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
There's a named_scope in a project I'm working on that looks like the following:
# default product scope only lists available and non-deleted products
::Product.named_scope :active, lambda { |*args|
Product.not_deleted.available(args.first).scope(:find)
}
The initial named_scope makes sense. The confusing part here is how .scope(:find) works. This is clearly calling another named scope (not_deleted), and applying .scope(:find) afterwards. What/how does .scope(:find) work here?
A quick answer
Product.not_deleted.available(args.first)
is a named-scope itself, formed by combining both named scopes.
scope(:find) gets the conditions for a named-scope (or combination of scopes), which you can in turn use to create a new named-scope.
So by example:
named_scope :active, :conditions => 'active = true'
named_scope :not_deleted, :conditions => 'deleted = false'
then you write
named_scope :active_and_not_deleted, :conditions => 'active = true and deleted = false'
or, you could write
named_scope :active_and_not_deleted, lambda { self.active.not_deleted.scope(:find) }
which is identical. I hope that makes it clear.
Note that this has become simpler (cleaner) in rails 3, you would just write
scope :active_and_not_deleted, active.not_deleted
Scope is a method on ActiveRecord::Base that returns the current scope for the method passed in (what would actually be used to build the query if you were to run it at this moment).
# Retrieve the scope for the given method and optional key.
def scope(method, key = nil) #:nodoc:
if current_scoped_methods && (scope = current_scoped_methods[method])
key ? scope[key] : scope
end
end
So in your example, the lambda returns the scope for a Product.find call after merging all the other named scopes.
I have a named_scope:
named_scope :active, {:conditions => {:active => true}}
In my console output, Object.active.scope(:find) returns:
{:conditions => {:active => true}}
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
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])
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.