清理条件可能为 NULL 的 Rails 中的 SQL

Sanitizing SQL in Rails where conditions may be NULL

我正在努力清理原始 SQL 查询,其中 WHERE 条件可能有值或为 NULL。我希望使用 Active Record 的内置消毒剂...

(注意: 我将使用 简化的 查询进行演示 - 我们真正的查询是跨不同模型的复杂 UNION使用 AR 查询接口很难处理的类型)

尝试 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);

但如果 params[:parent_id] 为零,则 sanitized_query 最终为 SELECT * FROM folders WHERE user_id = 1 AND parent_id = NULL 这是无效的,因为 parent_id = NULL 应该是 parent_id IS NULL

尝试2:

然后我找到了 sanitize_sql_hash 方法,它似乎非常适合构建条件:

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);

但是第一行失败了:

NoMethodError: undefined method `abstract_class?' for Object:Class

该方法也被列为已弃用,将在 Rails 5 中删除,但这正是我要找的。是否有另一种方法可以从值的散列生成安全的 WHERE 条件?

看起来错误是在 https://github.com/rails/rails/blob/7bb620869725ad6de603f6a5393ee17df13aa96c/activerecord/lib/active_record/model_schema.rb#L160reset_table_name 方法中抛出的,所以可能此方法不适用于 ActiveRecord::Base class。

假设您有文件夹的模型 class 这应该有效:

Folder.send(:sanitize_sql_hash, {user_id: current_user.id, parent_id: params[:parent_id]})

在快速测试中我得到了类似的东西:

'"folders"."user_id" = 123 AND "folders"."parent_id" IS NULL'

我创建了一个 Rails 已弃用但非常需要的 5 安全版本 sanitize_sql_hash_for_conditions, using @Steve's :

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

在任何代表 table 的 AR 模型 class 上调用它(而不是直接在 ActiveRecord::Base 上)。

Folder.sanitize_sql_hash_for_conditions_alt({user_id: 1, parent_id: nil})
=> "\"folders\".\"user_id\" = 1 AND \"folders\".\"parent_id\" IS NULL"