active_admin_scoped_collection_actions 2.0.0 → 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: 1170fafbeb26fcb0da73fd784d5e2718f0e0bd6d82b9f30cb2dd0945851b3906
4
- data.tar.gz: 81fc10db07fd91bf1a18271e6ece37a6cf63f6f73d9c1472ce314fbaeaafac34
3
+ metadata.gz: ad87e54407e268139e8d45334645748dd0a02a22fdecd73bb6ea07bbf8f4ebe5
4
+ data.tar.gz: a4f1a7c3d13f6b0d56f33b5988caf0add3638e3b65d0d49d00b4038aea720d40
5
5
  SHA512:
6
- metadata.gz: 306ff67913daff3fac2e014f53770580c9a5d462390d4ed7b2ccbd89ebf70618a2d6a40c1bb09067b8e9e6699a850e3d3ce9f7dd4eaa6134e3177e680eb5b420
7
- data.tar.gz: f04a536138d89517dd532862ff708ace7ae330f86e8d78584c21481eaa706362560c7b1a5356a855a7c448529aea46e7a91d20d23326bb587441a22cc771d23c
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,20 +1,40 @@
1
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
2
  ![Coverage](https://img.shields.io/endpoint?url=https://activeadmin-plugins.github.io/active_admin_scoped_collection_actions/badge.json)
3
3
 
4
-
5
4
  # ActiveAdmin Scoped Collection Actions
5
+
6
6
  Plugin for ActiveAdmin. Provides batch Update and Delete for scoped_collection (Filters + Scope) across all pages.
7
7
 
8
- ![Step 1](/screenshots/sidebar.png)
8
+ ![Collection actions in the ActiveAdmin sidebar](/screenshots/sidebar.png)
9
9
 
10
- ![Step 1](/screenshots/pupup.png)
10
+ ![The confirmation dialog with the update form](/screenshots/pupup.png)
11
11
 
12
+ ## Contents
12
13
 
13
- # Description
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)
14
30
 
15
- 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.
31
+ ## Description
16
32
 
17
- # Install
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.
36
+
37
+ ## Installation
18
38
 
19
39
  Add this line to your application's Gemfile:
20
40
 
@@ -27,23 +47,23 @@ gem 'active_admin_scoped_collection_actions', github: 'activeadmin-plugins/activ
27
47
 
28
48
  And then execute:
29
49
 
30
- ```
31
- $ bundle
50
+ ```sh
51
+ bundle
32
52
  ```
33
53
 
34
- 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`:
35
55
 
36
56
  ```javascript
37
57
  //= require active_admin_scoped_collection_actions
38
58
  ```
39
59
 
40
- Also include CSS in "app/assets/stylesheets/active_admin.css.scss"
60
+ Also include the CSS in `app/assets/stylesheets/active_admin.css.scss`:
41
61
 
42
- ```css
62
+ ```scss
43
63
  @import "active_admin_scoped_collection_actions";
44
64
  ```
45
65
 
46
- # Usage
66
+ ## Usage
47
67
 
48
68
  Usually you need two standard actions: Delete and Update.
49
69
 
@@ -67,37 +87,53 @@ ActiveAdmin.register Post do
67
87
  end
68
88
  ```
69
89
 
70
- **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.
71
93
 
72
94
  ### Update action
73
95
 
74
- 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:
75
97
 
76
98
  ```ruby
77
- scoped_collection_action :scoped_collection_update, form: -> do
78
- { name: 'text',
79
- diagonal: 'text',
80
- manufactured_at: 'datepicker',
81
- vendor_id: Vendor.all.map { |region| [region.name, region.id] },
82
- has_3g: [['Yes', 't'], ['No', 'f']]
83
- }
84
- 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
85
106
  ```
86
107
 
87
- In this example Phone model has fields:
88
- * name - varchar string
89
- * diagonal - integer(of float)
90
- * manufactured_at - datetime
91
- * vendor_id - association "belongs_to :vendor, class_name: 'Vendor', foreign_key: :vendor_id"
92
- * has_3g - boolean
108
+ In this example the Phone model has fields:
109
+
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
93
115
 
94
- 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.
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.
95
120
 
96
- # Custom Actions
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:
97
125
 
98
- Example: We have Phone resource and it has column "manufactured_at". We need an action which will erase this date.
126
+ ```javascript
127
+ $(document).on('mass_update_modal_dialog:after_open', function (event, form) {
128
+ $(form).find('input.my-widget').myWidget();
129
+ });
130
+ ```
99
131
 
