I want to pass hashset to ActiveRecord finder method Model_name.where({ :key => value }). This works perfectly, but SQL composed from that uses straight comparison =. Is it possible to customize this and switch to LIKE comparison usage with hashset?
The :key => value syntax only works for =, IN, and BETWEEN conditions (depending on whether value is atomic, an Array, or a Range). Anything else requires you to pass the SQL as a string:
Model.where("key LIKE ?", value)
Related
In rails source (https://github.com/rails/rails/blob/fe4b0eee05f59831e1468ed50f55fbad0ce11e1d/activerecord/lib/active_record/sanitization.rb#L112) there is a sanitize_sql_like method that (I am hoping) will sanitize strings before using them with a SQL LIKE
however, I can't seem to use that, as Rails says that method doesn't exist.
My string has an apostrophe in it and the query is
#query = "Joe's"
Model.where("lower(field) LIKE ?", "%#{#query}%")
Using ActiveRecord::Base.sanitize doesn't help, as there are no results for the query.
How can I escape #query, and keep my SQL secured?
I've solved this same problem (on MySQL) using ActiveRecord::Sanitization::ClassMethods to properly sanitize user input. Unfortunately, the methods defined in ActiveRecord::Sanitization::ClassMethods are declared as protected, so they are only accessible in the scope of a Model class. The methods defined in ActiveRecord::Sanitization::ClassMethods are mixed in to all Model classes, so they are available directly from within the scope of a Model. That requires you to define a class/instance method or scope (ActiveRecord scope) on your model to use them, rather than using them externally, as in your example. However, design-wise, it's probably preferable to encapsulate the query logic in the Model anyway.
This solution also has the advantage of not only escaping the single-quote character, but also escaping other characters that would be interpreted by a SQL like query, such as the '%' character (among others). This should properly prevent SQL injection by escaping the the other characters which might be able to cause values to be interpreted rather than being treated as literals. There are also additional sanitization methods defined in ActiveRecord::Sanitization::ClassMethods that are useful for embedding user input (or other tainted input) in other SQL query contexts. Here is a working solution, tested on MySQL.
class Model < ActiveRecord::Base
# Finds a Model by case-insensitive substring match on Model.field
#
# #param query [String] A value to use in the substring match.
# #return [ActiveRecord::Relation] An `Relation` of `Model`s whose
# `field` includes the `query` substring.
scope :find_by_field_substring, ->(query) do
where(arel_table[:field].matches("%#{sanitize_sql_like(query)}%"))
end
end
You can then access this scope like this:
Model.find_by_field_substring "Joe's"
=> #<ActiveRecord::Relation [#<Model id: 1, field: "Joe's">]>
The usage of both ActiveRecord scopes and ActiveRecord::Relations are pretty well documented, but may only be available in newer versions of Rails.
Note that your database may require a second parameter to the sanitize_sql_like method to specify a different escape character than '\'.
Also note that escaping like this doesn't work for MySQL if it is running in NO_BACKSLASH_ESCAPES mode, because it forces a different mechanism for escaping values. If you're using NO_BACKSLASH_ESCAPES mode see this answer.
I haven't tested this part, but rather than using Arel, the SQL AST builder that sits underneath ActiveRecord, one could probably also use the style suggested in your initial example, as long as it is defined within the Model class.
class Model < ActiveRecord::Base
scope :find_by_field_substring, ->(query) do
where("lower(field) LIKE ?", "%#{sanitize_sql_like(query)}%")
end
end
If you use where properly, it will escape the input automatically
#query = "Joe's"
Model.where("lower(field) LIKE ?", "%#{#query}%")
Just note that your query is wrong. You have a lower() operator, then you pass an input which is not lower-case. The query will always return 0.
Moreover, lower() will reduce the ability of the database to use the index. In most databases, LIKE is already case insensitive (except for PostgreSQL where you should use ILIKE).
query = "Joe's"
Model.where("field LIKE ?", "%#{query}%")
or
query = "Joe's"
Model.where("field ILIKE ?", "%#{query}%")
Here's a real example on a real database. As you can see, the input is properly escaped in the final SQL.
> query = "Joe's"
> User.where("lower(email) LIKE ?", "%#{query}%")
User Load (4.4ms) SELECT "users".* FROM "users" WHERE (lower(email) LIKE '%Joe''s%')
=> #<ActiveRecord::Relation []>
Short and illegal answer:
ActiveRecord::Base.send(:sanitize_sql_like, text_to_escape)
Here's the output in the rails console (postgres)
irb(main):001:0> ActiveRecord::Base.send(:sanitize_sql_like, '%foo_bar%')
=> "\\%foo\\_bar\\%"
sanitize_sql_like is not a public method, so you should use it wisely.
You are not using this method just because of the apostrophe, it also escapes characters like %_ that are wildcards in Postgres.
This answer could be useful in case you want to extend ActiveRecord::Base, so all your models can include this method:
Rails extending ActiveRecord::Base
As previously stated, you can still use the sanitization methods if you use them within context of the model. Although adding a scope works (as shown in a previous answer), it's not strictly necessary. For example, you can add this to your model:
def self.where_ilike(search_terms)
where('search_tokens ILIKE ?', "%#{sanitize_sql_like(search_terms)}%")
end
I tried something like this
#Html.DisplayFor(modelItem => (item.Name + "#" + item.Department))
I get "InvalidOperationException" (Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.)
Both the members are strings, I thought this should work...
Thanks
The error is because DisplayFor accepts an Expression which is Expression<Func<TModel,TReturn>> and not a delegate Func<TModel, TReturn>. So you can't mix it with random C# code as it is not a delegate.
To get what you want, you can use this (odd syntax I know - because you have to escape the #):
#Html.DisplayFor(item => item.Name)#:###Html.DisplayFor(item => item.Department)
Refer the below links.I Hope this will solve your problem.
Get a template error when I try to do this?
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions
What's the best way to do IS IN queries, especially when involving a join?
Currently, I have something like the following:
Table1.joins(:table2).where( { :table2s => { :ident => params[:idents].split(',') } } )
This works and gets the job done. The resulting WHERE clause is something like
WHERE "table2s"."ident" IS IN ('a','b','c')
I feel like this would be cleaner though:
Table1.joins(:table2).where("table2s.ident IS IN ?", params[:idents]:split(','))
Is there a way to avoid the first style and use something more like the second style? (i.e., the where method recognizes the array and uses IS IN rather than '=' operator)
Letting the query compiler do it for you is generally a better way to do it as it will handle cases you might forget, such as passing a nil value and ending up with an erroneous IS IN(NULL) instead of IS NULL. You can clean up your statement, though:
Table1.joins(:table2).where(:table2s => { :ident => params[:idents].split(',') })
Taking this a step further, you can reduce it to:
Table1.joins(:table2).where('table2s.ident' => params[:idents].split(','))
You could further clean this up by writing a scope that encapsulates this instead of using this as-is.
You don't need the split. ActiveRecord is smart enough to understand arrays, so all you need is Table1.joins(:table2).where( { :table2s => { :ident => params[:idents]}}).
In fact, you don't need the nesting. Table1.joins(:table2).where('table2s.ident' => params[:idents]) should work find. Arel is pretty smart!
I have a method that takes an System.Action, this is what I'm trying to feed it:
Function() Me._existingImports = Me.GetImportedAds()
The thing is that it complains about the = sign since it thinks I'm trying to do a comparison, which I'm not. I want to assign the Me._existingImports the value of Me.GetImportedAds(), but VB.NET complains about DataTable not having a = operator.
How can I force it to use the assignment operator instead of the equality operator?
In C# this works perfectly fine:
() => this.existingImports = this.GetImportedAds()
For now the solution will be to use a standalone method, but that's way more code than needed.
When using Function(), you really define an anonymous function which means you map values to values.
Therefore Function() strictly needs an expression (like x or 42 ...) as the body, which an assignment is not! (Assignments don't evaluate to values like in C-style languages in VB)
Thus what you need is not a Function() but a Sub(), which contains statements (actions) rather than values.
Sub() Me._existingImports = Me.GetImportedAds()
C# doesn't distinguish here, the (much nicer) ... => ... syntax covers it all.
I am doing some plain SQLs in my rails model (for purists this is just for complex SQLs :)
Since I am not using find*/condition methods, is there a helper method that I can use straight to do that?
The quote method on the connection object escapes strings. When building up queries, use sanitize_sql_for_conditions to convert ActiveRecord conditions hashes or arrays to SQL WHERE clauses.
The methods in ActiveRecord::ConnectionAdapters::DatabaseStatements are handy for direct queries, in particular the ones starting with select_.
Rails uses sanitize_sql_for_conditions internally for dealing with placeholders. Of course, that method is protected so you can't (cleanly) use it outside of an ActiveRecord model. You can get around the protectedness using send:
escaped_string = Model.send(:sanitize_sql_for_conditions, [
'id = ? and name = ?',
params[:id], params[:name]
]
)