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.
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
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]
I would like to construct a query in ActiveRecord based on GET params and using named_scope. I thought I'd chain some scopes and add conditions based on GET param availability, but I fear this will overwork the db, sending a new query on each query portion added:
# in model
named_scope :sorted, :order => 'title, author'
named_scope :archived, :conditions => { :is_archived => true }
named_scope :by_date, lambda { |date| { :conditions => [ 'updated_at = ?', date ] } }
# in controller / helper
#articles = Article.sorted.all
#articles = #articles.by_date(params[:date]) if params[:date]
#articles = #articles.archived if params[:archived] == '1'
Another option I've thought of is building a method chaining string that'll then be sent to the object using Object#send, but that seems a bit dirty and somewhat problematic when the named_scope receives arguments (like by_date). I realize I can construct a query string to use with :conditions => ... in ActiveRecord::Base#find, but I thought I'd try first with named_scope to see if it's possible to do lazy querying with the latter. Any suggestions on how to do this using the named_scope and not having the database bombarded by queries? thanks.
You can make lambda more smarter
# in model
named_scope :sorted, :order => 'title, author'
named_scope :archived, lambda { |is_archived| (is_archived == 1) ? {:conditions => {:is_archived => true}} : {}}
named_scope :by_date, lambda { |date| date.nil? ? {} : { :conditions => [ 'updated_at = ?', date ]}}
# in controller / helper
#articles = Article.by_date(params[:date]).archived(params[:archived]).sorted
Having a bit of difficulty figuring out how to create a named_scope from this SQL query:
select * from foo where id NOT IN (select foo_id from bar) AND foo.category = ? ORDER BY RAND() LIMIT 1;
Category should be variable to change.
What's the most efficient way the named_scope can be written for the problem above?
named_scope :scope_name, lambda { |category|
{
:conditions => ["id NOT IN (select foo_id from bar) AND foo.category = ?", category],
:order => 'RAND()',
:limit => 1
}
}
More of a comment than an answer but it won't really fit...
zed_oxff is on the ball.
To simplify things and keep them DRY, you might consider defining discrete named scopes instead of one big one, and chaining them together.
For example:
named_scope :random_order, :order => 'RAND()'
named_scope :limit, :lambda => { |limit| :limit => limit }
named_scope :whatever, ...
So you would use them as follows:
Person.random_order.limit(3).whatever
I have a database of sports teams that I am displaying in tables/standings. The relevant code involves two models, Tableposition and Draw, which are associated in a has_one relationship. The following static named scope declaration works perfectly:
class Tableposition < ActiveRecord::Base
belongs_to :draw
named_scope :grouptable, :include => :draw, :conditions => ['draws.group = ?', "B"]
end
However, when I try to make it dynamic:
class Tableposition < ActiveRecord::Base
belongs_to :draw
named_scope :grouptable, :include => :draw,
lambda { |group| { :conditions => ['draws.group = ?', group] } }
end
I get the following error:
SyntaxError: /.../app/models/tableposition.rb:4: syntax error, unexpected '\n', expecting tASSOC
I've scoured the web for solutions and have tried converting the curly braces to a do ... end with brackets to no avail. Any thoughts would be hugely appreciated.
named_scope wants arguments like this: (name, options) You were giving it (name, include option, condition option) Where both include and condition options were hashes. Instead you need to give it one merged hash.
Corrected code:
named_scope :grouptable, lambda { |group|
{ :include => :draw, :conditions => ['draws.group = ?', group] }
}