将参数传递给 sql.squish

时间:2021-05-25 15:58:34

标签: ruby-on-rails postgresql

我想在我的选择请求中使用 EXIST 来创建一个“已发表”列,具体取决于文章是否在某一年的一本书中发表。

所以我做了以下

 p params[:years]               # "2002"
 p params[:years].is_a?(String) # true

 Article.select(<<~SQL.squish, years: params[:years])
    articles.*, EXISTS(
      SELECT 1
      FROM books
      WHERE books.year IN (:years)
    ) AS published
    SQL

但它给了我以下错误

<块引用>

不支持的参数类型:哈希。而是构建一个 Arel 节点

如果我像这样尝试

 p params[:years]               # "2002"
 p params[:years].is_a?(String) # true

 Article.select(<<~SQL.squish)
    articles.*, EXISTS(
      SELECT 1
      FROM books
      WHERE books.year IN (#{params[:years]})
    ) AS published
    SQL
<块引用>

PG::UndefinedFunction:错误:运算符不存在:字符变化 = bigint WHERE books.year IN (2022)

但是 params[:years] 是一个字符串,我不知道如何做我想做的事,而且我不理解这两个错误。

编辑:如果我像这样使用单引号

 p params[:years]               # "2002"
 p params[:years].is_a?(String) # true

 Article.select(<<~SQL.squish)
    articles.*, EXISTS(
      SELECT 1
      FROM books
      WHERE books.year IN ('#{params[:years]}')
    ) AS published
    SQL

它适用于单个值,但如果 params[:years] 是一个数组,它可以工作但输出不正确,如果我在 2003 年有一本书中的文章,它将设置为 false

 p params[:years]               # ["2002", "2003"]
 p params[:years].is_a?(String) # false
 Article.select(<<~SQL.squish)
    articles.*, EXISTS(
      SELECT 1
      FROM books
      WHERE books.year IN ('#{params[:years]}')
    ) AS published
    SQL

2 个答案:

答案 0 :(得分:2)

ActiveRecord 中的 Object.select 方法不支持像 .where 那样经过消毒的占位符插入。这就是为什么您会收到您收到的错误 - select 需要一个字段列表,但您的参数之一是哈希。

获得您正在寻找的功能的一种方法是,正如错误消息所暗示的那样,使用 Arel(如果您不熟悉它,请将 Arel 视为 ActiveRecord 在内部用于构建的“构建块”其 SQL 查询)。 Max's answer 可以很好地将您的查询翻译成 Arel。

或者,您可以像 ActiveRecord 的其他部分一样清理您自己的 SQL。为清楚起见,将其拆分为 SQL 解析和 select 语句:

sql = Article.sanitize_sql([<<~SQL, year: params[:years]])
  articles.*, EXISTS(
      SELECT 1
      FROM books
      WHERE books.year IN (:years)
    ) AS published
SQL
Article.select(sql)

我想说,手动清理您的 SQL 将使您克服当前遇到的绊脚石,但是如果您要在整个商店进行类似类型的操作,您将受益于了解 Arel以及如何使用它。

答案 1 :(得分:1)

通过将用户输入插入到 SQL 查询中,您可以为 SQL injection attack 敞开大门。

这很容易通过使用 Arel 来构造查询来防止:

Article.select(
  Article.arel_table[Arel.star],
  Book.select(1)
      .where(year: params[:years])  
      .arel
      .exists
      .as('published')
)
SELECT 
  "articles".*, 
  EXISTS (SELECT 1 FROM "books" WHERE "books"."year" IN (?, ?, ?)) AS published 
FROM "articles"