Ruby on Rails: named scope with lambda and an array - ruby-on-rails

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(',')] }
}

Related

Return nil if no current_user

I have the following Scope in my Rails app, which is used to fetch active Choices from the database based on the current_user. This works just fine, but if there is no current_user the the code fetches alle the Choices in the database. Here I just want it to fetch nothing.
scope :active, lambda{|user| user ? { :conditions => ["deliverydate = ? and user_id = ?", Date.tomorrow, user.id], :order => 'id DESC'} : {} }
How do I rewrite thee above to return nothing if there is no current_user?
The problem is that I'm using Pusher to push new data to the website, but if the user session expires then all data are pushed instead of nothing.. hopes this makes sense :)
As scopes return an ActiveRecord::Relation instance so it would be more correct to return empty ActiveRecord::Relation object like it's described here.
So, you have to add :none scope which does the trick:
scope :none, limit(0)
and then use it inside your scope like:
scope :active, ->(user = nil) { user ? { :conditions => ["deliverydate = ? and user_id = ?", Date.tomorrow, user.id], :order => 'id DESC'} : none }
scope :active, lambda{|user| user ? { :conditions => ["deliverydate = ? and user_id = ?", Date.tomorrow, user.id], :order => 'id DESC'} : nil }
That is because the empty hash ({}) has no conditions, which basically means return all rows.
Based on the way your code is structured, you could make a condition that is something like :id => -1, :id => nil or 1=0 or something that is always false so it won't return any rows.
(And as was mentioned in the comment below your question, scopes should not return nil since it cannot be chained.)

IN clause in :conditions in rails

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, ....

Issue with merging AR queries

I've been trying all different ways of combining to queries but I'm not having any luck at all. It's either returning one or the other depending on what query I put first. I want it so that it takes all the entries of for_group_with_account and all the entries of for_task_with_account and make one list. It's not the conditions that I want combined but the results of each of them combined. Hope that makes sense.
Here's the scopes in my tickets model:
scope :for_group_with_account, lambda { |account| joins(:group => :accounts).where("accounts.id = ?", account.id) }
scope :for_task_with_account, lambda { |account| joins(:tasks => :account).where("accounts.id = ?", account.id) }
scope :for_account, lambda { |account| for_task_with_account(account).merge(for_group_with_account(account)) }
For the last scope where I combine the scopes I've also tried:
scope :for_account, lambda { |account| for_task_with_account(account) + for_group_with_account(account) }
scope :for_account, lambda { |account| for_task_with_account(account) & for_group_with_account(account) }
scope :for_account, lambda { |account| for_task_with_account(account) && for_group_with_account(account) }
Still none of these are actually taking the two listings and combining them. Very very frustrating. Is there something I'm doing wrong?
Thanks.
scope :for_account, lambda { |account| for_task_with_account(account).for_group_with_account(account) }

Rails, working with named_scope

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.

Filtering ActiveRecord queries in rails

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.

Resources