Ransack + FlagShihTzu + Active Admin不能很好地协同工作

时间:2016-03-09 20:53:01

标签: ruby-on-rails activeadmin ransack

我正在使用辉煌的gem flag_shih_tzu在单个整数列上创建按位布尔标志,而不需要为每个标志创建单独的DB列。我已经很喜欢这个宝石很多年了,而且它非常适合以你通常所期望的方式与ActiveRecord属性进行交互。

然而,它与Ransack和Active Admin开箱即用效果不佳。 Active Admin要求我为每个标志添加允许的参数:

  permit_params do
   :identity_verified
  end

用于:identity_verified“flag”属性甚至可以显示在过滤器或索引列中,这很好;我不介意。但我遇到的真正问题是当我尝试使用:identity_verified标志作为过滤器时(当然是布尔值),Active Admin使用Any / Yes / No显示正常的选择选项,但是当我第一次提交时过滤查询,我得到一个例外:undefined method identity_verified_eq for Ransack::Search

好的,所以我做了一些研究,发现我需要为:identity_verified添加ransacker。那样做了,我不再得到例外,但是搜索者似乎根本没有做任何事情。事实上,我故意在块中输入语法错误以引发异常,但Active Admin只返回所有用户,无论他们是否:identity_verified。 ransacker块中的代码似乎甚至无法执行。任何人都可以帮我弄清楚如何为:identity_verified?

创建一个合适的Ransack定义

以下是代码:

用户模型:

  #  ===============
  #  = FlagShihTzu =
  #  ===============

  has_flags 1  => :guest,             
            2  => :prospect,         
            3  => :phone_verified,   
            4  => :identity_verified,


  #  ==============
  #  = Ransackers =
  #  ==============

  # this avoids the identity_verified_eq missing method exception, but 
  # doesn't appear to do anything else
  ransacker :identity_verified, args: [:ransacker_args] do |args|
    asdf # <-- should cause an exception
    Arel.sql(identity_verified_condition)
  end

Active Admin:

ActiveAdmin.register User do
  permit_params do
    :identity_verified
  end

  # ...

  filter :identity_verified, as: :boolean

  # ...

end

Identity Verified过滤器在Active Admin中显示为布尔选择,就像我期望的那样,但是当我提交过滤器时(如上所述),我将所有用户都恢复,并且ransacker块甚至没有似乎被执行了。我看过Ransack examples。我已经挖掘了所有四个宝石的代码(包括Formtastic),我仍然无法解决它。

以下是Active Admin对查询提交的POST URL:

http://example.com/admin/users?q%5Bidentity_verified%5D=true&commit=Filter&order=id_desc

这是Rails日志,用于确认:identity_verified param正在传递:

Processing by Admin::UsersController#index as HTML
  Parameters: {"utf8"=>"✓", "q"=>{"identity_verified"=>"true"}, "commit"=>"Filter", "order"=>"id_desc"}

同样,ransacker块的内部似乎没有被执行。我需要理解为什么,如果有的话,如何编写正确的Arel语句,以便我可以过滤此标志。

帮助?

2 个答案:

答案 0 :(得分:1)

恢复这个问题,因为这是G *的第一个结果,在这里搜索&#34;标志shih tzu activeadmin&#34;。此外,OP的解决方案似乎并不理想,因为它的负载和为这部分中满足标志条件的所有记录实例化AR对象:

results = object_class.send("#{flag}")
results = results.map(&:id)

所以,这是我目前为他人提供的解决方案:

# config/initializers/ransack.rb
Ransack.configure do |config|
  config.add_predicate 'flag_equals',
    arel_predicate: 'eq',
    formatter: proc { |v| (v.downcase == 'true') ? 1 : 0 },
    validator: proc { |v| v.present? },
    type: :string
end

# app/concerns/has_flags.rb
module HasFlags
  extend ActiveSupport::Concern

  included { include FlagShihTzu }

  class_methods do
    def flag_ransacker(flag_name, flag_col: 'flags')
      ransacker(flag_name) do |parent|
        Arel::Nodes::InfixOperation.new('DIV',
          Arel::Nodes::InfixOperation.new('&', parent.table[flag_col], flag_mapping[flag_col][flag_name]),
          flag_mapping[flag_col][flag_name])
      end
    end
  end
end

# app/models/foo.rb
class Foo < ActiveRecord::Base
  include HasFlags

  has_flags 1 => :bar, 2 => :baz
  flag_ransacker :bar
end

# app/admin/foos.rb
ActiveAdmin.register Foo do 
  filter :bar_flag_equals, as: :boolean, label: 'Bar'
end

答案 1 :(得分:0)

