active_admin_filter_extensions 1.0.1 → 1.2.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 +15 -2
- data/lib/active_admin_filter_extensions/filter_defaults.rb +15 -1
- data/lib/active_admin_filter_extensions/notice.rb +8 -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 +18 -1
- data/spec/data_access_spec.rb +30 -0
- data/spec/integration/filter_defaults_spec.rb +27 -0
- data/spec/spec_helper.rb +1 -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: d0c78b8910b00dd6300b7b3eebb5e5538eb534df7bb4ae2f37e206ed7c7e0a9b
|
|
4
|
+
data.tar.gz: 39b6b4f970c13cf26b71837b73463e9b1d13468eb2c6150deb4513759ddad7a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7854f6af602da404309b0734e83ab47b1d60df0a7ed7667abecc4525efb5e0b1d70f9f459bf43a30179e3823bf5621efab8ae969494cec12ffe9830d8214904
|
|
7
|
+
data.tar.gz: a5681dbf451ed707c92c6ce2be45cba12e4f7aaa81854eed31b783fc4335aa65720c26f7e6f83ef67a7d9638a93034bd8a2e20172efb37c9492986bb7464d167
|
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
|
|
|
@@ -348,7 +358,10 @@ Four prints in small type:
|
|
|
348
358
|
never a condition and is never touched.
|
|
349
359
|
* **Anything that reads `params[:q]` for the effective filters** (drill-down links, exports)
|
|
350
360
|
should read `filtering_params` instead - on the rendered page they agree, but only
|
|
351
|
-
`filtering_params` is also right on the non-redirected formats.
|
|
361
|
+
`filtering_params` is also right on the non-redirected formats. A page that translates those
|
|
362
|
+
values before passing them on - renaming a key an input suffixed, turning a sentinel into
|
|
363
|
+
another predicate - wants `applied_filter_params`: the same values as an indifferent copy,
|
|
364
|
+
safe to read by symbol and to mutate. Both are helpers, so a view can call them too.
|
|
352
365
|
|
|
353
366
|
Tested against Active Admin 3.5 on Rails 8.0 and 8.1, under Ruby 3.3, 3.4 and 4.0.
|
|
354
367
|
|
|
@@ -28,7 +28,7 @@ module ActiveAdminFilterExtensions
|
|
|
28
28
|
# #filter_defaults_apply? which lets defaults through on a partly filtered request keeps
|
|
29
29
|
# the values that were actually asked for.
|
|
30
30
|
#
|
|
31
|
-
# @return [Hash
|
|
31
|
+
# @return [Hash] values passed to Ransack
|
|
32
32
|
def filtering_params
|
|
33
33
|
requested = requested_filtering_params
|
|
34
34
|
# Materialized on every path: computing it is also what validates, loudly, that every
|
|
@@ -56,6 +56,20 @@ module ActiveAdminFilterExtensions
|
|
|
56
56
|
defaults.merge(requested || {})
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# The same values, as an indifferent deep copy: readable by symbol and safe to mutate. A page
|
|
60
|
+
# that builds its own collection or its own links usually has to translate those values
|
|
61
|
+
# into whatever it talks to - renaming a key an input suffixed, turning a sentinel into a
|
|
62
|
+
# different predicate - and must not do that to the Hash #filtering_params itself returns,
|
|
63
|
+
# which is also what fills the filters form and decides the canonicalizing redirect.
|
|
64
|
+
#
|
|
65
|
+
# @return [ActiveSupport::HashWithIndifferentAccess]
|
|
66
|
+
def applied_filter_params
|
|
67
|
+
# deep_dup, not just with_indifferent_access: that copies the containers but keeps the
|
|
68
|
+
# value objects, so a `gsub!` or `replace` on a String value would reach back into the
|
|
69
|
+
# request's own and change what the next #filtering_params answers.
|
|
70
|
+
filtering_params.deep_dup.with_indifferent_access
|
|
71
|
+
end
|
|
72
|
+
|
|
59
73
|
def complete_with_groups(defaults)
|
|
60
74
|
with_groups.each do |keys|
|
|
61
75
|
next unless keys.any? { |key| defaults.key?(key) }
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "activeadmin"
|
|
4
|
+
require "active_support/core_ext/object/deep_dup"
|
|
4
5
|
require "active_admin_filter_extensions/version"
|
|
5
6
|
|
|
6
7
|
# The violation messages live in plain i18n - config/locales/en.yml here, overridable by the
|
|
@@ -17,6 +18,21 @@ require "active_admin_filter_extensions/policies"
|
|
|
17
18
|
require "active_admin_filter_extensions/notice"
|
|
18
19
|
|
|
19
20
|
module ActiveAdminFilterExtensions
|
|
21
|
+
class << self
|
|
22
|
+
# Where a filters notice goes when the resource does not say. Active Admin writes action
|
|
23
|
+
# results to :notice, and an app whose layout renders its own key for "why this page is
|
|
24
|
+
# cut down" would otherwise repeat `flash_key:` on every index that declares a notice:
|
|
25
|
+
#
|
|
26
|
+
# ActiveAdminFilterExtensions.notice_flash_key = :notice_message
|
|
27
|
+
#
|
|
28
|
+
# Read when the notice is written, not when the resource is registered, so initializer
|
|
29
|
+
# order does not decide it.
|
|
30
|
+
attr_writer :notice_flash_key
|
|
31
|
+
|
|
32
|
+
def notice_flash_key
|
|
33
|
+
@notice_flash_key ||= :notice
|
|
34
|
+
end
|
|
35
|
+
end
|
|
20
36
|
end
|
|
21
37
|
|
|
22
38
|
# `add_filter` stores whatever options it is given without a whitelist, so `default:` needs no
|
|
@@ -30,7 +46,8 @@ ActiveAdmin.before_load do |_app|
|
|
|
30
46
|
ActiveAdmin::ResourceController.include ActiveAdminFilterExtensions::SearchKeys
|
|
31
47
|
ActiveAdmin::ResourceController.include ActiveAdminFilterExtensions::RequiredFilters
|
|
32
48
|
ActiveAdmin::ResourceController.include ActiveAdminFilterExtensions::FilterDefaults
|
|
33
|
-
ActiveAdmin::ResourceController.helper_method :filtering_params, :
|
|
49
|
+
ActiveAdmin::ResourceController.helper_method :filtering_params, :applied_filter_params,
|
|
50
|
+
:filter_default_values, :visible_filters
|
|
34
51
|
|
|
35
52
|
ActiveAdmin::Resource.prepend ActiveAdminFilterExtensions::ResourceExtension
|
|
36
53
|
ActiveAdmin::ResourceDSL.include ActiveAdminFilterExtensions::Policies::ResourcePolicyDSL
|
data/spec/data_access_spec.rb
CHANGED
|
@@ -53,6 +53,36 @@ RSpec.describe ActiveAdminFilterExtensions::DataAccess do
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
describe "#applied_filter_params" do
|
|
57
|
+
let(:filters) { { position_eq: { extensions: { default: "20" } } } }
|
|
58
|
+
|
|
59
|
+
it "reads by symbol what the request spelled as a String" do
|
|
60
|
+
expect(controller.send(:applied_filter_params)[:position_eq]).to eq("20")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "hands out a copy, so a caller translating the values cannot rewrite the search" do
|
|
64
|
+
applied = controller.send(:applied_filter_params)
|
|
65
|
+
applied[:position_gt] = applied.delete(:position_eq)
|
|
66
|
+
|
|
67
|
+
expect(controller.send(:filtering_params)).to eq("position_eq" => "20")
|
|
68
|
+
expect(searched_with).to eq("position_eq" => "20")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context "when the request carries a value of its own" do
|
|
72
|
+
let(:filters) { { title_cont: {} } }
|
|
73
|
+
# Mutable on purpose: with_indifferent_access alone copies the Hash but hands out this
|
|
74
|
+
# very String, so editing it in place would reach back into the request.
|
|
75
|
+
let(:params) { { q: { "title_cont" => String.new("hello") } } }
|
|
76
|
+
|
|
77
|
+
it "copies the values too, so editing one in place cannot reach the request" do
|
|
78
|
+
controller.send(:applied_filter_params)[:title_cont].replace("bye")
|
|
79
|
+
|
|
80
|
+
expect(controller.send(:filtering_params)).to eq("title_cont" => "hello")
|
|
81
|
+
expect(searched_with).to eq("title_cont" => "hello")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
56
86
|
describe "a Hash default" do
|
|
57
87
|
let(:filters) { { created_at: { as: :date_range, extensions: { default: { gteq: "2020-01-01" } } } } }
|
|
58
88
|
|
|
@@ -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
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/core_ext/object/blank"
|
|
4
|
+
require "active_support/core_ext/object/deep_dup"
|
|
4
5
|
require "active_support/core_ext/hash/indifferent_access"
|
|
5
6
|
require "active_support/hash_with_indifferent_access"
|
|
6
7
|
# Pure Ruby, no Active Admin boot required - `visible_filters` resolves `:if` / `:unless` with it.
|