diff --git a/lib/active_admin_filter_extensions/policies.rb b/lib/active_admin_filter_extensions/policies.rb index 109909e..1164b15 100644 --- a/lib/active_admin_filter_extensions/policies.rb +++ b/lib/active_admin_filter_extensions/policies.rb @@ -232,10 +232,29 @@ def with_groups # The filter's own predicate dropdown submits _gt/_lt/_not_eq and friends beside the # derived head key; a value policy guarding only the head leaves the same column one - # dropdown click from unpoliced. Siblings = request keys naming the same attribute. + # dropdown click from unpoliced. Siblings = request keys naming the same attribute, + # minus the predicates answered by a boolean rather than by a value of the attribute's + # own type: `?q[customer_id_null]=true` carries "true", which `sanitize: :digits` + # empties and `validate: :integer` refuses - the reset would then drop a condition a + # drill-down link spelled out. A boolean predicate the filter declares itself stays + # policed: that one its author asked for. def sibling_predicate_keys(q, keys) bases = keys.map { |key| strip_ransack_predicate(key) } - q.keys.select { |key| bases.include?(strip_ransack_predicate(key)) } + declared = keys.map(&:to_s) + q.keys.select do |key| + next false unless bases.include?(strip_ransack_predicate(key)) + + declared.include?(key.to_s) || !boolean_predicate?(key) + end + end + + def boolean_predicate?(name) + # Without Ransack there is nothing to ask - and nothing is stripped either, so a + # sibling can only be the declared key itself. + return false unless defined?(::Ransack::Predicate) + + predicate = ::Ransack::Predicate.detect_from_string(name.to_s) + predicate && ::Ransack::Predicate.named(predicate)&.type == :boolean end def strip_ransack_predicate(name) diff --git a/lib/active_admin_filter_extensions/version.rb b/lib/active_admin_filter_extensions/version.rb index 4f3ae43..560ba51 100644 --- a/lib/active_admin_filter_extensions/version.rb +++ b/lib/active_admin_filter_extensions/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module ActiveAdminFilterExtensions - VERSION = "1.0.0" + VERSION = "1.0.1" end diff --git a/spec/integration/numeric_builtins_spec.rb b/spec/integration/numeric_builtins_spec.rb index d222ab9..c6bc500 100644 --- a/spec/integration/numeric_builtins_spec.rb +++ b/spec/integration/numeric_builtins_spec.rb @@ -78,6 +78,19 @@ def query_of(url) expect(page).to have_no_content("drop me") end + it "leaves a boolean predicate of the same attribute to Ransack" do + # `?q[position_null]=true` is answered by "true", not by a value of the column's own + # type: the digits sanitizer would empty it and the integer validator refuse it, and the + # reset would drop a condition the admin's own drill-down link spelled out. + Post.create!(title: "no position", body: "no position body", status: "draft") + + visit "/admin/integer_posts?q%5Bposition_null%5D=true" + + expect(query_of(page.current_url)["q"]).to eq("position_null" => "true") + expect(page).to have_content("no position") + expect(page).to have_no_content("keep me") + end + it "sanitizes a sibling predicate too" do visit "/admin/integer_posts?q%5Bposition_lt%5D=2%200a"