所以,在幸运地找到这篇文章ActiveAdmin Filters with Ransack之后,我终于想出了答案。它的要点是使用DSL正确定义Active Admin过滤器,更重要的是,模型中适当的ransacker用于要过滤的FlagShihTzu标志。

这是一个有效的例子:

模型/ user.rb:

class User
  include FlagShihTzu

  # define the flag
  has_flags 1 => :identity_verified

  # convenience method to define the necessary ransacker for a flag

  def self.flag_ransacker(flag)
    ransacker flag.to_sym,
    formatter: proc { |true_false|
      if true_false == "true"
        results = object_class.send("#{flag}")
      else
        results = object_class.send("not_#{flag}")
      end
      results = results.map(&:id)
      results = results.present? ? results : nil
    }, splat_params: true do |parent|
    parent.table[:id]
  end

end

管理员/ user.rb:

ActiveAdmin.register User do

  # A method used like a standard ActiveAdmin::Resource `filter` DSL call, but for FlagShizTzu flags
  # A corresponding `flag_ransacker` call must be made on the model, which must include
  # the FlagShizTzuRansack module defined in app/concerns/models/flag_shih_tzu_ransack.rb
  def flag_filter(flag)
    @resource.flag_ransacker flag.to_sym # call the ransacker builder on the model
    flag = flag.to_s
    filter_name = "#{flag}_in" # we use the 'in' predicate to allow multiple results
    filter filter_name.to_sym,
      :as => :select,
      :label => flag.gsub(/[\s_]+/, ' ').titleize,
      :collection => %w[true false]
  end

  flag_filter :identity_verified

end

瞧,这是一个用于旗帜标志的边栏过滤器。关键是在过滤器声明中的标志名称末尾添加in谓词,而不是排除它,默认为eq Ransack谓词。使用pry和调试器来定义ransacker本身需要反复试验,但主要基于上述帖子。

最终,我最终将两个文件中的内联方法拉出到我包含在必要模型中的模块和需要它们的AA资源定义中。

应用程序/关切/模型/ flag_shih_tzu_ransack.rb:

# Used to define Ransackers for ActiveAdmin FlagShizTzu filters
# See app/admin/support/flag_shih_tzu.rb
module FlagShihTzuRansack
  extend ActiveSupport::Concern

  module ClassMethods
    # +flags are one or more FlagShihTzu flags that need to have ransackers defined for
    # ActiveAdmin filtering
    def flag_ransacker(*flags)
      object_class = self
      flags.each do |flag|
        flag = flag.to_s
        ransacker flag.to_sym,
          formatter: proc { |true_false|
            if true_false == "true"
              results = object_class.send("#{flag}")
            else
              results = object_class.send("not_#{flag}")
            end
            results = results.map(&:id)
            results = results.present? ? results : nil
          }, splat_params: true do |parent|
          parent.table[:id]
        end
      end
    end
  end
end

应用程序/管理/支持/ flag_shih_tzu.rb:

# Convenience extension to filter on FlagShizTzu flags in the AA side_bar
module Kandidly
  module ActiveAdmin
    module DSL

      # used like a standard ActiveAdmin::Resource `filter` DSL call, but for FlagShizTzu flags
      # A corresponding `flag_ransacker` call must be made on the model, which must include
      # the FlagShizTzuRansack module defined in app/concerns/models/flag_shih_tzu_ransack.rb
      def flag_filter(flag)
        @resource.flag_ransacker flag.to_sym # call the ransacker builder on the model
        flag = flag.to_s
        filter_name = "#{flag}_in" # we use the 'in' predicate to allow multiple results
        filter filter_name.to_sym,
          :as => :select,
          :label => flag.gsub(/[\s_]+/, ' ').titleize,
          :collection => %w[true false]
      end
    end
  end
end

ActiveAdmin::ResourceDSL.send :include, Kandidly::ActiveAdmin::DSL

然后更清洁,在模型中:

class User
  include FlagShihTzu
  include FlagShihTzuRansack

  # define the flag
  has_flags 1 => :identity_verified

并在资源定义中:

ActiveAdmin.register User do

  flag_filter :identity_verified

end

可能有更优雅的方法实现,但有一个有效的解决方案,我正在继续。希望这能帮助那些投票赞成这个问题的人。 Ransack文档有点不尽如人意。感谢Russ在Jaguar Design Studio上的帖子,以及https://github.com/activerecord-hackery/ransack/issues/36上的评论者,他们帮助我更好地理解了Ransack的工作原理。最后,我不得不深入挖掘宝石以获得我的最终解决方案,但如果没有他们的贡献,我就不知道从哪里开始。