100
- In ActiveAdmin resource:
132
+ ## Custom actions
133
+
134
+ Example: we have a Phone resource with a `manufactured_at` column. We need an action which will erase this date.
135
+
136
+ In the ActiveAdmin resource:
101
137
 
102
138
  ```ruby
103
139
  ActiveAdmin.register Phone do
@@ -113,13 +149,12 @@ ActiveAdmin.register Phone do
113
149
  end
114
150
  ```
115
151
 
116
- 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.
117
-
118
-
119
- # Details and Settings
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.
120
154
 
155
+ ## Details and settings
121
156
 
122
- ### Why I don't see Sidebar with Collection Actions.
157
+ ### Why don't I see the sidebar with collection actions?
123
158
 
124
159
  Sidebar visibility by default depends on several things.
125
160
 
@@ -129,19 +164,18 @@ First you must set:
129
164
  config.batch_actions = true
130
165
  ```
131
166
 
132
- 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.
133
168
 
134
169
  ```ruby
135
170
  scoped_collection_action :something_here
136
171
  ```
137
172
 
138
- You resource should have some collection actions. If it doesn't have any, the sidebar will not appear.
173
+ Your resource should have some collection actions. If it does not have any, the sidebar will not appear.
139
174
 
140
- By default we dont allow perform actions on **all** the records. We want protect you from accidental deleting.
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
- Sidebar with buttons will appear only after you perform filtering or scopes on resource records.
143
-
144
- And lastly you can manage sidebar visibility by resource config:
178
+ And lastly, you can manage sidebar visibility through the resource config:
145
179
 
146
180
  ```ruby
147
181
  # Always
@@ -151,91 +185,163 @@ config.scoped_collection_actions_if = -> { params[:scope] }
151
185
  # etc.
152
186
  ```
153
187
 
154
- You can also manage visibility of each action individually:
188
+ You can also manage the visibility of each action individually:
155
189
 
156
190
  ```ruby
157
191
  scoped_collection_action :scoped_collection_destroy, if: proc { can? :destroy, Blog }
158
192
  ```
159
193
 
160
- ### Can I use my handler on update/delete action?
194
+ ### Can I use my own handler for the update and delete actions?
161
195
 
162
- You can pass block to default actions update and delete.
163
- 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`.
164
198
 
165
- 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:
166
201
 
167
202
  ```ruby
168
- scoped_collection_action :scoped_collection_update,
169
- form: -> {
170
- {name: 'text'}
171
- } do
172
- scoped_collection_records.update_all(name: params[:changes][:name])
173
- flash[:notice] = 'Name successfully changed.'
174
- render nothing: true, status: :no_content, location: admin_dashboard_path
175
- 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
176
211
  ```
177
212
 
213
+ ### How can I rename a button?
178
214
 
179
- ### How can I rename button?
180
-
181
- Every scoped_collection_action has option `:title`.
215
+ Every `scoped_collection_action` has the `:title` option.
182
216
 
183
217
  Example:
184
218
 
185
219
  ```ruby
186
- scoped_collection_action :erase_date, title: 'Nullify' do
187
- scoped_collection_records.update_all(manufactured_at: nil)
188
- end
220
+ scoped_collection_action :erase_date, title: 'Nullify' do
221
+ scoped_collection_records.update_all(manufactured_at: nil)
222
+ end
189
223
  ```
190
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
+ ```
191
232
 
192
- ### How can I modify modal dialog title?
233
+ ### How can I tell the user how many records are affected?
193
234
 
194
- Similar to button title. Use option `:confirm`
235
+ The `:confirm_summary` option adds a sentence with the number of affected records to the dialog:
195
236
 
196
237
  ```ruby
197
- scoped_collection_action :scoped_collection_destroy, confirm: 'Delete all phones?'
238
+ scoped_collection_action :scoped_collection_destroy, confirm_summary: true
198
239
  ```
199
240
 
241
+ ```text
242
+ Delete all?
243
+
244
+ You are going to delete 100500 record(s).
245
+
246
+ [ OK ] [ Cancel ]
247
+ ```
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.
200
263
 
201
- ### Can I replace you pop-up form with my own?
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.
202
266
 
203
- Yes. But also you must take care of mandatory parameters passed to the server.
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:
204
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:
205
278
 
206
279
  ```ruby
