Scope parameter as array in Rails 5 - ruby-on-rails

I have this scope with the parameter city, the scope receives the parameter, joins a table call Restaurant (because I have the param there) and then it makes a where condition with IN for multiple OR.
scope :by_cities, -> (city) { joins(:restaurant).where('restaurants.city IN (?)', city) }
The thing is that I want to learn how the scope can receive an array of param in the scope, I already have tried a lot of stuff and I'm taking the guide of this guy: Rails 4 scope with argument and this: ActiveRecord where field = ? array of possible values
Logs
Parameters: {"city"=>"SanPedro"}
SELECT "vacancies".* FROM "vacancies" INNER JOIN "restaurants" ON
"restaurants"."id" = "vacancies"."restaurant_id" WHERE (restaurants.city
IN ('SanPedro'))
Route
http://localhost:3000/v1/vacancies?city=SanPedro&Monterrey
But, don't understand very well how to do it, anyone knows about this?

Minimally, there is a problem with how you're forming your url query, here:
http://localhost:3000/v1/vacancies?city=SanPedro&Monterrey
As you can see in your params:
Parameters: {"city"=>"SanPedro"}
Your not getting an array of cities. Try something more like:
http://localhost:3000/v1/vacancies?city%5B%5D=San+Pedro&city%5B%5D=Monterrey
Which should give you something like:
Parameters: {"city"=>['San Pedro', 'Monterrey']}
BTW, you can see how a proper query string should look by doing:
{city: ['San Pedro', 'Monterrey']}.to_query
in your console.
Then, you should be able to do something like:
Vacancy.by_city(params[:city])

Related

How to find the length or existence of an array/activerecord relation within an activerecord query

