Rails + ActiveAdmin - 使用ransacker进行过滤会抛出错误PG :: SyntaxError:ERROR:语法错误在","附近

cha*_*ann 6 ruby postgresql activeadmin ransack ruby-on-rails-4

我对Ruby的项目on Rails的4.1.4,使用activeadmin 1.0.0.pre from git://github.com/activeadmin/activeadmin,pg 0.17.1和PostgreSQL 9.3

在项目中我有这些模型:

  1. class User has_one :account

  2. class Account belongs_to :user has_many :project_accounts has_many :projects, :through => :project_accounts

  3. class Project # the project has a boolean attribute 'archive' has_many :project_accounts

  4. class ProjectAccount belongs_to :account belongs_to :project

我有一个任务是在索引页面上实现一个名为"by_active_projects"的ActiveAdmin过滤器,因此它必须显示已定义活动项目数的用户,这意味着这样的项目具有archive == false.例如,如果我在过滤器中键入"2",则必须找到具有正好2个活动项目的此类帐户.

现在我在ActiveAdmin中注册了"帐户"资源,并在admin/account.rb我添加的内部filter :by_active_projects_eq

之后我having_active_projects为Account模型(models/account.rb)定义了一个范围:

scope :having_active_projects, ->(number) { joins(:projects).where("projects.archive = ?", false).having("count(projects) = ?", number).group("accounts.id") }
Run Code Online (Sandbox Code Playgroud)

下一步,我为Account模型定义了一个ransacker,如下所示:

ransacker :by_active_projects, formatter: proc{ |v|
    data = Account.having_active_projects(v).map(&:id)
    data ||= nil
  } do |parent|
    parent.table[:id]
  end
Run Code Online (Sandbox Code Playgroud)

在开发数据库中有一个帐户,它有8个活动项目,过滤效果很好.但是,当我试图通过2个活动项目过滤帐户时,我遇到了错误.在DB中有三个这样的帐户,错误页面报告我查询语法错误:

SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "accounts" WHERE "accounts"."deleted_at" IS NULL AND "accounts"."id" = 'e4d247ec-e64d-4e8a-996a-4d73ccb11257', 'bcb8fa61-4a53-4b45-8954-8fb6ae328365', '93d670b6-7b8f-4c27-91cc-e0f44c137114' LIMIT 30 OFFSET 0) subquery_for_count
Run Code Online (Sandbox Code Playgroud)

如你所见,而不是

"accounts"."id" IN ('e4d247ec-e64d-4e8a-996a-4d73ccb11257', 'bcb8fa61-4a53-4b45-8954-8fb6ae328365', '93d670b6-7b8f-4c27-91cc-e0f44c137114')
Run Code Online (Sandbox Code Playgroud)

这个东西正在生成:

"accounts"."id" = 'e4d247ec-e64d-4e8a-996a-4d73ccb11257', 'bcb8fa61-4a53-4b45-8954-8fb6ae328365', '93d670b6-7b8f-4c27-91cc-e0f44c137114'
Run Code Online (Sandbox Code Playgroud)

我试着深入研究源代码,在ActiveRecord,ActiveAdmin和Ransack lib中的代码段上移动断点,并发现该关系是在Arel :: Nodes :: Equality的帮助下构建的.我不确定这是原因,但我可以肯定地说:

LIB/active_record /关系/ query_methods.rb `

560   def where!(opts = :chain, *rest)
561      if opts == :chain
562        WhereChain.new(self)
563      else
564        references!(PredicateBuilder.references(opts)) if Hash === opts
565        self.where_values += build_where(opts, rest)
566        self
567      end
568    end`
Run Code Online (Sandbox Code Playgroud)

self 这是Account的Active Record关系;

在呼叫build_where#565行之前,self.to_sql等于

SELECT "accounts".* FROM "accounts"  WHERE "accounts"."deleted_at" IS NULL  ORDER BY "accounts"."created_at" desc
Run Code Online (Sandbox Code Playgroud)

在调用它并将结果分配给之后self.where_values,

self.to_sql 等于

SELECT "accounts".* FROM "accounts"  WHERE "accounts"."deleted_at" IS NULL AND "accounts"."id" = 'e4d247ec-e64d-4e8a-996a-4d73ccb11257', 'bcb8fa61-4a53-4b45-8954-8fb6ae328365', '93d670b6-7b8f-4c27-91cc-e0f44c137114'  ORDER BY "accounts"."created_at" desc
Run Code Online (Sandbox Code Playgroud)

任何关于此事的帮助或信息表示赞赏!谢谢!

cha*_*ann 5

所以我找到了解决方案:

首先,我已经改变了我的过滤器,admin/account.rb

filter :by_active_projects_eq
Run Code Online (Sandbox Code Playgroud)

filter :by_active_projects_in,
         :as => :string
Run Code Online (Sandbox Code Playgroud)

这种方法导致正确的 SQL 生成,

"accounts"."id" IN ('e4d247ec-e64d-4e8a-996a-4d73ccb11257', 'bcb8fa61-4a53-4b45-8954-8fb6ae328365', '93d670b6-7b8f-4c27-91cc-e0f44c137114')
Run Code Online (Sandbox Code Playgroud)

在那之后,我也不得不改变我的掠夺者

ransacker :by_active_projects, formatter: proc{ |v|
    data = Account.having_active_projects(v).map(&:id)
    data ||= nil
  } do |parent|
    parent.table[:id]
  end
Run Code Online (Sandbox Code Playgroud)

ransacker :by_active_projects, formatter: proc{ |v|
    data = Account.having_active_projects(v).pluck(:id)
    data.present? ? data : nil
  } do |parent|
    parent.table[:id]
  end
Run Code Online (Sandbox Code Playgroud)

因为它的实现方式也导致了错误的查询:例如,没有这样的帐户正好有 5 个活动项目。在这种情况下

data = Account.having_active_projects(v).pluck(:id)
Run Code Online (Sandbox Code Playgroud)

返回“空数组”,并处理这个数组而data ||= nil从未真正返回过nil,这导致了这样的 SQL:

SELECT COUNT(count_column) FROM (SELECT  1 AS count_column FROM "accounts"  WHERE "accounts"."deleted_at" IS NULL AND "accounts"."id" IN () LIMIT 30 OFFSET 0) subquery_for_count
Run Code Online (Sandbox Code Playgroud)

请注意"accounts"."id" IN ()造成麻烦的部分。

替换data ||= nil为 后data.present? ? data : nil,如果data不存在,则为其分配一个nil,并正确生成 SQL 中的该部分:"accounts"."id" IN (NULL)