207
- scoped_collection_action :my_pop_action, class: 'my_popup'
280
+ scoped_collection_action :scoped_collection_update,
281
+ confirm_submit: true,
282
+ form: -> { { body: 'text' } }
283
+ ```
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
291
+
292
+ [ OK ] [ Back ]
208
293
  ```
209
294
 
210
- Now in HTML page, you have button:
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.
298
+
299
+ `true` shows the default message ("Are you sure?"). Pass a String or a Proc to use your own:
300
+
301
+ ```ruby
302
+ scoped_collection_action :scoped_collection_update,
303
+ confirm_submit: 'Update all filtered phones?',
304
+ form: -> { { name: 'text' } }
305
+ ```
306
+
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:
211
316
 
212
317
  ```html
213
- <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>
214
319
  ```
215
320
 
216
- Without handler, clicking on the button does nothing.
321
+ Without a handler, clicking the button does nothing.
217
322
 
218
323
  You can render the form in any way you want:
219
- - It can be some popup(Fancybox, Simplemodal, etc.), or some inline collapsible form.
220
- - It can even be a separate full-page.
221
-
222
- One thing is important - how you will send data to server. Generally it should be:
223
324
 
224
- POST request
325
+ - it can be a popup (Fancybox, Simplemodal, etc.), or an inline collapsible form;
326
+ - it can even be a separate full page.
225
327
 
226
- URL: /admin/collection_path/batch_action
328
+ One thing is important — how you send the data to the server. It should be:
227
329
 
228
- with GET params identical to current page
330
+ | | |
331
+ | --- | --- |
332
+ | Method | `POST` |
333
+ | URL | `/admin/collection_path/batch_action` |
334
+ | Query string | identical to the current page |
229
335
 
230
- The easiest way to get them is:
336
+ The easiest way to build it is:
231
337
 
232
338
  ```javascript
233
339
  url = window.location.pathname + '/batch_action' + window.location.search
234
340
  ```
235
341
 
236
- And Request body params should be like:
342
+ And the request body params should look like:
237
343
 
238
- ```
344
+ ```text
239
345
  changes[manufactured_at] = "2015-07-21 18:11"
240
346
  changes[diagonal] = "7"
241
347
  changes[some_filed_name]='new value'
@@ -243,69 +349,73 @@ authenticity_token = "2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0="
243
349
  batch_action = "my_pop_action"
244
350
  ```
245
351
 
246
- ```authenticity_token``` and ```batch_action``` you can get from data-attribute of the Button.
247
-
352
+ `authenticity_token` and `batch_action` can be taken from the data attribute of the button.
248
353
 
249
354
  Example in JavaScript:
250
355
 
251
356
  ```javascript
252
- url = window.location.pathname + '/batch_action' + window.location.search
253
- form_data = {
254
- changes: {"manufactured_at": "2015-07-21 18:11", "diagonal": "7"},
255
- collection_selection: [],
256
- authenticity_token: "2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0=",
257
- batch_action: "my_pop_action"
258
- }
259
- $.post(url, form_data).always () ->
260
- 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
+ });
261
367
  ```
262
368
 
263
- ### How notify user about success and error operations?
369
+ ### How do I notify the user about success and errors?
264
370
 
265
- We recommend using Rails Flash messages.
371
+ We recommend using Rails flash messages.
266
372
 
267
- 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:
268
374
 
269
375
  ```ruby
270
- class Phone < ActiveRecord::Base
271
- validates :diagonal, numericality: { only_integer: true }
272
- end
376
+ class Phone < ActiveRecord::Base
377
+ validates :diagonal, numericality: { only_integer: true }
378
+ end
273
379
  ```
274
380
 
275
381
  ```ruby
276
- scoped_collection_action :change_diagonal, form: { diagonal: 'text' } do
277
- errors = []
278
- scoped_collection_records.find_each do |record|
279
- errors << "#{record.errors.full_messages.join('. ')}" unless record.update(diagonal: params[:changes][:diagonal])
280
- end
281
- if errors.empty?
282
- flash[:notice] = 'Diagonal changed successfully'
283
- else
284
- flash[:error] = errors.join('. ')
285
- end
286
- 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])
287
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
288
394
  ```
289
395
 
290
- 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:
291
397
 
292
- ```
398
+ ```text
293
399
  Diagonal must be an integer.
294
400
  ```
295
401
 
