Reputation: 849
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
Reputation: 15296
Chaining where
calls like that will result in AND
s 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 the
owner_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