I have two models:
class Listing
has_many :shipping_selections
end
&
class ShippingSelection
belongs_to :listing
enum status: { incomplete: 0, complete: 1 }
end
I need to write a query that will return me listings that do not have any attached shipping_selections with a 'complete' status.
I have tried things like this:
Listing.includes(:shipping_selections).where('shipping_selections.complete.any? != ?, true)
However I got the following error:
"ActiveRecord::PreparedStatementInvalid: wrong number of bind variables (1 for 2) in: shipping_selections.complete.any? = ?"
It seems that you cannot use ruby methods like this in the SQL statement.
I then tried to use SQL statments instead, such as this:
Listing.where('cardinality(shipping_selections.complete) != ?, 0')
However this also didn't seem to work.
Thanks for the help!
The reason why you get an error is because you try to query in SQL. So what happens is that it tries to read your code in pure SQL, and SQL does not include any built in Rails functions. If you want to query in SQL you need to check for status == 0 and not .complete, it does not exist in a SQL context. That is built in active record rails functions which allows us to write it like so. What you are looking for is something like this: Listing.where(shipping_selections: shipping_selections.inclomplete)
The problem is you want to use ruby code as a SQL statement. So, as you are using enum for the status field, you can use these:
To get Listing array with completed shipping selections:
Listing.joins(:shipping_selections).merge(ShippingSelection.complete)
To get Listing array with incompleted shipping selections:
Listing.joins(:shipping_selections).merge(ShippingSelection.incomplete)

Rails SQL Injection: How vulnerable is this code?

I'm trying to understand SQL Injection. It seems like people can get pretty creative. Which gets me wondering about my search-based rails webapp I'm making.
Suppose I just fed user-entered information directly into the "where" statement of my SQL query. How much damage could be done to my database by allowing this?
def self.search(search)
if search
includes(:hobbies, :addresses).where(search)
else
self.all
end
So basically, whatever the user types into the search bar on the home page gets fed straight into that 'where' statement.
An example of a valid 'search' would be:
"hobby LIKE ? OR (gender LIKE ? AND hobby LIKE ?)", "golf", "male", "polo"
Does the fact that it's limited to the context of a 'where' statement provide any sort of defense? Could they still somehow perform delete or create operations?
EDIT:
When I look at this tutorial, I don't see a straightforward way to perform a deletion or creation action out of the where clause. If my database contains no information that I'm not willing to display from a valid search result, and there's no such thing as user accounts or admin privileges, what's really the danger here?
I took this from another post here: Best way to go about sanitizing user input in rails
TL;DR
Regarding user input and queries: Make sure to always use the active record query methods (such as .where), and avoid passing parameters using string interpolation; pass them as hash parameter values, or as parameterized statements.
Regarding rendering potentially unsafe user-generated html / javascript content: As of Rails 3, html/javascript text is automatically properly escaped so that it appears as plain text on the page, rather than interpreted as html/javascript, so you don't need to explicitly sanitize (or use <%= h(potentially_unsafe_user_generated_content)%>
If I understand you correctly, you don't need to worry about sanitizing data in this manner, as long as you use the active record query methods correctly. For example:
Lets say our parameter map looks like this, as a result of a malicious user inputting the following string into the user_name field:
:user_name => "(select user_name from users limit 1)"
The bad way (don't do this):
Users.where("user_name = #{params[:id}") # string interpolation is bad here
The resulting query would look like:
SELECT users.* FROM users WHERE (user_name = (select user_name from users limit 1))
Direct string interpolation in this manner will place the literal contents of the parameter value with key :user_name into the query without sanitization. As you probably know, the malicious user's input is treated as plain 'ol SQL, and the danger is pretty clear.
The good way (Do this):
Users.where(id: params[:id]) # hash parameters
OR
Users.where("id = ?", params[:id]) # parameterized statement
The resulting query would look like:
SELECT users.* FROM users WHERE user_name = '(select user_name from users limit 1)'
So as you can see, Rails in fact sanitizes it for you, so long as you pass the parameter in as a hash, or method parameter (depending on which query method you're using).
The case for sanitization of data on creating new model records doesn't really apply, as the new or create methods are expecting a hash of values. Even if you attempt to inject unsafe SQL code into the hash, the values of the hash are treated as plain strings, for example:
User.create(:user_name=>"bobby tables); drop table users;")
Results in the query:
INSERT INTO users (user_name) VALUES ('bobby tables); drop table users;')
So, same situation as above.
I hope that helps. Let me know if I've missed or misunderstood anything.
Edit Regarding escaping html and javascript, the short version is that ERB "escapes" your string content for you so that it is treated as plain text. You can have it treated like html if you really want, by doing your_string_content.html_safe.
However, simply doing something like <%= your_string_content %> is perfectly safe. The content is treated as a string on the page. In fact, if you examine the DOM using Chrome Developer Tools or Firebug, you should in fact see quotes around that string.

how to use LIKE in included relations

I want to use search condition with included relations, just like below
Post.includes(:tags).where( tags: { title: '%token%' }).all
The posts and tags table has been associated with a 3rd table named post_tag_relations.
The schema is like below:
posts
id: pk
title: string
content: text
tags
id: pk
title: string
post_tag_relations
id: pk
tag_id: integer
post_id: integer
The syntax only works with equal condition, I really dont know how to use LIKE search condition.
When using Post.joins(:tags) and Tag.area_table[:title].matches('%token%') it will works fine, but some post that has no tags will not be fetch out.
Could anyone help me? Thanks a lot.
UPDATE:
The Rails version is 4.1.
I want to search the post like posts.title LIKE '%token%' OR tags.title LIKE '%token%', so if use Post.joins(:tags) will not be functional if some posts have no tags. So I need use Post.includes(:tags) instead.
UPDATED AGAIN:
looks cannot use one-query to fetch, so I had already try another database schema...
Why not do this:
Post.includes(:tags).where(Tag.arel_table[:title].matches('%token%').or(Tag.arel_table[:post_id].eq(nil)))
Since ruby-on-rails-2 the joins operation is used in all cases before the includes operation during performance, but since includes uses LEFT OUTER JOIN operator, you should use exactly it. May be you need also to use not LEFT, but FULL join. So try this with arel gem:
class Post
scope :with_token(token), -> do |token|
re = Regexp.union(token).to_s
cond = Arel.sql("title REGEXP ? OR content REGEXP ?", re, re)
includes(:tags).where(Tag.arel_table[:title].eq(token).or(cond))
end
end
Of course original condition could be replaced to use LIKE operator:
class Post
scope :with_token(token), -> do |token|
includes(:tags).where(arel_table[:title].matches("%#{token}%")
.or(arel_table[:content].matches("%#{token}%")
.or(Tag.arel_table[:title].eq(token))))
end
end
NOTE: If there are some errors, provide please result SQL.
Something like this:
Post.includes(:tags).where( "tags.title LIKE ?", "%#{token}%" )
could work.
(The syntax might be a little wrong, sorry, but you get the idea)

Returning a hash instead of array with ActiveRecord::Base.connection

I have to use a query like this :
query = Enc.joins(:rec).group("enc.bottle").
select("enc.bottle as mode, count(rec.id) as numrec, sum(enc.value) as sumvalue")
That I use with :
#enc = ActiveRecord::Base.connection.select_all(query)
To get the data, I've to do #enc.rows.first[0] (it works)
But #enc.rows.first["mode"] doesn't work ! Because each row of #enc.rows contains array.. not a map with the name of each field.
Maybe select_all is a wrong method.
Does it exist another method to get the data with the name of field ?
Thank you
EDIT
If you can associate a model with the query, then there's no need for the generic select_all method. You can use find_by_sql like this:
Enc.find_by_sql(query).first.mode
# => testing
Note that you will no be able to see the aliases when inspecting the results, but they are there. Also, the convention is to use plural names for the tables. You might find it easier to just sticks with the defaults.

Remove a 'where' clause from an ActiveRecord::Relation

I have a class method on User, that returns applies a complicated select / join / order / limit to User, and returns the relation. It also applies a where(:admin => true) clause. Is it possible to remove this one particular where statement, if I have that relation object with me?
Something like
User.complex_stuff.without_where(:admin => true)
I know this is an old question, but since rails 4 now you can do this
User.complex_stuff.unscope(where: :admin)
This will remove the where admin part of the query, if you want to unscope the whole where part unconditinoally
User.complex_stuff.unscope(:where)
ps: thanks to #Samuel for pointing out my mistake
I haven't found a way to do this. The best solution is probably to restructure your existing complex_stuff method.
First, create a new method complex_stuff_without_admin that does everything complex_stuff does except for adding the where(:admin => true). Then rewrite the complex_stuff method to call User.complex_stuff_without_admin.where(:admin => true).
Basically, just approach it from the opposite side. Add where needed, rather than taking away where not needed.
This is an old question and this doesn't answer the question per say but rewhere is a thing that exists.
From the documentation:
Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
So something like:
Person.where(name: "John Smith", status: "live").rewhere(name: "DickieBoy")
Will output:
SELECT `people`.* FROM `people` WHERE `people`.`name` = 'DickieBoy' AND `people`.`status` = 'live';
The key point being that the name column has been overwritten, but the status column has stayed.
You could do something like this (where_values holds each where query; you'd have to tweak the SQL to match the exact output of :admin => true on your system). Keep in mind this will only work if you haven't actually executed the query yet (i.e. you haven't called .all on it, or used its results in a view):
#users = User.complex_stuff
#users.where_values.delete_if { |query| query.to_sql == "\"users\".\"admin\" = 't'" }
However, I'd strongly recommend using Emily's answer of restructuring the complex_stuff method instead.
I needed to do this (Remove a 'where' clause from an ActiveRecord::Relation which was being created by a scope) while joining two scopes, and did it like this: self.scope(from,to).values[:joins].
I wanted to join values from the two scopes that made up the 'joined_scope' without the 'where' clauses, so that I could add altered 'where' clauses separately (altered to use 'OR' instead of 'AND').
For me, this went in the joined scope, like so:
scope :joined_scope, -> (from, to) {
joins(self.first_scope(from,to).values[:joins])
.joins(self.other_scope(from,to).values[:joins])
.where(first_scope(from,to).ast.cores.last.wheres.inject{|ws, w| (ws &&= ws.and(w)) || w}
.or(other_scope(from,to).ast.cores.last.wheres.last))
}
Hope that helps someone

Resources