问题
I'm struggling to sanitize a raw SQL query in which the WHERE conditions may either have a value or be NULL. I was hoping to use Active Record's built-in sanitizers...
(NOTE: I'll be using a simplified query for demo purposes- our real one is a complex UNION across different model types that would be hard to do with the AR query interface)
Try 1:
raw_query = "SELECT * FROM folders WHERE user_id = ? AND parent_id = ?"
sanitized_query = ActiveRecord::Base.send(:sanitize_sql_array, [raw_query, current_user.id, params[:parent_id]])
results = ActiveRecord::Base.connection.execute(sanitized_query);
But if params[:parent_id]
is nil, then sanitized_query
ends up as
SELECT * FROM folders WHERE user_id = 1 AND parent_id = NULL
Which isn't valid, as parent_id = NULL
should be parent_id IS NULL
Try 2:
I then found the sanitize_sql_hash method, which seemed perfect for building the condition:
sanitized_conditions = ActiveRecord::Base.send(:sanitize_sql_hash, {user_id: current_user.id, parent_id: params[:parent_id]})
sanitized_query = "SELECT * FROM folders WHERE #{sanitized_conditions}"
results = ActiveRecord::Base.connection.execute(sanitized_query);
But the first line fails with:
NoMethodError: undefined method `abstract_class?' for Object:Class
The method is also listed as deprecated and will be removed in Rails 5, but it's exactly what I'm looking for. Is there another way to generate a safe WHERE condition from a hash of values?
回答1:
It looks like the error is being thrown in the reset_table_name
method at https://github.com/rails/rails/blob/7bb620869725ad6de603f6a5393ee17df13aa96c/activerecord/lib/active_record/model_schema.rb#L160 so maybe this method is not designed to work on the ActiveRecord::Base class.
Assuming you have got a model class for Folder this should work:
Folder.send(:sanitize_sql_hash, {user_id: current_user.id, parent_id: params[:parent_id]})
In a quick test I got something like:
'"folders"."user_id" = 123 AND "folders"."parent_id" IS NULL'
回答2:
I created a Rails 5-safe version of the deprecated but much needed sanitize_sql_hash_for_conditions, using @Steve's trick:
class ActiveRecord::Base
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
#
# (This is an alternative to sanitize_sql_hash_for_conditions, which was deprecated in Rails 5.
# SEE: https://api.rubyonrails.org/v4.2/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_sql_hash_for_conditions)
#
# This is needed because the latest SQL sanitization methods cannot handle conditions where an attribute is nil.
def self.sanitize_sql_hash_for_conditions_alt(attrs)
self.where(attrs).to_sql.split('WHERE ')[1]
end
end
Call it on whatever AR model class represents the table you're targeting (not on ActiveRecord::Base directly).
Folder.sanitize_sql_hash_for_conditions_alt({user_id: 1, parent_id: nil})
=> "\"folders\".\"user_id\" = 1 AND \"folders\".\"parent_id\" IS NULL"
来源:https://stackoverflow.com/questions/28001762/sanitizing-sql-in-rails-where-conditions-may-be-null