views:

58

answers:

3

PROBLEM:

I want to run a query which would trigger something like

select * from users where code in (1,2,4);

using a named_scope.


WHAT I TRIED:

This is for a single code:

named_scope :of_code, lambda {|code| {:conditions => ["code = ?", code]}}

I tried something like

named_scope :of_codes, lambda {|codes| {:conditions => ["code in ?", codes]}}

and sent

user.of_codes('(1,2,4)')

it triggers select * from users where code in '(1,2,4)' which raises a MySQL error because of the extra quotes.

PS: Ideally I would like to send user.of_codes([1,2,4])

+2  A: 

you can try follwing

named_scope :of_codes, lambda {|codes| {:conditions => ["code in "+codes]}}

and

user.of_codes('(1,2,4)')

EDITED For SQL INJECTION PROBLEM USE

named_scope :of_codes, lambda {|codes| {:conditions => ["code in (?) ", codes]}}

and

user.of_codes([1,2,4])
Salil
Thanks Salil. I should have figured that out.. .
Shikher
This will leave you open to SQL injection attacks. If it's *at all possible* that those codes will come from user input, you *must not* use this method.
James A. Rosen
@james:- this is my assumption that those codes will not come from user.
Salil
@james :- check my edited answer. hope that will not open injection attacks :)
Salil
+4  A: 

This will work just find and not expose you to the SQL injection attack:

named_scope :of_codes, lambda { |codes|
  { :conditions => ['code in (?)', codes] }
}

User.of_codes([1, 2, 3])
# executes "select * from users where code in (1,2,3)"

If you want to be a little more slick, you can do this:

named_scope :of_codes, lambda { |*codes|
  { :conditions => ['code in (?)', [*codes]] }
}

Then you can call it either with an Array (as above): User.of_codes([1, 2, 3]), or with a list of code arguments: User.of_codes(1, 2, 3).

James A. Rosen
Your second solution won't not work when passed an Array. Instead of `[*codes]` use `codes.flatten`
Joshua Cheek
Are you saying to use `['code in (?)', [*codes].flatten]`? Unless you're passing non-`Array`s and `Array`s in the same call, the `[*]` will do the flattening for you. I suppose if you want to be able to do `User.of_codes(1, 2, [3, 4])` then you would need `#flatten`, but that seems awkward.
James A. Rosen
+3  A: 

The simplest approach would be to use a hash for conditions instead of an array:

named_scope :of_codes, lambda { |*codes| { :conditions => { :code => codes } } }

This will work as expected.

User.of_codes(1, 2, 3) # => SELECT ... code IN (1,2,3)
User.of_codes(1) # => SELECT ... code IN (1)
Michal
I'm not sure when this support was added. It certainly works on all my Rails 2.3.x applications, but it might not work on older ones.
James A. Rosen