active_admin_filter_extensions 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +11 -1
- data/lib/active_admin_filter_extensions/notice.rb +8 -2
- data/lib/active_admin_filter_extensions/policies.rb +21 -2
- data/lib/active_admin_filter_extensions/redirect.rb +1 -1
- data/lib/active_admin_filter_extensions/version.rb +1 -1
- data/lib/active_admin_filter_extensions.rb +15 -0
- data/spec/integration/filter_defaults_spec.rb +27 -0
- data/spec/integration/numeric_builtins_spec.rb +13 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d830dd4ae0f8db4ee8e74080ea33d93071aaafc4a7ad30b0b7b5ab67da1ceeca
|
|
4
|
+
data.tar.gz: da0e3e34de9845286118c35f11078230e6add9693f62bd37b63745d1ac6c7cb3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 42761e0adb059a3e53670ce31ed273de6eabfccef2f4339236022e74008229bcc2e8964a4545cd0f54ab20d0b48c6619904938a7d47e7bac114e34c6e2642819
|
|
7
|
+
data.tar.gz: '02581ca6ab8af05d3a5c1aa8e483aef78ae21085432d957609d179efc9c7afe48b8c5f05230c9003acbbfb057a7eab505896174a42fd2442b0af8c45ff28f286'
|
data/README.md
CHANGED
|
@@ -170,7 +170,17 @@ end
|
|
|
170
170
|
```
|
|
171
171
|
|
|
172
172
|
The message is flashed only when the defaults actually took effect. Pass `flash_key:` to use
|
|
173
|
-
something other than `:notice
|
|
173
|
+
something other than `:notice`, or move the default for the whole application once:
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# config/initializers/active_admin_filter_extensions.rb
|
|
177
|
+
ActiveAdminFilterExtensions.notice_flash_key = :filters_notice
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Worth doing where the layout renders its own key for "why this page is cut down": Active Admin
|
|
181
|
+
writes action results to `:notice`, and on a shared key the arriving message wins, so a batch
|
|
182
|
+
action's result would silence the notice. The key is read when the notice is written and
|
|
183
|
+
`flash_key:` on the resource still beats it.
|
|
174
184
|
|
|
175
185
|
## Required filters: the mandatory window
|
|
176
186
|
|
|
@@ -12,7 +12,12 @@ module ActiveAdminFilterExtensions
|
|
|
12
12
|
# message is only flashed when the defaults actually took effect, so it stays quiet once the
|
|
13
13
|
# admin has filtered for themselves.
|
|
14
14
|
module Notice
|
|
15
|
-
|
|
15
|
+
# The key this resource's notice goes under: what it declared, else the app-wide default.
|
|
16
|
+
def self.flash_key_for(config)
|
|
17
|
+
config.default_filters_notice_flash_key || ActiveAdminFilterExtensions.notice_flash_key
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def default_filters_notice(message, flash_key: nil)
|
|
16
21
|
# Declared on the resource rather than closed over, so that the redirect - which answers
|
|
17
22
|
# the request the notice belongs on with a 302 - can find it and carry it across.
|
|
18
23
|
config.default_filters_notice = message
|
|
@@ -27,7 +32,8 @@ module ActiveAdminFilterExtensions
|
|
|
27
32
|
# default slice - the notice on top of it would be noise.
|
|
28
33
|
next if filter_policy_errors.present?
|
|
29
34
|
|
|
30
|
-
flash.now[
|
|
35
|
+
flash.now[Notice.flash_key_for(active_admin_config)] =
|
|
36
|
+
::MethodOrProcHelper.render_in_context(self, message)
|
|
31
37
|
end
|
|
32
38
|
end
|
|
33
39
|
end
|
|
@@ -232,10 +232,29 @@ module ActiveAdminFilterExtensions
|
|
|
232
232
|
|
|
233
233
|
# The filter's own predicate dropdown submits _gt/_lt/_not_eq and friends beside the
|
|
234
234
|
# derived head key; a value policy guarding only the head leaves the same column one
|
|
235
|
-
# dropdown click from unpoliced. Siblings = request keys naming the same attribute
|
|
235
|
+
# dropdown click from unpoliced. Siblings = request keys naming the same attribute,
|
|
236
|
+
# minus the predicates answered by a boolean rather than by a value of the attribute's
|
|
237
|
+
# own type: `?q[customer_id_null]=true` carries "true", which `sanitize: :digits`
|
|
238
|
+
# empties and `validate: :integer` refuses - the reset would then drop a condition a
|
|
239
|
+
# drill-down link spelled out. A boolean predicate the filter declares itself stays
|
|
240
|
+
# policed: that one its author asked for.
|
|
236
241
|
def sibling_predicate_keys(q, keys)
|
|
237
242
|
bases = keys.map { |key| strip_ransack_predicate(key) }
|
|
238
|
-
|
|
243
|
+
declared = keys.map(&:to_s)
|
|
244
|
+
q.keys.select do |key|
|
|
245
|
+
next false unless bases.include?(strip_ransack_predicate(key))
|
|
246
|
+
|
|
247
|
+
declared.include?(key.to_s) || !boolean_predicate?(key)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def boolean_predicate?(name)
|
|
252
|
+
# Without Ransack there is nothing to ask - and nothing is stripped either, so a
|
|
253
|
+
# sibling can only be the declared key itself.
|
|
254
|
+
return false unless defined?(::Ransack::Predicate)
|
|
255
|
+
|
|
256
|
+
predicate = ::Ransack::Predicate.detect_from_string(name.to_s)
|
|
257
|
+
predicate && ::Ransack::Predicate.named(predicate)&.type == :boolean
|
|
239
258
|
end
|
|
240
259
|
|
|
241
260
|
def strip_ransack_predicate(name)
|
|
@@ -71,7 +71,7 @@ module ActiveAdminFilterExtensions
|
|
|
71
71
|
filter_policy_errors.empty? && filter_defaults_engaged?
|
|
72
72
|
# On a shared key - the default is :notice, the same key a batch action writes - the
|
|
73
73
|
# arriving message wins: it names what just happened, the notice can wait.
|
|
74
|
-
key = active_admin_config
|
|
74
|
+
key = Notice.flash_key_for(active_admin_config)
|
|
75
75
|
flash[key] = ::MethodOrProcHelper.render_in_context(self, message) if flash[key].blank?
|
|
76
76
|
end
|
|
77
77
|
|
|
@@ -17,6 +17,21 @@ require "active_admin_filter_extensions/policies"
|
|
|
17
17
|
require "active_admin_filter_extensions/notice"
|
|
18
18
|
|
|
19
19
|
module ActiveAdminFilterExtensions
|
|
20
|
+
class << self
|
|
21
|
+
# Where a filters notice goes when the resource does not say. Active Admin writes action
|
|
22
|
+
# results to :notice, and an app whose layout renders its own key for "why this page is
|
|
23
|
+
# cut down" would otherwise repeat `flash_key:` on every index that declares a notice:
|
|
24
|
+
#
|
|
25
|
+
# ActiveAdminFilterExtensions.notice_flash_key = :notice_message
|
|
26
|
+
#
|
|
27
|
+
# Read when the notice is written, not when the resource is registered, so initializer
|
|
28
|
+
# order does not decide it.
|
|
29
|
+
attr_writer :notice_flash_key
|
|
30
|
+
|
|
31
|
+
def notice_flash_key
|
|
32
|
+
@notice_flash_key ||= :notice
|
|
33
|
+
end
|
|
34
|
+
end
|
|
20
35
|
end
|
|
21
36
|
|
|
22
37
|
# `add_filter` stores whatever options it is given without a whitelist, so `default:` needs no
|
|
@@ -191,6 +191,33 @@ RSpec.describe "Filter default values", type: :feature do
|
|
|
191
191
|
|
|
192
192
|
expect(page).to have_no_content("Showing published posts by default")
|
|
193
193
|
end
|
|
194
|
+
|
|
195
|
+
describe "the app-wide key" do
|
|
196
|
+
# Deliberately a key no resource declares, so an example cannot pass by coincidence:
|
|
197
|
+
# NoticePost's own key is :filters_notice.
|
|
198
|
+
around do |example|
|
|
199
|
+
previous = ActiveAdminFilterExtensions.notice_flash_key
|
|
200
|
+
ActiveAdminFilterExtensions.notice_flash_key = :app_wide_notice
|
|
201
|
+
example.run
|
|
202
|
+
ActiveAdminFilterExtensions.notice_flash_key = previous
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it "takes a notice that named no key of its own" do
|
|
206
|
+
# The resource says only the message; the app said where its notices go. Asserted on
|
|
207
|
+
# the rendered flash class, which is what the layout keys off.
|
|
208
|
+
visit "/admin/colliding_notice_posts"
|
|
209
|
+
|
|
210
|
+
expect(page).to have_css(".flash_app_wide_notice", text: "Showing published posts by default")
|
|
211
|
+
expect(page).to have_no_css(".flash_notice")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "loses to the key the resource declared" do
|
|
215
|
+
visit "/admin/notice_posts"
|
|
216
|
+
|
|
217
|
+
expect(page).to have_css(".flash_filters_notice", text: "Showing published posts by default")
|
|
218
|
+
expect(page).to have_no_css(".flash_app_wide_notice")
|
|
219
|
+
end
|
|
220
|
+
end
|
|
194
221
|
end
|
|
195
222
|
|
|
196
223
|
describe "requests that carry no q of their own" do
|
|
@@ -78,6 +78,19 @@ RSpec.describe "built-in numeric policies", type: :feature do
|
|
|
78
78
|
expect(page).to have_no_content("drop me")
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
it "leaves a boolean predicate of the same attribute to Ransack" do
|
|
82
|
+
# `?q[position_null]=true` is answered by "true", not by a value of the column's own
|
|
83
|
+
# type: the digits sanitizer would empty it and the integer validator refuse it, and the
|
|
84
|
+
# reset would drop a condition the admin's own drill-down link spelled out.
|
|
85
|
+
Post.create!(title: "no position", body: "no position body", status: "draft")
|
|
86
|
+
|
|
87
|
+
visit "/admin/integer_posts?q%5Bposition_null%5D=true"
|
|
88
|
+
|
|
89
|
+
expect(query_of(page.current_url)["q"]).to eq("position_null" => "true")
|
|
90
|
+
expect(page).to have_content("no position")
|
|
91
|
+
expect(page).to have_no_content("keep me")
|
|
92
|
+
end
|
|
93
|
+
|
|
81
94
|
it "sanitizes a sibling predicate too" do
|
|
82
95
|
visit "/admin/integer_posts?q%5Bposition_lt%5D=2%200a"
|
|
83
96
|
|