ScottOBot
ScottOBot

Reputation: 849

Chain of multiple where Active Record clauses not working

I am fairly new to Ruby on Rails and ActiveRecord. I have a database model named location which describes a point of interest on a map. A location has a location_type field that can have three different location types (business, dispensary or contact). A location also has an owner_id as well which is the user_id of the user who created the location.

In the controller the user requests all of their locations by providing their ID. The dispensary and business locations are public so all users should be able to view them, while the contacts should only be shown to the user who is the owner of them. Therefore I am tasked with creating an ActiveRecord query that returns all dispensaries and businesses in the database and all contacts that were created by that user. I was trying to do this by chaining together where clauses but for some reason this has failed:

@locations = Location.where(:location_type => ["business", "dispensary"]).where(:location_type => "contact", :owner_id => params[:id])

Which generates this PostgreSQL:

SELECT "locations".* FROM "locations"  WHERE "locations"."location_type" IN ('business', 'dispensary') AND "locations"."location_type" = 'contact' AND "locations"."owner_id" = 1

I suspect that this failed because the first where returns just the locations of type business and dispensary and the second where queries that returned data which has no locations of type contact within it. How can I query for all dispensaries and businesses combined with a set of filtered contacts?

Upvotes: 0

Views: 1672

Answers (1)

khampson
khampson

Reputation: 15296

Chaining where calls like that will result in ANDs at the SQL level. where can take raw SQL for an argument, in which you could explicitly add an OR, but parameterizing it properly is rather messy IMO (although it can be done). So, for this type of query, I think it would probably be best to drop down into using raw SQL with sanitized inputs (to guard against SQL injection).

i.e. something like this:

x = ActiveRecord::Base.connection.raw_connection.prepare(
    "SELECT * FROM locations
     WHERE location_type IN ('business', 'dispensary') OR
           (location_type = 'contact' AND owner_id = ?)")
x.execute(params[:id])
x.close

This will select all items from the locations table where the location_type is either 'business' or 'dispensary', regardless of the owner_id, and all items where the location_typeis 'contact' where theowner_id` matches the one passed in.

Edit in response to comment from OP:

I tend to prefer raw SQL whenever possible for more complex queries, as I find it easier to control the behavior (ORMs can sometimes do things that are less than desirable, such as executing the same query 1000 times to get 1000 entries instead of one SQL query once, resulting in terrible performance), however, if you'd prefer stay within the bounds of ActiveRecord, you can use the form of where that takes arguments. It'll be somewhat raw SQL, in that you need to specify the where clause yourself, but you won't need to get a raw_connection and explicitly execute -- it'll work within the framework of the ActiveRecord query you were doing.

So, that would look something like this:

@locations = Location.where("location_type IN ('business', 'dispensary') OR
    (location_type = 'contact' AND owner_id = ?)", params[:id])

See this Active Record guide page for more info, section 2.2.

Edit in response to follow-up question from OP:

Regarding the ? in the SQL, you can think of it as a placeholder of sorts (there's really no formatting to be done with it, but rather signifies a parameter goes there).

The reason it's important is that when a ? is placed in the query and then the actual value you want to use is passed as an argument to where (and certain other functions as well), the underlying SQL driver will interpolate the parameter into the query in such a way that prevents SQL injection, which could allow for all kinds of different problems. If you were to instead do the interpolation yourself directly into the query string, you would still be potentially susceptible to SQL injection. So not only is ? safe from SQLI, it's specifically intended to prevent it.

You can have a bunch of ? in your query, as long as you pass the corresponding number of parameters as arguments after the query string (otherwise the SQL driver should error out).

Upvotes: 2

Related Questions