active_admin_scoped_collection_actions 1.0.1 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f4cfa1626f8c35e683c20d35f7594f5fc2e69e698b7f306ca86912525b272eb
4
- data.tar.gz: ebb1cf80d7894879aa5a3f2556e83b8f837c3d8621ad6137d7795fe2249d20d1
3
+ metadata.gz: ad87e54407e268139e8d45334645748dd0a02a22fdecd73bb6ea07bbf8f4ebe5
4
+ data.tar.gz: a4f1a7c3d13f6b0d56f33b5988caf0add3638e3b65d0d49d00b4038aea720d40
5
5
  SHA512:
6
- metadata.gz: b2dd83b556c2734c8eda0afe354cb3c93c7610fe6354f922595ad6c6c890a3d1ec7dcf320c92597fe10ad2754fde45b0da83e2575eb0ae911da01c92314290df
7
- data.tar.gz: ef7839e57ec7b247dc1952ee47c0ffb121042b1952949655d01b0a36ae1339c2b2cf24414c7ab3e007b994fb3ef45a87c4ecdef7e35c1431319712d8ac443cf9
6
+ metadata.gz: b1177f3b81e88e75871e76e08089f33ad6626b3e161963c12e59f64f83852add74271443a513a388e97e1648c8e5446040bd0a687863c4f4d398f636f7a4b377
7
+ data.tar.gz: 9f7eb669df1c427416300a75127a786b151a46f80f65bb40e8979ada2b666c9c80cfe045336f2099b09d284367723f2d1b02874be36c68de22ddd39e7fb43e92
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## ActiveAdmin Scoped Collection Actions 2.1.0 (October 02, 2026) ##
2
+
3
+ * New action option `:confirm_submit` - turns the fields dialog into two steps, the second one replaces the
4
+ fields with amount of affected records and the changes to be applied
5
+ * New action option `:confirm_summary` - tells the user how many records the action affects
6
+ * Form field can be defined as `{type: 'text', class: 'my-widget'}` to render input with own class
7
+ * **The required Ruby version is now 3.3** — Ruby 3.1 and 3.2 are EOL; stay on 2.0.0 for those
8
+ * Tested against Ruby 3.3, 3.4 and 4.0 with Rails 8.0 and 8.1; Rails 7.1/7.2 left the CI matrix but are not excluded by any constraint
9
+ * The packaged gem ships only `lib`, `vendor`, `config` and the docs — no spec suite, no screenshots
10
+
11
+ ## ActiveAdmin Scoped Collection Actions 2.0.0 (April 07, 2026) ##
12
+
13
+ * compatibility with ActiveAdmin 3.5, including its jQuery UI dialog changes
14
+ * fixed the Sprockets manifest error on Rails 8.0+
15
+ * New action option `:if` to conditionally hide a collection action (@qoqa #48)
16
+ * `:confirm` accepts a proc (@qoqa #47)
17
+ * dropped EOL Ruby and Rails versions
18
+
1
19
  ## ActiveAdmin Scoped Collection Actions 1.0.1 (March 02, 2024) ##
2
20
 
3
21
  * compatibility with ActiveAdmin 3.x (@oskarpearson #43)
@@ -10,12 +28,10 @@
10
28
 
11
29
  * Swedish locale (@buren #27)
12
30
 
13
-
14
31
  ## ActiveAdmin Scoped Collection Actions 0.3.3 (July 17, 2017) ##
15
32
 
16
33
  * Fixed compatibility with Rails 5.1
17
34
 
18
-
19
35
  ## ActiveAdmin Scoped Collection Actions 0.3.2 (July 17, 2017) ##
20
36
 
21
37
  * Added I18n support.
data/README.md CHANGED
@@ -1,18 +1,40 @@
1
- [![Build Status](https://img.shields.io/travis/activeadmin-plugins/active_admin_scoped_collection_actions.svg)](https://travis-ci.org/activeadmin-plugins/active_admin_scoped_collection_actions)
1
+ [![Build Status](https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions/actions/workflows/ci.yml/badge.svg)](https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions/actions/workflows/ci.yml)
2
+ ![Coverage](https://img.shields.io/endpoint?url=https://activeadmin-plugins.github.io/active_admin_scoped_collection_actions/badge.json)
2
3
 
3
4
  # ActiveAdmin Scoped Collection Actions
5
+
4
6
  Plugin for ActiveAdmin. Provides batch Update and Delete for scoped_collection (Filters + Scope) across all pages.
5
7
 
6
- ![Step 1](/screenshots/sidebar.png)
8
+ ![Collection actions in the ActiveAdmin sidebar](/screenshots/sidebar.png)
9
+
10
+ ![The confirmation dialog with the update form](/screenshots/pupup.png)
7
11
 
8
- ![Step 1](/screenshots/pupup.png)
12
+ ## Contents
9
13
 
14
+ - [Description](#description)
15
+ - [Installation](#installation)
16
+ - [Usage](#usage)
17
+ - [Update action](#update-action)
18
+ - [Custom actions](#custom-actions)
19
+ - [Details and settings](#details-and-settings)
20
+ - [Why don't I see the sidebar with collection actions?](#why-dont-i-see-the-sidebar-with-collection-actions)
21
+ - [Can I use my own handler for the update and delete actions?](#can-i-use-my-own-handler-for-the-update-and-delete-actions)
22
+ - [How can I rename a button?](#how-can-i-rename-a-button)
23
+ - [How can I change the modal dialog title?](#how-can-i-change-the-modal-dialog-title)
24
+ - [How can I tell the user how many records are affected?](#how-can-i-tell-the-user-how-many-records-are-affected)
25
+ - [How can I ask for an extra confirmation before the form is submitted?](#how-can-i-ask-for-an-extra-confirmation-before-the-form-is-submitted)
26
+ - [Can I replace the pop-up form with my own?](#can-i-replace-the-pop-up-form-with-my-own)
27
+ - [How do I notify the user about success and errors?](#how-do-i-notify-the-user-about-success-and-errors)
28
+ - [Can I perform an action only on selected items?](#can-i-perform-an-action-only-on-selected-items)
29
+ - [License](#license)
10
30
 
11
- # Description
31
+ ## Description
12
32
 
13
- This gem give you ability to perform various batch actions on any filtered (or scoped) resource. Action applies to all records across all pages. It is similar to ActiveAdmin batch action, but affects all filtered records. This is usefull if you want to delete or update a lot of records in one click.
33
+ This gem gives you the ability to perform various batch actions on any filtered (or scoped) resource. An action
34
+ applies to all records across all pages. It is similar to an ActiveAdmin batch action, but affects all filtered
35
+ records. This is useful when you want to delete or update a lot of records in one click.
14
36
 
15
- # Install
37
+ ## Installation
16
38
 
17
39
  Add this line to your application's Gemfile:
18
40
 
@@ -25,23 +47,23 @@ gem 'active_admin_scoped_collection_actions', github: 'activeadmin-plugins/activ
25
47
 
26
48
  And then execute:
27
49
 
28
- ```
29
- $ bundle
50
+ ```sh
51
+ bundle
30
52
  ```
31
53
 
32
- Add the following line at the end of "app/assets/javascript/active_admin.js":
54
+ Add the following line at the end of `app/assets/javascript/active_admin.js`:
33
55
 
34
56
  ```javascript
35
57
  //= require active_admin_scoped_collection_actions
36
58
  ```
37
59
 
38
- Also include CSS in "app/assets/stylesheets/active_admin.css.scss"
60
+ Also include the CSS in `app/assets/stylesheets/active_admin.css.scss`:
39
61
 
40
- ```css
62
+ ```scss
41
63
  @import "active_admin_scoped_collection_actions";
42
64
  ```
43
65
 
44
- # Usage
66
+ ## Usage
45
67
 
46
68
  Usually you need two standard actions: Delete and Update.
47
69
 
@@ -65,37 +87,53 @@ ActiveAdmin.register Post do
65
87
  end
66
88
  ```
67
89
 
68
- **Important**: Visit Posts page with your browser and you will see no changes. Now, perform any filter with the Filters sidebar. Only after you filter will you see a delete button. It will be in sidebar under Filters.
90
+ > **Important**
91
+ > Visit the Posts page with your browser and you will see no changes. Now, perform any filter with the Filters
92
+ > sidebar. Only after you filter will you see a delete button. It will be in the sidebar under Filters.
69
93
 
70
94
  ### Update action
71
95
 
72
- Update is second standard action and is more complex. It has "form" hash wrapped in Proc:
96
+ Update is the second standard action and is more complex. It has a `form` hash wrapped in a Proc:
73
97
 
74
98
  ```ruby
75
- scoped_collection_action :scoped_collection_update, form: -> do
76
- { name: 'text',
77
- diagonal: 'text',
78
- manufactured_at: 'datepicker',
79
- vendor_id: Vendor.all.map { |region| [region.name, region.id] },
80
- has_3g: [['Yes', 't'], ['No', 'f']]
81
- }
82
- end
99
+ scoped_collection_action :scoped_collection_update, form: -> do
100
+ { name: 'text',
101
+ diagonal: 'text',
102
+ manufactured_at: 'datepicker',
103
+ vendor_id: Vendor.all.map { |region| [region.name, region.id] },
104
+ has_3g: [['Yes', 't'], ['No', 'f']] }
105
+ end
83
106
  ```
84
107
 
85
- In this example Phone model has fields:
86
- * name - varchar string
87
- * diagonal - integer(of float)
88
- * manufactured_at - datetime
89
- * vendor_id - association "belongs_to :vendor, class_name: 'Vendor', foreign_key: :vendor_id"
90
- * has_3g - boolean
108
+ In this example the Phone model has fields:
91
109
 
92
- Parameter "form" is a proc object which returns Hash. It defines what fields you want to be able to update. Hash keys are column names in database. Hash values are a types of HTML inputs. We support only "text", "datepicker" and "selectbox". If you want something more complex - you can build your own forms.
110
+ - `name` — varchar string
111
+ - `diagonal` — integer (or float)
112
+ - `manufactured_at` — datetime
113
+ - `vendor_id` — association `belongs_to :vendor, class_name: 'Vendor', foreign_key: :vendor_id`
114
+ - `has_3g` — boolean
115
+
116
+ The `form` parameter is a Proc which returns a Hash. It defines which fields you want to be able to update. Hash
117
+ keys are column names in the database, hash values are types of HTML inputs. Supported values are `text`,
118
+ `checkbox`, `datepicker`, and an Array of options for a selectbox. If you want something more complex, you can
119
+ build your own forms.
120
+
121
+ A field value can also be a Hash, `{type: 'text', class: 'my-widget'}`. Such a field is rendered as a plain input
122
+ with your own class on it, so you can turn it into any widget (datetime picker, autocomplete, etc.) with your own
123
+ JavaScript. The dialog triggers the `mass_update_modal_dialog:after_open` event on `body` with the form as an
124
+ argument — this is the place to initialize your widgets:
125
+
126
+ ```javascript
127
+ $(document).on('mass_update_modal_dialog:after_open', function (event, form) {
128
+ $(form).find('input.my-widget').myWidget();
129
+ });
130
+ ```
93
131
 
94
- # Custom Actions
132
+ ## Custom actions
95
133
 
96
- Example: We have Phone resource and it has column "manufactured_at". We need an action which will erase this date.
134
+ Example: we have a Phone resource with a `manufactured_at` column. We need an action which will erase this date.
97
135
 
98
- In ActiveAdmin resource:
136
+ In the ActiveAdmin resource:
99
137
 
100
138
  ```ruby
101
139
  ActiveAdmin.register Phone do
@@ -111,13 +149,12 @@ ActiveAdmin.register Phone do
111
149
  end
112
150
  ```
113
151
 
114
- This simple code will create a new button "Erase date" in sidebar. After clicking this button, the user will see confirm message "Are you sure?". After confirming, all filtered records will be updated.
115
-
152
+ This simple code will create a new "Erase date" button in the sidebar. After clicking it, the user will see the
153
+ confirmation message "Are you sure?". After confirming, all filtered records will be updated.
116
154
 
117
- # Details and Settings
155
+ ## Details and settings
118
156
 
119
-
120
- ### Why I don't see Sidebar with Collection Actions.
157
+ ### Why don't I see the sidebar with collection actions?
121
158
 
122
159
  Sidebar visibility by default depends on several things.
123
160
 
@@ -127,19 +164,18 @@ First you must set:
127
164
  config.batch_actions = true
128
165
  ```
129
166
 
130
- Actually, inside of this Gem we use "batch_actions". So without them Collection Actions wouldn't work.
167
+ Internally this gem uses `batch_actions`, so without them collection actions will not work.
131
168
 
132
169
  ```ruby
133
170
  scoped_collection_action :something_here
134
171
  ```
135
172
 
136
- You resource should have some collection actions. If it doesn't have any, the sidebar will not appear.
137
-
138
- By default we dont allow perform actions on **all** the records. We want protect you from accidental deleting.
173
+ Your resource should have some collection actions. If it does not have any, the sidebar will not appear.
139
174
 
140
- Sidebar with buttons will appear only after you perform filtering or scopes on resource records.
175
+ By default we do not allow performing actions on **all** the records — this protects you from accidental
176
+ deletion. The sidebar with buttons appears only after you filter or scope the resource records.
141
177
 
142
- And lastly you can manage sidebar visibility by resource config:
178
+ And lastly, you can manage sidebar visibility through the resource config:
143
179
 
144
180
  ```ruby
145
181
  # Always
@@ -149,85 +185,163 @@ config.scoped_collection_actions_if = -> { params[:scope] }
149
185
  # etc.
150
186
  ```
151
187
 
152
- ### Can I use my handler on update/delete action?
188
+ You can also manage the visibility of each action individually:
189
+
190
+ ```ruby
191
+ scoped_collection_action :scoped_collection_destroy, if: proc { can? :destroy, Blog }
192
+ ```
193
+
194
+ ### Can I use my own handler for the update and delete actions?
153
195
 
154
- You can pass block to default actions update and delete.
155
- And do custom redirect after it. Use `render` (location: 'something') instead of `redirect_to()`.
196
+ You can pass a block to the default update and delete actions, and do a custom redirect after it. Use `head`
197
+ with a `location:` instead of `redirect_to`.
156
198
 
157
- This example renders form which allows to change `name` field. And after it do redirect to dashboard page.
199
+ This example renders a form which allows changing the `name` field, and after that redirects to the dashboard
200
+ page:
158
201
 
159
202
  ```ruby
160
- scoped_collection_action :scoped_collection_update,
161
- form: -> {
162
- {name: 'text'}
163
- } do
164
- scoped_collection_records.update_all(name: params[:changes][:name])
165
- flash[:notice] = 'Name successfully changed.'
166
- render nothing: true, status: :no_content, location: admin_dashboard_path
167
- end
203
+ scoped_collection_action :scoped_collection_update,
204
+ form: -> {
205
+ { name: 'text' }
206
+ } do
207
+ scoped_collection_records.update_all(name: params[:changes][:name])
208
+ flash[:notice] = 'Name successfully changed.'
209
+ head :no_content, location: admin_dashboard_path
210
+ end
168
211
  ```
169
212
 
213
+ ### How can I rename a button?
170
214
 
171
- ### How can I rename button?
172
-
173
- Every scoped_collection_action has option `:title`.
215
+ Every `scoped_collection_action` has the `:title` option.
174
216
 
175
217
  Example:
176
218
 
177
219
  ```ruby
178
- scoped_collection_action :erase_date, title: 'Nullify' do
179
- scoped_collection_records.update_all(manufactured_at: nil)
180
- end
220
+ scoped_collection_action :erase_date, title: 'Nullify' do
221
+ scoped_collection_records.update_all(manufactured_at: nil)
222
+ end
223
+ ```
224
+
225
+ ### How can I change the modal dialog title?
226
+
227
+ Similar to the button title — use the `:confirm` option:
228
+
229
+ ```ruby
230
+ scoped_collection_action :scoped_collection_destroy, confirm: 'Delete all phones?'
231
+ ```
232
+
233
+ ### How can I tell the user how many records are affected?
234
+
235
+ The `:confirm_summary` option adds a sentence with the number of affected records to the dialog:
236
+
237
+ ```ruby
238
+ scoped_collection_action :scoped_collection_destroy, confirm_summary: true
239
+ ```
240
+
241
+ ```text
242
+ Delete all?
243
+
244
+ You are going to delete 100500 record(s).
245
+
246
+ [ OK ] [ Cancel ]
181
247
  ```
182
248
 
249
+ `true` uses the default text — the `confirm_destroy_summary` locale key for `:scoped_collection_destroy` and
250
+ `confirm_action_summary` for any other action. Pass a String or a Proc to use your own text. `{count}` in the
251
+ text is replaced with the number.
252
+
253
+ The amount is requested from the server with the same filters, scope and checked records the action itself will
254
+ use, so it costs one extra COUNT query. Until the answer arrives the message is not displayed at all — there is
255
+ a spinner in its place and OK is disabled, so nothing can be confirmed before the user sees what they confirm.
256
+
257
+ When that request happens depends on which option is set:
258
+
259
+ - neither `:confirm_summary` nor `:confirm_submit` — nothing is counted and no request is made;
260
+ - `:confirm_summary` — once, when the dialog is opened;
261
+ - `:confirm_submit` — once, when OK is pressed and the second step appears, and not at all when the user checked
262
+ no fields, because then there is nothing to confirm and the action runs straight away.
183
263
 
184
- ### How can I modify modal dialog title?
264
+ Setting both does **not** make two requests. `:confirm_submit` takes over, and `:confirm_summary` only supplies
265
+ the wording for the sentence on its second step.
185
266
 
186
- Similar to button title. Use option `:confirm`
267
+ Both options in one flow — an update action with `confirm_submit:` and the summary it shows, from the sidebar
268
+ button to the result:
269
+
270
+ ![confirm_summary and confirm_submit](/screenshots/example_confirm_summary.png)
271
+
272
+ ### How can I ask for an extra confirmation before the form is submitted?
273
+
274
+ An action without a form is confirmed by the modal dialog itself — it has no fields and the user just presses OK.
275
+ An action with a form applies changes immediately after OK is pressed. Use the `:confirm_submit` option to turn
276
+ such an action into two steps, where the second one replaces the fields with a summary of what is going to
277
+ happen:
187
278
 
188
279
  ```ruby
189
- scoped_collection_action :scoped_collection_destroy, confirm: 'Delete all phones?'
280
+ scoped_collection_action :scoped_collection_update,
281
+ confirm_submit: true,
282
+ form: -> { { body: 'text' } }
190
283
  ```
191
284
 
285
+ ```text
286
+ Are you sure?
287
+
288
+ You are going to update 100500 record(s) with:
289
+ Body: Text here...
290
+ Author_id: Jane
192
291
 
193
- ### Can I replace you pop-up form with my own?
292
+ [ OK ] [ Back ]
293
+ ```
194
294
 
195
- Yes. But also you must take care of mandatory parameters passed to the server.
295
+ "Back" returns to the form with everything the user filled in still there. Only the fields the user checked are
296
+ listed, and when nothing is checked there is nothing to confirm — the action is performed right away. The text of
297
+ the sentence is the `confirm_submit_summary` locale key, or the `:confirm_summary` option when you set it.
196
298
 
299
+ `true` shows the default message ("Are you sure?"). Pass a String or a Proc to use your own:
197
300
 
198
301
  ```ruby
199
- scoped_collection_action :my_pop_action, class: 'my_popup'
302
+ scoped_collection_action :scoped_collection_update,
303
+ confirm_submit: 'Update all filtered phones?',
304
+ form: -> { { name: 'text' } }
200
305
  ```
201
306
 
202
- Now in HTML page, you have button:
307
+ ### Can I replace the pop-up form with my own?
308
+
309
+ Yes, but you must also take care of the mandatory parameters passed to the server.
310
+
311
+ ```ruby
312
+ scoped_collection_action :my_pop_action, class: 'my_popup'
313
+ ```
314
+
315
+ Now in the HTML page you have a button:
203
316
 
204
317
  ```html
205
- <button class="my_popup" data="{&quot;auth_token&quot;:&quot;2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0=&quot;,&quot;batch_action&quot;:&quot;my_pop_action&quot;,&quot;confirm&quot;:&quot;Are you sure?&quot;}">My pop action</button>
318
+ <button class="my_popup" data="{&quot;auth_token&quot;:&quot;2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0=&quot;,&quot;batch_action&quot;:&quot;my_pop_action&quot;,&quot;confirm&quot;:&quot;Are you sure?&quot;}">My pop action</button>
206
319
  ```
207
320
 
208
- Without handler, clicking on the button does nothing.
321
+ Without a handler, clicking the button does nothing.
209
322
 
210
323
  You can render the form in any way you want:
211
- - It can be some popup(Fancybox, Simplemodal, etc.), or some inline collapsible form.
212
- - It can even be a separate full-page.
213
324
 
214
- One thing is important - how you will send data to server. Generally it should be:
325
+ - it can be a popup (Fancybox, Simplemodal, etc.), or an inline collapsible form;
326
+ - it can even be a separate full page.
215
327
 
216
- POST request
328
+ One thing is important — how you send the data to the server. It should be:
217
329
 
218
- URL: /admin/collection_path/batch_action
330
+ | | |
331
+ | --- | --- |
332
+ | Method | `POST` |
333
+ | URL | `/admin/collection_path/batch_action` |
334
+ | Query string | identical to the current page |
219
335
 
220
- with GET params identical to current page
221
-
222
- The easiest way to get them is:
336
+ The easiest way to build it is:
223
337
 
224
338
  ```javascript
225
339
  url = window.location.pathname + '/batch_action' + window.location.search
226
340
  ```
227
341
 
228
- And Request body params should be like:
342
+ And the request body params should look like:
229
343
 
230
- ```
344
+ ```text
231
345
  changes[manufactured_at] = "2015-07-21 18:11"
232
346
  changes[diagonal] = "7"
233
347
  changes[some_filed_name]='new value'
@@ -235,69 +349,73 @@ authenticity_token = "2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0="
235
349
  batch_action = "my_pop_action"
236
350
  ```
237
351
 
238
- ```authenticity_token``` and ```batch_action``` you can get from data-attribute of the Button.
239
-
352
+ `authenticity_token` and `batch_action` can be taken from the data attribute of the button.
240
353
 
241
354
  Example in JavaScript:
242
355
 
243
356
  ```javascript
244
- url = window.location.pathname + '/batch_action' + window.location.search
245
- form_data = {
246
- changes: {"manufactured_at": "2015-07-21 18:11", "diagonal": "7"},
247
- collection_selection: [],
248
- authenticity_token: "2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0=",
249
- batch_action: "my_pop_action"
250
- }
251
- $.post(url, form_data).always () ->
252
- window.location.reload()
357
+ url = window.location.pathname + '/batch_action' + window.location.search
358
+ form_data = {
359
+ changes: { "manufactured_at": "2015-07-21 18:11", "diagonal": "7" },
360
+ collection_selection: [],
361
+ authenticity_token: "2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0=",
362
+ batch_action: "my_pop_action"
363
+ }
364
+ $.post(url, form_data).always(function () {
365
+ window.location.reload();
366
+ });
253
367
  ```
254
368
 
255
- ### How notify user about success and error operations?
369
+ ### How do I notify the user about success and errors?
256
370
 
257
- We recommend using Rails Flash messages.
371
+ We recommend using Rails flash messages.
258
372
 
259
- Example with updating phone diagonal attribute. In this case model Phone has validation:
373
+ Example with updating the phone diagonal attribute. In this case the Phone model has a validation:
260
374
 
261
375
  ```ruby
262
- class Phone < ActiveRecord::Base
263
- validates :diagonal, numericality: { only_integer: true }
264
- end
376
+ class Phone < ActiveRecord::Base
377
+ validates :diagonal, numericality: { only_integer: true }
378
+ end
265
379
  ```
266
380
 
267
381
  ```ruby
268
- scoped_collection_action :change_diagonal, form: { diagonal: 'text' } do
269
- errors = []
270
- scoped_collection_records.find_each do |record|
271
- errors << "#{record.errors.full_messages.join('. ')}" unless record.update(diagonal: params[:changes][:diagonal])
272
- end
273
- if errors.empty?
274
- flash[:notice] = 'Diagonal changed successfully'
275
- else
276
- flash[:error] = errors.join('. ')
277
- end
278
- render nothing: true, status: :no_content
382
+ scoped_collection_action :change_diagonal, form: { diagonal: 'text' } do
383
+ errors = []
384
+ scoped_collection_records.find_each do |record|
385
+ errors << record.errors.full_messages.join('. ') unless record.update(diagonal: params[:changes][:diagonal])
279
386
  end
387
+ if errors.empty?
388
+ flash[:notice] = 'Diagonal changed successfully'
389
+ else
390
+ flash[:error] = errors.join('. ')
391
+ end
392
+ head :no_content
393
+ end
280
394
  ```
281
395
 
282
- When you try to update diagonal with "5.6" you will see flash error:
396
+ When you try to update the diagonal with "5.6" you will see a flash error:
283
397
 
284
- ```
398
+ ```text
285
399
  Diagonal must be an integer.
286
400
  ```
287
401
 
288
- But if you use your custom popup, you can show messages with JS.
289
-
402
+ But if you use your own custom popup, you can show messages with JS.
290
403
 
291
- ### Can I perform action only on selected items?
404
+ ### Can I perform an action only on selected items?
292
405
 
293
- Standard index-page of a resource with batch_action enabled has selectable column.
406
+ The standard index page of a resource with `batch_action` enabled has a selectable column.
294
407
 
295
- If you checked some items and parform any Collection Action, the handler will take care of it. If you write custom actions, you should do like this:
408
+ If you have checked some items and perform any collection action, the handler will take care of it. If you write
409
+ custom actions, you should do it like this:
296
410
 
297
411
  ```ruby
298
- scoped_collection_action :do_something do
299
- scoped_collection_records.find_each do |record|
300
- record.update(name: 'x')
301
- end
412
+ scoped_collection_action :do_something do
413
+ scoped_collection_records.find_each do |record|
414
+ record.update(name: 'x')
302
415
  end
416
+ end
303
417
  ```
418
+
419
+ ## License
420
+
421
+ Released under the [MIT License](LICENSE.txt).
@@ -2,6 +2,9 @@ de:
2
2
  active_admin_scoped_collection_actions:
3
3
  sidebar_msg: 'Diese Aktion betrifft alle ausgewählten Datensätze. Wenn keine ausgewählt sind, betrifft sie alle gefilterten Datensätze.'
4
4
  confirm_action_message: 'Sind Sie sicher?'
5
+ confirm_submit_summary: 'Sie werden {count} Datensätze aktualisieren mit:'
6
+ confirm_destroy_summary: 'Sie werden {count} Datensätze löschen.'
7
+ confirm_action_summary: 'Sie werden diese Aktion für {count} Datensätze ausführen.'
5
8
  actions:
6
9
  delete: 'Löschen'
7
10
  update: 'Aktualisieren'
@@ -2,6 +2,10 @@ en:
2
2
  active_admin_scoped_collection_actions:
3
3
  sidebar_msg: 'This batch operations affect selected records. Or if none is selected, it will involve all records by current filters and scopes.'
4
4
  confirm_action_message: 'Are you sure?'
5
+ # {count} is replaced with amount of affected records
6
+ confirm_submit_summary: 'You are going to update {count} record(s) with:'
7
+ confirm_destroy_summary: 'You are going to delete {count} record(s).'
8
+ confirm_action_summary: 'You are going to perform this action on {count} record(s).'
5
9
  actions:
6
10
  delete: 'Delete batch'
7
11
  update: 'Update batch'
@@ -2,6 +2,9 @@ es:
2
2
  active_admin_scoped_collection_actions:
3
3
  sidebar_msg: 'Estas operaciones afectan las filas seleccionadas o en caso que no halla seleccionado ninguna, afectará a todo los registros afectados por los filtros aplicados.'
4
4
  confirm_action_message: '¿Está seguro que quiere realizar esta acción?'
5
+ confirm_submit_summary: 'Va a actualizar {count} registro(s) con:'
6
+ confirm_destroy_summary: 'Va a eliminar {count} registro(s).'
7
+ confirm_action_summary: 'Va a realizar esta acción sobre {count} registro(s).'
5
8
  actions:
6
9
  delete: 'Batch Borrado'
7
10
  update: 'Batch Actualizado'
@@ -2,6 +2,9 @@ sv:
2
2
  active_admin_scoped_collection_actions:
3
3
  sidebar_msg: 'Den här bulkoperationen påverkar markerade poster. Eller om inget är markerat så kommer det påverka alla poster i nuvarande filter och scope.'
4
4
  confirm_action_message: 'Är du säker?'
5
+ confirm_submit_summary: 'Du kommer att uppdatera {count} post(er) med:'
6
+ confirm_destroy_summary: 'Du kommer att ta bort {count} post(er).'
7
+ confirm_action_summary: 'Du kommer att utföra den här åtgärden på {count} post(er).'
5
8
  actions:
6
9
  delete: 'Ta bort batch'
7
10
  update: 'Uppdatera batch'
@@ -2,6 +2,7 @@ module ActiveAdminScopedCollectionActions
2
2
  module DSL
3
3
 
4
4
  def scoped_collection_action(name, options = {}, &block)
5
+ add_scoped_collection_records_count_action
5
6
  if name == :scoped_collection_destroy
6
7
  options[:title] = I18n.t('active_admin_scoped_collection_actions.actions.delete') if options[:title].nil?
7
8
  add_scoped_collection_action_default_destroy(options, &block)
@@ -15,6 +16,24 @@ module ActiveAdminScopedCollectionActions
15
16
  config.add_scoped_collection_action(name, options)
16
17
  end
17
18
 
19
+ # Amount of records affected by a scoped collection action. Used by the confirmation
20
+ # dialog of an action with :confirm_submit option. Collection is already limited by
21
+ # authorization scope, so it exposes nothing the user can not see on the index page.
22
+ def add_scoped_collection_records_count_action
23
+ batch_action :scoped_collection_records_count, if: proc { false } do
24
+ # Count the rows this relation yields, not the rows its WHERE matches.
25
+ # The action walks the same relation with find_each, which honours
26
+ # limit and offset, so stripping them here would promise more records
27
+ # than it goes on to touch. Counting through a subquery keeps every
28
+ # clause and collapses a grouped scope -- whose bare .count answers
29
+ # with a Hash the dialog would render as [object Object] -- into the
30
+ # single number the dialog needs.
31
+ scope = scoped_collection_records.except(:eager_load, :select, :order)
32
+ count = scope.model.unscoped.from(scope.select('1'), :subquery_for_count).count
33
+ render json: { count: count }
34
+ end
35
+ end
36
+
18
37
  def add_scoped_collection_action_default_update(options, &block)
19
38
  batch_action :scoped_collection_update, if: proc { false } do
20
39
  unless authorized?(:batch_edit, resource_class)
@@ -1,5 +1,6 @@
1
1
  module ActiveAdminScopedCollectionActions
2
2
  module ResourceExtension
3
+ include MethodOrProcHelper
3
4
 
4
5
  def initialize(*)
5
6
  super
@@ -54,6 +55,8 @@ module ActiveAdminScopedCollectionActions
54
55
  div I18n.t('active_admin_scoped_collection_actions.sidebar_msg')
55
56
 
56
57
  active_admin_config.scoped_collection_actions.each do |key, options={}|
58
+ next if options.key?(:if) && !call_method_or_exec_proc(options[:if])
59
+
57
60
  b_title = options.fetch(:title, ::ActiveSupport::Inflector.humanize(key))
58
61
  b_options = {}
59
62
  b_options[:class] = options[:class] if options[:class].present?
@@ -65,6 +68,19 @@ module ActiveAdminScopedCollectionActions
65
68
  b_data[:inputs] = options[:form].is_a?(Proc) ? options[:form].call : options[:form]
66
69
  end
67
70
  b_data[:confirm] = options.fetch(:confirm, I18n.t('active_admin_scoped_collection_actions.confirm_action_message'))
71
+ b_data[:confirm] = b_data[:confirm].call if b_data[:confirm].is_a?(Proc)
72
+ confirm_submit = options.fetch(:confirm_submit, false)
73
+ confirm_submit = confirm_submit.call if confirm_submit.is_a?(Proc)
74
+ if confirm_submit
75
+ b_data[:confirm_submit] = confirm_submit.is_a?(String) ? confirm_submit : I18n.t('active_admin_scoped_collection_actions.confirm_action_message')
76
+ b_data[:confirm_submit_summary] = I18n.t('active_admin_scoped_collection_actions.confirm_submit_summary')
77
+ end
78
+ confirm_summary = options.fetch(:confirm_summary, false)
79
+ confirm_summary = confirm_summary.call if confirm_summary.is_a?(Proc)
80
+ if confirm_summary
81
+ default_key = key.to_sym == :scoped_collection_destroy ? 'confirm_destroy_summary' : 'confirm_action_summary'
82
+ b_data[:confirm_summary] = confirm_summary.is_a?(String) ? confirm_summary : I18n.t("active_admin_scoped_collection_actions.#{default_key}")
83
+ end
68
84
  b_options[:data] = b_data.to_json
69
85
  button b_title, b_options
70
86
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveAdminScopedCollectionActions
2
- VERSION = '1.0.1'.freeze
2
+ VERSION = '2.1.0'.freeze
3
3
  end
@@ -1,4 +1,26 @@
1
+ //= require ./lib/dialog_records_summary
1
2
  //= require ./lib/dialog_mass_fields_update
3
+ //= require ./lib/dialog_confirm_submit
4
+
5
+ ActiveAdmin.scopedCollectionBatchActionUrl = function(){
6
+ return window.location.pathname + '/batch_action' + window.location.search;
7
+ };
8
+
9
+ ActiveAdmin.scopedCollectionSelection = function(){
10
+ const selection = [];
11
+ $('.paginated_collection').find('input.collection_selection:checked').each((i, el) => selection.push($(el).val()));
12
+ return selection;
13
+ };
14
+
15
+ // Amount of records the action is going to be performed on. Same scope(filters,
16
+ // current scope and checked records) as the action itself uses.
17
+ ActiveAdmin.scopedCollectionRecordsCount = function(authToken){
18
+ return $.post(ActiveAdmin.scopedCollectionBatchActionUrl(), {
19
+ batch_action: 'scoped_collection_records_count',
20
+ authenticity_token: authToken,
21
+ collection_selection: ActiveAdmin.scopedCollectionSelection()
22
+ });
23
+ };
2
24
 
3
25
  $(document).ready(() =>
4
26
 
@@ -6,16 +28,30 @@ $(document).ready(() =>
6
28
  e.preventDefault();
7
29
  const fields = JSON.parse( $(this).attr('data') );
8
30
 
31
+ const count = () => ActiveAdmin.scopedCollectionRecordsCount(fields['auth_token']);
32
+ const options = {};
33
+
34
+ if (fields['confirm_submit']) {
35
+ // fields dialog gets a second step with the summary of what is going to be done
36
+ options['confirmSubmit'] = {
37
+ title: fields['confirm_submit'],
38
+ summary: fields['confirm_summary'] || fields['confirm_submit_summary'],
39
+ count: count
40
+ };
41
+ } else if (fields['confirm_summary']) {
42
+ // dialog itself tells what is going to be done
43
+ options['summary'] = { text: fields['confirm_summary'], count: count };
44
+ }
45
+
9
46
  return ActiveAdmin.dialogMassFieldsUpdate(fields['confirm'], fields['inputs'],
10
47
  inputs=> {
11
- const url = window.location.pathname + '/batch_action' + window.location.search;
48
+ const url = ActiveAdmin.scopedCollectionBatchActionUrl();
12
49
  const form_data = {
13
50
  changes: inputs,
14
- collection_selection: [],
51
+ collection_selection: ActiveAdmin.scopedCollectionSelection(),
15
52
  authenticity_token: fields['auth_token'],
16
53
  batch_action: fields['batch_action']
17
54
  };
18
- $('.paginated_collection').find('input.collection_selection:checked').each((i, el) => form_data["collection_selection"].push($(el).val()));
19
55
 
20
56
  return $.post(url, form_data).always(function(data, textStatus, jqXHR) {
21
57
  if (jqXHR.getResponseHeader('Location')) {
@@ -24,6 +60,6 @@ $(document).ready(() =>
24
60
  return window.location.reload();
25
61
  }
26
62
  });
27
- });
63
+ }, options);
28
64
  })
29
65
  );
@@ -0,0 +1,52 @@
1
+ // Second step of the fields dialog: instead of the fields it shows what is going to be
2
+ // changed and how many records are affected. "Back" returns to the filled form.
3
+ // form: dialog of ActiveAdmin.dialogMassFieldsUpdate
4
+ // options: {title: 'Are you sure?', summary: 'You are going to update {count} record(s) with:',
5
+ // count: function returning jqXHR with {count: 100500}}
6
+ // changes: [{label: 'Name', value: 'test'}, ...]
7
+ ActiveAdmin.dialogConfirmSubmit = function(form, options, changes, callback){
8
+ const widget = form.dialog('widget');
9
+ const fieldsStep = { content: form.children(), title: form.dialog('option', 'title'), buttons: form.dialog('option', 'buttons') };
10
+
11
+ const step = $('<div class="dialog_confirm_submit"></div>');
12
+ const summary = options['summary'] ?
13
+ ActiveAdmin.dialogRecordsSummary(options['summary'], options['count']).appendTo(step) : null;
14
+ const list = $('<ul class="dialog_confirm_submit_changes"></ul>').appendTo(step);
15
+
16
+ for (let change of Array.from(changes)) {
17
+ $('<li></li>')
18
+ .append($('<b></b>').text(change['label']))
19
+ .append(document.createTextNode(`: ${change['value']}`))
20
+ .appendTo(list);
21
+ }
22
+
23
+ fieldsStep.content.hide();
24
+ step.appendTo(form);
25
+ widget.addClass('active_admin_dialog_confirm_submit');
26
+
27
+ form.dialog('option', {
28
+ title: options['title'] || fieldsStep.title,
29
+ buttons: {
30
+ OK() {
31
+ // amount of affected records is not known yet
32
+ if (form.data('waiting')) { return; }
33
+
34
+ return callback();
35
+ },
36
+ Back() {
37
+ form.data('waiting', false);
38
+ step.remove();
39
+ fieldsStep.content.show();
40
+ widget.removeClass('active_admin_dialog_confirm_submit');
41
+ return form.dialog('option', { title: fieldsStep.title, buttons: fieldsStep.buttons });
42
+ }
43
+ }
44
+ });
45
+
46
+ if (summary) {
47
+ ActiveAdmin.dialogSpinnerUntil(step.children(), summary.data('countRequest'));
48
+ ActiveAdmin.dialogWaitFor(form, summary.data('countRequest'));
49
+ }
50
+
51
+ return form;
52
+ };
@@ -1,17 +1,50 @@
1
- ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
1
+ // Fields checked by the user with their values, as they are displayed in the dialog.
2
+ // @return [Array<Object>] [{label: 'Name', value: 'test'}, ...]
3
+ ActiveAdmin.dialogMassFieldsChanges = function(form){
4
+ const changes = [];
5
+
6
+ form.find('.mass_update_protect_fild_flag:checked').each(function() {
7
+ const flag = $(this);
8
+ const field = flag.nextAll('input, select, textarea').first();
9
+ let value;
10
+
11
+ if (field.is('select')) {
12
+ value = field.find('option:selected').map((i, option) => $(option).text().trim()).get().join(', ');
13
+ } else if (field.attr('type') === 'checkbox') {
14
+ value = field.is(':checked');
15
+ } else {
16
+ value = field.val();
17
+ }
18
+
19
+ changes.push({ label: flag.next('label').text().trim(), value: value });
20
+ });
21
+
22
+ return changes;
23
+ };
24
+
25
+ // options: {summary: {text: 'You are going to delete {count} record(s).', count: countFn},
26
+ // confirmSubmit: @see ActiveAdmin.dialogConfirmSubmit}
27
+ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback, options){
28
+ options = options || {};
29
+ const confirmSubmit = options['confirmSubmit'];
2
30
  let html = `<form id="dialog_confirm" title="${message}"><div style="padding-right:4px;padding-left:1px;margin-right:2px"><ul>`;
3
31
  for (let name in inputs) {
4
32
  var elem, opts, wrapper;
5
33
  let type = inputs[name];
6
- if (/^(datepicker|checkbox|text)$/.test(type)) {
34
+ let klass = '';
35
+ if ($.isPlainObject(type)) {
36
+ // {type: 'text', class: 'my-widget'} - input rendered with your own class,
37
+ // so it can be turned into any widget by your own JS
38
+ [wrapper, klass, type] = Array.from(['input', type['class'] || '', type['type'] || 'text']);
39
+ } else if (/^(datepicker|checkbox|text)$/.test(type)) {
7
40
  wrapper = 'input';
41
+ klass = type === 'datepicker' ? type : '';
8
42
  } else if ($.isArray(type)) {
9
43
  [wrapper, elem, opts, type] = Array.from(['select', 'option', type, '']);
10
44
  } else {
11
45
  throw new Error(`Unsupported input type: {${name}: ${type}}`);
12
46
  }
13
47
 
14
- let klass = type === 'datepicker' ? type : '';
15
48
  html += `<li>
16
49
  <input type='checkbox' class='mass_update_protect_fild_flag' value='Y' id="mass_update_dialog_${name}" />
17
50
  <label for="mass_update_dialog_${name}"> ${name.charAt(0).toUpperCase() + name.slice(1)}</label>
@@ -43,10 +76,14 @@ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
43
76
 
44
77
  const form = $(html).appendTo('body');
45
78
 
79
+ const summary = options['summary'] ?
80
+ ActiveAdmin.dialogRecordsSummary(options['summary']['text'], options['summary']['count']).prependTo(form) : null;
81
+
46
82
  $('body').trigger('mass_update_modal_dialog:before_open', [form]);
47
83
 
48
- return form.dialog({
84
+ const dialog = form.dialog({
49
85
  modal: true,
86
+ classes: { 'ui-dialog': 'ui-corner-all active_admin_dialog active_admin_dialog_mass_update_by_filter' },
50
87
  dialogClass: 'active_admin_dialog active_admin_dialog_mass_update_by_filter',
51
88
  maxHeight: window.innerHeight - (window.innerHeight * 0.1),
52
89
  open() {
@@ -60,9 +97,18 @@ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
60
97
  });
61
98
  },
62
99
  buttons: {
63
- OK(e){
64
- $(e.target).closest('.ui-dialog-buttonset').html('<span>Processing. Please wait...</span>');
65
- return callback($(this).serializeObject());
100
+ OK(){
101
+ // amount of affected records is not known yet
102
+ if (form.data('waiting')) { return; }
103
+
104
+ const submit = () => {
105
+ form.dialog('widget').find('.ui-dialog-buttonset').html('<span>Processing. Please wait...</span>');
106
+ return callback(form.serializeObject());
107
+ };
108
+ const changes = ActiveAdmin.dialogMassFieldsChanges(form);
109
+ // nothing is checked - there is nothing to confirm
110
+ if (!confirmSubmit || !changes.length) { return submit(); }
111
+ return ActiveAdmin.dialogConfirmSubmit(form, confirmSubmit, changes, submit);
66
112
  },
67
113
  Cancel() {
68
114
  $('.mass_update_protect_fild_flag').off('change');
@@ -70,4 +116,11 @@ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
70
116
  }
71
117
  }
72
118
  });
119
+
120
+ if (summary) {
121
+ ActiveAdmin.dialogSpinnerUntil(summary, summary.data('countRequest'));
122
+ ActiveAdmin.dialogWaitFor(form, summary.data('countRequest'));
123
+ }
124
+
125
+ return dialog;
73
126
  };
@@ -0,0 +1,47 @@
1
+ // Sentence about the records the action is going to be performed on,
2
+ // e.g. "You are going to delete 100500 record(s)."
3
+ // template: text where {count} is replaced with the amount
4
+ // countFn: function returning jqXHR with {count: 100500}
5
+ // @return [jQuery] paragraph, request promise is available as .data('countRequest')
6
+ ActiveAdmin.dialogRecordsSummary = function(template, countFn){
7
+ const summary = $('<p class="dialog_records_summary"></p>');
8
+ const showCount = count => summary.text(template.replace('{count}', count));
9
+
10
+ if (!countFn) {
11
+ showCount('');
12
+ return summary.data('countRequest', $.Deferred().resolve().promise());
13
+ }
14
+
15
+ const request = countFn()
16
+ .done(data => showCount(data['count']))
17
+ .fail(() => showCount('?'));
18
+
19
+ return summary.data('countRequest', request);
20
+ };
21
+
22
+ // Shows a spinner instead of the content until the promise is settled, so the user
23
+ // never sees a half of the message.
24
+ ActiveAdmin.dialogSpinnerUntil = function(content, promise){
25
+ const spinner = $('<div class="dialog_spinner"></div>').insertBefore(content.first());
26
+
27
+ content.hide();
28
+
29
+ return promise.always(function(){
30
+ spinner.remove();
31
+ content.show();
32
+ });
33
+ };
34
+
35
+ // Keeps OK of the dialog disabled while the promise is pending, so nothing can be
36
+ // confirmed before the user sees what they confirm.
37
+ ActiveAdmin.dialogWaitFor = function(dialog, promise){
38
+ const button = dialog.dialog('widget').find('.ui-dialog-buttonset button').first();
39
+
40
+ dialog.data('waiting', true);
41
+ button.prop('disabled', true).addClass('ui-state-disabled');
42
+
43
+ return promise.always(function(){
44
+ dialog.data('waiting', false);
45
+ button.prop('disabled', false).removeClass('ui-state-disabled');
46
+ });
47
+ };
@@ -23,6 +23,45 @@
23
23
  }
24
24
  }
25
25
 
26
+ .active_admin_dialog_mass_update_by_filter, .active_admin_dialog_confirm_submit {
27
+ .dialog_records_summary {
28
+ margin: 0 0 5px 0;
29
+ }
30
+ .dialog_spinner {
31
+ display: block;
32
+ box-sizing: border-box;
33
+ width: 24px;
34
+ height: 24px;
35
+ margin: 15px auto;
36
+ border: 3px solid currentColor;
37
+ border-right-color: transparent;
38
+ border-radius: 50%;
39
+ animation: dialog_spinner_rotate 0.8s linear infinite;
40
+ }
41
+ button:disabled {
42
+ cursor: default;
43
+ opacity: 0.6;
44
+ }
45
+ }
46
+
47
+ @keyframes dialog_spinner_rotate {
48
+ to { transform: rotate(360deg); }
49
+ }
50
+
51
+ .active_admin_dialog_confirm_submit {
52
+ .dialog_confirm_submit_changes {
53
+ margin: 5px 0 0 0;
54
+
55
+ li {
56
+ display: block;
57
+ margin: 3px 0 0 0;
58
+ }
59
+ }
60
+ button {
61
+ cursor: pointer;
62
+ }
63
+ }
64
+
26
65
  .sidebar_section.panel {
27
66
  .scoped_collection_action_button {
28
67
  cursor: pointer;
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_admin_scoped_collection_actions
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gena M.
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-03-02 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activeadmin
@@ -16,7 +15,7 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '1.1'
18
+ version: '3.0'
20
19
  - - "<"
21
20
  - !ruby/object:Gem::Version
22
21
  version: '4.0'
@@ -26,7 +25,7 @@ dependencies:
26
25
  requirements:
27
26
  - - ">="
28
27
  - !ruby/object:Gem::Version
29
- version: '1.1'
28
+ version: '3.0'
30
29
  - - "<"
31
30
  - !ruby/object:Gem::Version
32
31
  version: '4.0'
@@ -38,15 +37,9 @@ executables: []
38
37
  extensions: []
39
38
  extra_rdoc_files: []
40
39
  files:
41
- - ".github/workflows/ci.yml"
42
- - ".gitignore"
43
40
  - CHANGELOG.md
44
- - CODE_OF_CONDUCT.md
45
- - Gemfile
46
41
  - LICENSE.txt
47
42
  - README.md
48
- - Rakefile
49
- - active_admin_scoped_collection_actions.gemspec
50
43
  - config/locales/de.yml
51
44
  - config/locales/en.yml
52
45
  - config/locales/es.yml
@@ -58,17 +51,15 @@ files:
58
51
  - lib/active_admin_scoped_collection_actions/engine.rb
59
52
  - lib/active_admin_scoped_collection_actions/resource_extension.rb
60
53
  - lib/active_admin_scoped_collection_actions/version.rb
61
- - screenshots/pupup.png
62
- - screenshots/sidebar.png
63
- - tasks/test.rake
64
54
  - vendor/assets/javascripts/active_admin_scoped_collection_actions.js
55
+ - vendor/assets/javascripts/lib/dialog_confirm_submit.js
65
56
  - vendor/assets/javascripts/lib/dialog_mass_fields_update.js
57
+ - vendor/assets/javascripts/lib/dialog_records_summary.js
66
58
  - vendor/assets/stylesheets/active_admin_scoped_collection_actions.scss
67
59
  homepage: https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions
68
60
  licenses:
69
61
  - MIT
70
62
  metadata: {}
71
- post_install_message:
72
63
  rdoc_options: []
73
64
  require_paths:
74
65
  - lib
@@ -76,15 +67,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
67
  requirements:
77
68
  - - ">="
78
69
  - !ruby/object:Gem::Version
79
- version: '0'
70
+ version: '3.3'
80
71
  required_rubygems_version: !ruby/object:Gem::Requirement
81
72
  requirements:
82
73
  - - ">="
83
74
  - !ruby/object:Gem::Version
84
75
  version: '0'
85
76
  requirements: []
86
- rubygems_version: 3.5.6
87
- signing_key:
77
+ rubygems_version: 3.7.1
88
78
  specification_version: 4
89
79
  summary: scoped_collection actions extension for ActiveAdmin
90
80
  test_files: []
@@ -1,44 +0,0 @@
1
- name: CI
2
- on:
3
- pull_request:
4
- push:
5
- branches:
6
- - master
7
- jobs:
8
- test:
9
- name: Tests with Ruby ${{ matrix.ruby }} Rails ${{ matrix.rails }} Active Admin ${{ matrix.activeadmin }}
10
- runs-on: ubuntu-latest
11
- strategy:
12
- matrix:
13
- ruby:
14
- - '3.0.0'
15
- - '3.1.0'
16
- - '3.2.0'
17
- - '3.3.0'
18
- rails:
19
- - '6.1.0'
20
- - '7.0.0'
21
- - '7.1.0'
22
- activeadmin:
23
- - '2.14.0'
24
- - '3.0.0'
25
- - '3.1.0'
26
- - '3.2.0'
27
- exclude:
28
- - rails: '7.1.0'
29
- activeadmin: '2.14.0'
30
- - rails: '7.1.0'
31
- activeadmin: '3.0.0'
32
- env:
33
- RAILS: ${{ matrix.rails }}
34
- AA: ${{ matrix.activeadmin }}
35
- steps:
36
- - uses: actions/checkout@v4
37
- - uses: ruby/setup-ruby@v1
38
- with:
39
- ruby-version: ${{ matrix.ruby }}
40
- - name: Run tests
41
- run: |
42
- gem install bundler -v '< 2'
43
- bundle install
44
- bundle exec rspec spec
data/.gitignore DELETED
@@ -1,15 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- /.idea
11
- .DS_Store
12
- /spec/rails
13
- .versions.conf
14
- vendor/bundle
15
- bin
data/CODE_OF_CONDUCT.md DELETED
@@ -1,13 +0,0 @@
1
- # Contributor Code of Conduct
2
-
3
- As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
-
5
- We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
-
7
- Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
-
9
- Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
-
11
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
-
13
- This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile DELETED
@@ -1,24 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
4
-
5
- group :test do
6
- default_rails_version = '7.1.0'
7
- default_activeadmin_version = '3.2.0'
8
-
9
- gem 'rails', "~> #{ENV['RAILS'] || default_rails_version}"
10
- gem 'activeadmin', "~> #{ENV['AA'] || default_activeadmin_version}"
11
-
12
- gem 'sprockets-rails'
13
- gem 'rspec-rails'
14
- gem 'coveralls_reborn', require: false
15
- gem 'sass-rails'
16
- gem 'sqlite3', '~> 1.4.0'
17
- gem 'launchy'
18
- gem 'database_cleaner'
19
- gem 'capybara'
20
- gem 'webdrivers'
21
- gem 'byebug'
22
- gem 'draper'
23
- gem 'webrick', require: false
24
- end
data/Rakefile DELETED
@@ -1,7 +0,0 @@
1
- require "bundler"
2
- require 'rake'
3
- Bundler.setup
4
- Bundler::GemHelper.install_tasks
5
-
6
- # Import all our rake tasks
7
- FileList['tasks/**/*.rake'].each { |task| import task }
@@ -1,21 +0,0 @@
1
- # coding: utf-8
2
- require File.expand_path('../lib/active_admin_scoped_collection_actions/version', __FILE__)
3
-
4
- Gem::Specification.new do |spec|
5
- spec.name = "active_admin_scoped_collection_actions"
6
- spec.version = ActiveAdminScopedCollectionActions::VERSION
7
- spec.authors = ["Gena M."]
8
- spec.email = ["workgena@gmail.com"]
9
-
10
- spec.summary = %q{scoped_collection actions extension for ActiveAdmin}
11
- spec.description = %q{Plugin for ActiveAdmin. Provides batch Update and Delete for scoped_collection (Filters + Scope) across all pages.}
12
- spec.homepage = "https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions"
13
- spec.license = "MIT"
14
-
15
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
- spec.bindir = "exe"
17
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
- spec.require_paths = ["lib"]
19
-
20
- spec.add_dependency "activeadmin", ">= 1.1", "< 4.0"
21
- end
Binary file
Binary file
data/tasks/test.rake DELETED
@@ -1,14 +0,0 @@
1
- desc 'Creates a test rails app for the specs to run against'
2
- task :setup do
3
- require 'rails/version'
4
-
5
- rails_new_args = %w[
6
- --skip-turbolinks
7
- --skip-spring
8
- --skip-bootsnap
9
- -m
10
- spec/support/rails_template.rb
11
- ].join(' ')
12
-
13
- system "bundle exec rails new spec/rails/rails-#{Rails::VERSION::STRING} #{rails_new_args}"
14
- end