296
- But if you use your custom popup, you can show messages with JS.
297
-
402
+ But if you use your own custom popup, you can show messages with JS.
298
403
 
299
- ### Can I perform action only on selected items?
404
+ ### Can I perform an action only on selected items?
300
405
 
301
- 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.
302
407
 
303
- 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:
304
410
 
305
411
  ```ruby
306
- scoped_collection_action :do_something do
307
- scoped_collection_records.find_each do |record|
308
- record.update(name: 'x')
309
- end
412
+ scoped_collection_action :do_something do
413
+ scoped_collection_records.find_each do |record|
414
+ record.update(name: 'x')
310
415
  end
416
+ end
311
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)
@@ -69,6 +69,18 @@ module ActiveAdminScopedCollectionActions
69
69
  end
70
70
  b_data[:confirm] = options.fetch(:confirm, I18n.t('active_admin_scoped_collection_actions.confirm_action_message'))
71
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
72
84
  b_options[:data] = b_data.to_json
73
85
  button b_title, b_options
74
86
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveAdminScopedCollectionActions
2
- VERSION = '2.0.0'.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,9 +76,12 @@ 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,
50
86
  classes: { 'ui-dialog': 'ui-corner-all active_admin_dialog active_admin_dialog_mass_update_by_filter' },
51
87
  dialogClass: 'active_admin_dialog active_admin_dialog_mass_update_by_filter',
@@ -61,9 +97,18 @@ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
61
97
  });
62
98
  },
63
99
  buttons: {
64
- OK(e){
65
- $(e.target).closest('.ui-dialog-buttonset').html('<span>Processing. Please wait...</span>');
66
- 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);
67
112
  },
68
113
  Cancel() {
69
114
  $('.mass_update_protect_fild_flag').off('change');
@@ -71,4 +116,11 @@ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
71
116
  }
72
117
  }
73
118
  });
119
+
120
+ if (summary) {
121
+ ActiveAdmin.dialogSpinnerUntil(summary, summary.data('countRequest'));
122
+ ActiveAdmin.dialogWaitFor(form, summary.data('countRequest'));
123
+ }
124
+
125
+ return dialog;
74
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_admin_scoped_collection_actions
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gena M.
@@ -37,15 +37,9 @@ executables: []
37
37
  extensions: []
38
38
  extra_rdoc_files: []
39
39
  files:
40
- - ".github/workflows/ci.yml"
41
- - ".gitignore"
42
40
  - CHANGELOG.md
43
- - CODE_OF_CONDUCT.md
44
- - Gemfile
45
41
  - LICENSE.txt
46
42
  - README.md
47
- - Rakefile
48
- - active_admin_scoped_collection_actions.gemspec
49
43
  - config/locales/de.yml
50
44
  - config/locales/en.yml
51
45
  - config/locales/es.yml
@@ -57,11 +51,10 @@ files:
57
51
  - lib/active_admin_scoped_collection_actions/engine.rb
58
52
  - lib/active_admin_scoped_collection_actions/resource_extension.rb
59
53
  - lib/active_admin_scoped_collection_actions/version.rb
60
- - screenshots/pupup.png
61
- - screenshots/sidebar.png
62
- - tasks/test.rake
63
54
  - vendor/assets/javascripts/active_admin_scoped_collection_actions.js
55
+ - vendor/assets/javascripts/lib/dialog_confirm_submit.js
64
56
  - vendor/assets/javascripts/lib/dialog_mass_fields_update.js
57
+ - vendor/assets/javascripts/lib/dialog_records_summary.js
65
58
  - vendor/assets/stylesheets/active_admin_scoped_collection_actions.scss
66
59
  homepage: https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions
67
60
  licenses:
@@ -74,7 +67,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
74
67
  requirements:
75
68
  - - ">="
76
69
  - !ruby/object:Gem::Version
77
- version: 3.1.0
70
+ version: '3.3'
78
71
  required_rubygems_version: !ruby/object:Gem::Requirement
79
72
  requirements:
80
73
  - - ">="
@@ -1,87 +0,0 @@
1
- name: CI
2
- on:
3
- pull_request:
4
- push:
5
- branches: [master]
6
-
7
- permissions:
8
- contents: read
9
- pages: write
10
- id-token: write
11
-
12
- jobs:
13
- test:
14
- name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / AA ${{ matrix.activeadmin }}
15
- runs-on: ubuntu-latest
16
- strategy:
17
- fail-fast: false
18
- matrix:
19
- ruby: ['3.2', '3.3', '3.4']
20
- rails: ['7.1.0', '7.2.0', '8.0.0']
21
- activeadmin: ['3.2.0', '3.3.0', '3.4.0', '3.5.0']
22
- exclude:
23
- - rails: '8.0.0'
24
- activeadmin: '3.2.0'
25
- env:
26
- RAILS: ${{ matrix.rails }}
27
- AA: ${{ matrix.activeadmin }}
28
- steps:
29
- - uses: actions/checkout@v4
30
- - uses: ruby/setup-ruby@v1
31
- with:
32
- ruby-version: ${{ matrix.ruby }}
33
- bundler-cache: true
34
- - name: Run tests
35
- run: bundle exec rspec spec
36
-
37
- - name: Generate badge.json
38
- if: matrix.ruby == '3.4' && matrix.rails == '8.0.0' && matrix.activeadmin == '3.5.0'
39
- run: |
40
- LAST_RUN="coverage/.last_run.json"
41
- if [ ! -f "$LAST_RUN" ]; then
42
- mkdir -p badge
43
- echo '{"schemaVersion":1,"label":"coverage","message":"unknown","color":"lightgrey"}' > badge/badge.json
44
- exit 0
45
- fi
46
- PERCENT=$(ruby -rjson -e "puts JSON.parse(File.read('$LAST_RUN')).dig('result','line').round(1)")
47
- PERCENT_NUM=$(ruby -rjson -e "puts JSON.parse(File.read('$LAST_RUN')).dig('result','line')")
48
- if ruby -e "exit(($PERCENT_NUM >= 90) ? 0 : 1)"; then COLOR="brightgreen"
49
- elif ruby -e "exit(($PERCENT_NUM >= 75) ? 0 : 1)"; then COLOR="green"
50
- elif ruby -e "exit(($PERCENT_NUM >= 60) ? 0 : 1)"; then COLOR="yellow"
51
- else COLOR="red"; fi
52
- mkdir -p badge
53
- echo "{\"schemaVersion\":1,\"label\":\"coverage\",\"message\":\"${PERCENT}%\",\"color\":\"${COLOR}\"}" > badge/badge.json
54
-
55
- - name: Upload badge artifact
56
- if: matrix.ruby == '3.4' && matrix.rails == '8.0.0' && matrix.activeadmin == '3.5.0'
57
- uses: actions/upload-artifact@v4
58
- with:
59
- name: coverage-badge
60
- path: badge
61
-
62
- deploy-coverage:
63
- needs: test
64
- if: github.ref == 'refs/heads/master' && github.event_name == 'push'
65
- runs-on: ubuntu-latest
66
- environment:
67
- name: github-pages
68
- url: ${{ steps.deployment.outputs.page_url }}
69
-
70
- steps:
71
- - name: Download coverage badge
72
- uses: actions/download-artifact@v4
73
- with:
74
- name: coverage-badge
75
- path: coverage
76
-
77
- - name: Setup Pages
78
- uses: actions/configure-pages@v5
79
-
80
- - name: Upload Pages artifact
81
- uses: actions/upload-pages-artifact@v3
82
- with:
83
- path: coverage
84
-
85
- - name: Deploy to GitHub Pages
86
- id: deployment
87
- uses: actions/deploy-pages@v4
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,21 +0,0 @@
1
- source 'https://rubygems.org'
2
- gemspec
3
-
4
- default_rails_version = '7.1.0'
5
- default_activeadmin_version = '3.5.0'
6
-
7
- gem 'rails', "~> #{ENV['RAILS'] || default_rails_version}"
8
- gem 'activeadmin', "~> #{ENV['AA'] || default_activeadmin_version}"
9
- gem 'sprockets-rails'
10
- gem 'sass-rails'
11
-
12
- group :test do
13
- gem 'simplecov', require: false
14
- gem 'rspec-rails'
15
- gem 'sqlite3', '~> 2.0'
16
- gem 'database_cleaner'
17
- gem 'capybara'
18
- gem 'cuprite'
19
- gem 'draper'
20
- gem 'webrick', require: false
21
- 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,23 +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.required_ruby_version = '>= 3.1.0'
16
-
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
- spec.bindir = "exe"
19
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
- spec.require_paths = ["lib"]
21
-
22
- spec.add_dependency "activeadmin", ">= 3.0", "< 4.0"
23
- 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