I need to select some dynamic price ranges submitted from a search form. How should I approach this with scopes? I am looking for something like this
Painting.price_range(['1..500', '2000..5000'])
SELECT * FROM paintings WHERE price BETWEEN 1..500 **OR** BETWEEN 2000..5000 etc.
Best regards.
Asbjørn Morell.
named_scope :price_range, :conditions => ["(price BETWEEN 1 AND 500) OR (price BETWEEN 2000 AND 5000)"]
OR
named_scope :price_range, :conditions => ["(price ?) OR (price ?)", (1..500).to_s(:db), (2000..5000).to_s(:db)]
Dynamic
named_scope :price_between, lambda { |from, to| { :conditions => ['price > ? AND price <= ?', from, to] } }
named_scope :price_between, lambda { |from, to| { :conditions => ['price BETWEEN ? AND ?', from, to] } }
->
MyModel.price_between(1,100)
You'll need to use a lambda on the named_scope. The following should work:
named_scope :price_range, lambda { |ranges|
{
:conditions => ["(" +
ranges.collect {"price between ? and ?"}.join(" or ") +
")"] +
ranges.collect {|r| [r.min, r.max]}.flatten
}
}
The first ranges.collect creates as many "between ? and ?" checks as you have ranges and then the second ranges.collect flattens out the ranges and adds them as values to be sanitized into the conditions. I've stuck brackets round the ors just to be on the safe side.
Related
I am working in rails 2, I want to execute Query
PunchingInformation.all(
:select => "users.id, login, firstname, lastname,
sec_to_time(avg(time_to_sec(punching_informations.punch_in_time))) as 'avg_pit',
sec_to_time(avg(time_to_sec(punching_informations.punch_out_time))) as 'avg_pot'",
:joins => :user,
:group => "users.id",
:conditions => {
"punching_informations.date between '#{start_date}' and '#{end_date}'",
["punching_informations.user_id IN (?)", employees.map { |v| v.to_i } ]
}
)
But it always return error like
Mysql::Error: Unknown column 'punching_informations.date between '2012-09-01' and '2012-09-25'' in 'where clause': SELECT users.id,login, firstname,lastname, sec_to_time(avg(time_to_sec(punching_informations.punch_in_time))) as 'avg_pit',
sec_to_time(avg(time_to_sec(punching_informations.punch_out_time))) as 'avg_pot' FROM punching_informations INNER JOIN users ON users.id = punching_informations.user_id AND (users.type = 'User' OR users.type = 'AnonymousUser' ) WHERE (punching_informations.date between '2012-09-01' and '2012-09-25' IN ('punching_informations.user_id IN (?)','--- \n- 28\n- 90\n')) GROUP BY users.id
Need your help.
It is a bit unclear what you meant (you have array, but taken in curly braces {} like a hash), but it seems ruby treats first string ("punching_informations.date between '#{start_date}' and '#{end_date}'") as a column, and second array, as array of expected values, thus making the invalid IN condition.
Perhaps it would work if rewritten as
:conditions => {
[ "(punching_informations.date between '#{start_date}' AND '#{end_date}') AND punching_informations.user_id IN (?)", employees.map { |v| v.to_i } ]
}
or even better
:conditions => {
[ "(punching_informations.date between ? AND ?) AND punching_informations.user_id IN (?)", start_date, end_date, employees.map { |v| v.to_i } ]
}
add punching_informations.date and punching_informations.user_id in select
:select => "punching_informations.date, punching_informations.user_id, users.id, ....
I have the following 2 named_scopes:
named_scope :scope1, lambda { |pi_id|
{ :select => "DISTINCT REF_PRO.*",
:joins => "INNER JOIN LNK_PRO ON LNK_PRO.PR_PRO_FK = REF_PRO.RPR_ID
INNER JOIN EMI_SUBMISSION on EMI_SUBMISSION.SUB_ID = LNK_PRO.PR_SUBMISSION_FK
INNER JOIN EMI_PERSON on EMI_PERSON.PER_ID = EMI_SUBMISSION.SUB_PI_FK ",
:conditions=>["EMI_PERSON.PER_ID = ? ", pi_id],
:group => "REF_PRO.RPR_ID"
}
}
named_scope :scope2, lambda { |pi_id|
{ :select => "REF_PRO.*",
:joins => "INNER JOIN REF_USER ON REF_USER.USR_ID = REF_PRO.RPR_CREATED_BY
INNER JOIN LNK_USER_PI on LNK_USER_PI.USP_USER_FK = REF_USER.USR_ID ",
:conditions=>["LNK_USER_PI.USP_PI_ID = ? ", pi_id]
}
}
I need to join their results.
Is there a way of doing a union of the 2 resultsets? If not, how can I modify one named_scope so that it returns me the combined results of the above named scopes?
Thanks a lot for your help
You can do this:
Model.scope1+Model.scope2
named_scope :all_public, lambda { |users|
{ :conditions => ["visibility = ? || (visibility = ? && user_id = ?)", Shared::PUBLIC, Shared::PRIVATE, users] }
}
That works nice for one user, but is there a way to modify it to work where users is an array of user ids?
Something like this and then just pass a single element array for the single ID case
named_scope :all_public, lambda { |users|
{ :conditions => ["visibility = ? OR (visibility = ? AND user_id IN (?))", Shared::PUBLIC, Shared::PRIVATE, users.join(',')] }
}
I have these two lines in my model, written for PostgreSQL:
named_scope :by_month, lambda { |month| { :conditions => ["EXTRACT(MONTH FROM recorded_on) = ?", month] }}
named_scope :by_year, lambda { |year| { :conditions => ["EXTRACT(YEAR FROM recorded_on) = ?", year] }}
I'm running PostgreSQL in production, but I'm developing with SQLite3. How can I write those lines in a way that is database-agnostic?
Btw, "recorded_on" is formed from the following:
Model.recorded_on = Time.parse("Fri, 01 May 2009 08:42:23 -0400")
Okay, found out there's a better way (thanks to this article):
Basically do nothing in the model!
date = Date.new(year, month, 1)
Model.find(:all, :conditions => { :recorded_on => date..date.end_of_month })
BETWEEN should be pretty universal: how about something like this:
named_scope :by_month, lambda { |month|
d1 = Date.new(2009, month, 1)
d2 = d1.end_of_month
{ :conditions => ['recorded_on between ? and ?', d1, d2]}
}
named_scope :by_year, lambda { |year|
d1 = Date.new(year, 1, 1)
d2 = d1.end_of_year
{ :conditions => ['recorded_on between ? and ?', d1, d2]}
}
If you have times, you'd need to get a little smarter with the hours, minutes and seconds.
I'm used to Django where you can run multiple filter methods on querysets, ie Item.all.filter(foo="bar").filter(something="else").
The however this is not so easy to do in Rails. Item.find(:all, :conditions => ["foo = :foo", { :foo = bar }]) returns an array meaning this will not work:
Item.find(:all, :conditions => ["foo = :foo", { :foo = 'bar' }]).find(:all, :conditions => ["something = :something", { :something = 'else' }])
So I figured the best way to "stack" filters is to modify the conditions array and then run the query.
So I came up with this function:
def combine(array1,array2)
conditions = []
conditions[0] = (array1[0]+" AND "+array2[0]).to_s
conditions[1] = {}
conditions[1].merge!(array1[1])
conditions[1].merge!(array2[1])
return conditions
end
Usage:
array1 = ["foo = :foo", { :foo = 'bar' }]
array2 = ["something = :something", { :something = 'else' }]
conditions = combine(array1,array2)
items = Item.find(:all, :conditions => conditions)
This has worked pretty well. However I want to be able to combine an arbitrary number of arrays, or basically shorthand for writing:
conditions = combine(combine(array1,array2),array3)
Can anyone help with this? Thanks in advance.
What you want are named scopes:
class Item < ActiveRecord::Base
named_scope :by_author, lambda {|author| {:conditions => {:author_id => author.id}}}
named_scope :since, lambda {|timestamp| {:conditions => {:created_at => (timestamp .. Time.now.utc)}}}
named_scope :archived, :conditions => "archived_at IS NOT NULL"
named_scope :active, :conditions => {:archived_at => nil}
end
In your controllers, use like this:
class ItemsController < ApplicationController
def index
#items = Item.by_author(current_user).since(2.weeks.ago)
#items = params[:archived] == "1" ? #items.archived : #items.active
end
end
The returned object is a proxy and the SQL query will not be run until you actually start doing something real with the collection, such as iterating (for display) or when you call Enumerable methods on the proxy.
I wouldn't do it like you proposed.
Since find return an array, you can use array methods to filter it, on example:
Item.find(:all).select {|i| i.foo == bar }.select {|i| i.whatever > 23 }...
You can also achive what you want with named scopes.
You can take a look at Searchlogic. It makes it easier to use conditions on
ActiveRecord sets, and even on Arrays.
Hope it helps.
You can (or at least used to be able to) filter like so in Rails:
find(:all, :conditions => { :foo => 'foo', :bar => 'bar' })
where :foo and :bar are field names in the active record. Seems like all you need to do is pass in a hash of :field_name => value pairs.