base_editing_bootstrap 1.9.0 → 1.10.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: e3e85aa56464a6efc36c36170a8d8473b8c6c97107f62ba9a3d9599232c0d30b
4
- data.tar.gz: 48c8890bba4d8740cfb1a210a93a9743b658fa3ef505f0c39db5b6b621504a35
3
+ metadata.gz: 489e2ffcb61b916547f555b4b80a90e9d542c0738afb83dd104418e29865a542
4
+ data.tar.gz: 3f7cba0e07451401983eeef463ff0a18ad97f2aee1a5c16a836980d4e77e4ca3
5
5
  SHA512:
6
- metadata.gz: 9716d018b278998444613dec959a1cf70938d17b02828ff3c5b4bd1efa3ef5ec94e46fefcc06de6f8329f04201f12a2a57ad3375867d5e9f693f0c18a16db4e3
7
- data.tar.gz: 6236f739ce354a526bbab9664337dccecc6b54f39976c7491cbc3bc15b8d8d4f310714bb0df6c08b5815d37343563da1dfc1528d6b865cf7eaadcd6c235bad5d
6
+ metadata.gz: 2e62893f671fe1ef62e0c9fe52cef3c104fdab7646bb1a03b774ae9d9c5650f288e7329b5e4c5e29fda3f935c0e3706a1af8ecb90b220a16fa1f6f5e8759e726
7
+ data.tar.gz: 52055575f2459020e2963d8af95da7c904c3301cf8c4127685a1529f062ab884d9fa77fe59b22b3ff20234e23b5ee0d5db84ae9cfc472cda58ea7748cf63ec9c
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
  All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
3
3
 
4
4
  - - -
5
+ ## 1.10.0 - 2025-09-12
6
+ #### Features
7
+ - Automatic Nested attributes - (dad38de) - Marino Bonetti
8
+
9
+ - - -
10
+
5
11
  ## 1.9.0 - 2025-07-30
6
12
  #### Bug Fixes
7
13
  - Correct add is-invalid class on relation fields - (21e4a92) - Marino Bonetti
data/README.md CHANGED
@@ -1,11 +1,14 @@
1
1
  # BaseEditingBootstrap
2
+
2
3
  [![Gem Version](https://badge.fury.io/rb/base_editing_bootstrap.svg)](https://badge.fury.io/rb/base_editing_bootstrap)
3
4
 
4
5
  ### Active tested on:
6
+
5
7
  * rails: 7.x,8.x
6
8
  * ruby: 3.x
7
9
 
8
10
  ## Installation
11
+
9
12
  Add this line to your application's Gemfile:
10
13
 
11
14
  ```ruby
@@ -13,29 +16,46 @@ gem "base_editing_bootstrap"
13
16
  ```
14
17
 
15
18
  And then execute:
19
+
16
20
  ```bash
17
21
  $ bundle
18
22
  ```
19
23
 
20
24
  Or install it yourself as:
25
+
21
26
  ```bash
22
27
  $ gem install base_editing_bootstrap
23
28
  ```
24
29
 
25
30
  Then run installer:
31
+
26
32
  ```bash
27
33
  $ bundle exec rails g base_editing_bootstrap:install
28
34
  ```
29
35
 
30
36
  **Si presume quindi che ActiveStorage sia correttamente installato, completo del javascript per il direct upload**
31
37
 
38
+ ### Note for NestedAttributes
39
+
40
+ Seguire le istruzioni per installare anche NestedAttributeForm Controller per stimulus:
41
+
42
+ ```shell
43
+ bin/importmap pin @stimulus-components/rails-nested-form
44
+ ```
45
+
46
+ e seguire installazione https://www.stimulus-components.com/docs/stimulus-rails-nested-form
47
+
32
48
  ### Generators
49
+
33
50
  Then Install dependency (if you run base_editing_bootstrap:install you are good to go):
51
+
34
52
  ```bash
35
53
 
36
54
  bundle exec rails g pundit:install
37
55
  ```
38
- Aggiungere ad ApplicationController
56
+
57
+ Aggiungere ad ApplicationController
58
+
39
59
  ```ruby
40
60
  include Pundit::Authorization
41
61
  ```
@@ -48,40 +68,43 @@ documentazione e avrete la vostra versione di boostrap installata.
48
68
  Installare `gem "factory_bot_rails"`
49
69
 
50
70
  ### Initializers
71
+
51
72
  E' possibile configurare BaseEditingBootstrap con alcune impostazioni:
73
+
52
74
  ```ruby
53
75
  BaseEditingBootstrap.configure do |config|
54
- ##
55
- # Controller da cui derivare poi il BaseEditingController da cui derivano
56
- # tutti i controller sottostanti
57
- # @default "ApplicationController"
58
- # config.inherited_controller = 'ApplicationController'
59
-
60
- ##
61
- # Configurazione per alterare lo standard di azione post aggiornamento record
62
- # il default è andare nella pagina di editing del record
63
- # possibili valori :edit , :index
64
- # config_accessor :after_success_update_redirect, default: :edit
65
-
66
- ##
67
- # Configurazione per alterare lo standard di azione post creazione record
68
- # il default è andare nella pagina di editing del record
69
- # possibili valori :edit , :index
70
- # config_accessor :after_success_create_redirect, default: :edit
71
-
72
- end
76
+ ##
77
+ # Controller da cui derivare poi il BaseEditingController da cui derivano
78
+ # tutti i controller sottostanti
79
+ # @default "ApplicationController"
80
+ # config.inherited_controller = 'ApplicationController'
81
+
82
+ ##
83
+ # Configurazione per alterare lo standard di azione post aggiornamento record
84
+ # il default è andare nella pagina di editing del record
85
+ # possibili valori :edit , :index
86
+ # config_accessor :after_success_update_redirect, default: :edit
87
+
88
+ ##
89
+ # Configurazione per alterare lo standard di azione post creazione record
90
+ # il default è andare nella pagina di editing del record
91
+ # possibili valori :edit , :index
92
+ # config_accessor :after_success_create_redirect, default: :edit
93
+
94
+ end
73
95
 
74
96
  ```
75
97
 
76
98
  ## Usage
99
+
77
100
  Utilizzo per modello base, in questo esempio prendiamo come modello Post come esempio del dummy.
78
101
 
79
- - Creare il Modello ed includere
102
+ - Creare il Modello ed includere
80
103
  ```ruby
81
104
  include BaseEditingBootstrap::BaseModel
82
105
  ```
83
106
  - La factory nelle spec deve contenere il trait `with_invalid_attributes` per definire la situazione di dati per record
84
- non valido. ES:
107
+ non valido. ES:
85
108
  ```ruby
86
109
  trait :with_invalid_attributes do
87
110
  name {nil} # name dovrebbe essere obbligatorio nel modello
@@ -126,13 +149,13 @@ Utilizzo per modello base, in questo esempio prendiamo come modello Post come es
126
149
  ```shell
127
150
  rails g base_editing_bootstrap:field_override ModelName field1 field2:type
128
151
  ```
129
- - è possibile customizzare
130
- - un text help per ogni campo andando ad aggiungere nelle traduzioni la relativa
152
+ - è possibile customizzare
153
+ - un text help per ogni campo andando ad aggiungere nelle traduzioni la relativa
131
154
  traduzione nella posizione: `it.activerecord.attributes.MODEL.FIELD/help_text` oppure `help_text_html` in caso di
132
155
  contenuto con html
133
- - un blocco per l'unità di misura accanto al campo aggiungendo alle traduzioni:
156
+ - un blocco per l'unità di misura accanto al campo aggiungendo alle traduzioni:
134
157
  `it.activerecord.attributes.MODEL.FIELD/unit`
135
-
158
+
136
159
  - [OPTIONAL] la medesima cosa è possibile fare con il rendering dei campi
137
160
  delle celle della tabella
138
161
  ```shell
@@ -148,48 +171,51 @@ Utilizzo per modello base, in questo esempio prendiamo come modello Post come es
148
171
  **Cell Field**:
149
172
  - created_at => timestamps.html.erb
150
173
  - updated_at => timestamps.html.erb
151
- - Enum => _enum.html.erb
174
+ - Enum => _enum.html.erb
152
175
  Per gli enum, le traduzioni dei labels di ogni valore provengono da i18n
153
176
  attraverso l'helper: `Utilities::EnumHelper#enum_translation` con variant `:cell_field`
154
177
  il quale sfrutta human_attribute_name del modello con 'attributo.enum_value',
155
178
  quindi ad esempio per un modello `Post` con enum `categoria` e un enum `importante`, la ricerca nelle traduzioni
156
179
  saranno così composte:
157
180
  - it.activerecord.attributes.post/categoria.importante_cell_field
158
- - it.activerecord.attributes.categoria.importante_cell_field
181
+ - it.activerecord.attributes.categoria.importante_cell_field
159
182
  - it.attributes.importante_cell_field
160
183
  - it.activerecord.attributes.post/categoria.importante
161
184
  - it.activerecord.attributes.categoria.importante
162
185
  - it.attributes.importante => nil
163
- - default => base.html.erb
164
-
186
+ - default => base.html.erb
187
+
165
188
  **Form Field**
166
- - Integer => _integer.html.erb
167
- - Float => _decimal.html.erb
168
- - Decimal => _decimal.html.erb
169
- - DateTime => _datetime.html.erb
170
- - Date => _date.html.erb
171
- - Boolean => _boolean.html.erb
172
- - Enum => _enum.html.erb
189
+ - Integer => _integer.html.erb
190
+ - Float => _decimal.html.erb
191
+ - Decimal => _decimal.html.erb
192
+ - DateTime => _datetime.html.erb
193
+ - Date => _date.html.erb
194
+ - Boolean => _boolean.html.erb
195
+ - Enum => _enum.html.erb
173
196
  Per gli enum, le traduzioni dei labels di ogni valore provengono da i18n
174
197
  attraverso l'helper: `Utilities::EnumHelper#enum_translation`" con variante `:form_field`
175
- il quale sfrutta human_attribute_name del modello con 'attributo.enum_value',
198
+ il quale sfrutta human_attribute_name del modello con 'attributo.enum_value',
176
199
  quindi ad esempio per un modello `Post` con enum `categoria` e un enum `importante`, la ricerca nelle traduzioni
177
- saranno così composte:
200
+ saranno così composte:
178
201
  - it.activerecord.attributes.post/categoria.importante_form_field
179
202
  - it.activerecord.attributes.categoria.importante_form_field
180
203
  - it.attributes.importante_form_field
181
204
  - it.activerecord.attributes.post/categoria.importante
182
205
  - it.activerecord.attributes.categoria.importante
183
206
  - it.attributes.importante => nil
184
- - belongs_to => _belongs_to_select.html.erb
207
+ - belongs_to => _belongs_to_select.html.erb
185
208
  Come si può leggere dal partial, il modello che viene utilizzato come base dati per la collection deve
186
209
  avere come metodo `option_label` che deve ritornare la label da utilizzare nelle options.
187
210
  Di default questo metodo utilizza il semplice #to_s
188
211
  Ha anche un metodo per il valore da utilizzare come chiave, di default viene dedotto dalla reflection
189
212
  come anche il nome della classe da utilizzare come sorgente dei dati della collection
190
- - Default/String => _base.html.erb
191
-
192
- In futuro si prevede di aggiungere automatismi per renderizzare senza
213
+ - accept_nested_field => _accept_nested_field.html.erb
214
+ Questo partial renderizza una tabella per i campi associati al modello.
215
+ Più informazioni nelle note per il [nested attributes](#nested-attributes)
216
+ - Default/String => _base.html.erb
217
+
218
+ In futuro si prevede di aggiungere automatismi per renderizzare senza
193
219
  l'intervento dell'utente dei campi.
194
220
  - [OPTIONAL] Search Form:
195
221
  Per poter aggiungere una form di ricerca basta aggiungere alla policy
@@ -205,22 +231,40 @@ Utilizzo per modello base, in questo esempio prendiamo come modello Post come es
205
231
  #...
206
232
  ```
207
233
 
234
+ ### Nested Attributes
235
+
236
+ Il funzionamento si basa completamente sul sistema di NestedAttributes
237
+ di [Rails](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html)
238
+ Note:
239
+
240
+ - Nelle policy bisogna definire come campo da editare il nome della relazione/nested_attribute
241
+ - Nella policy bisogna inoltre definire per i permitted_attributes anche il
242
+ `XXXXX_attributes => [Array attributi da editare] + [:id,:_destroy]`
243
+ - Il permitted _destroy vale solamente nel caso in cui si definisca nei nested_attributes che debba essere cancellabile.
244
+ - I campi visualizzati del modello sono presi dalla relativa policy.
245
+
246
+ Fai riferimento all'implementazione di esempio del dummy `Company->addresses`
247
+
208
248
  ### Translations
249
+
209
250
  Traduzioni disponibili:
210
- Per i bottoni della index, è possibile eseguire l'override del testo presente nel bottone.
251
+ Per i bottoni della index, è possibile eseguire l'override del testo presente nel bottone.
211
252
  Leggere la documentazione nel file `app/helpers/base_editing_helper.rb#translate_with_controller_scoped`
212
253
 
213
-
214
254
  ## Testing helpers
215
255
 
216
256
  ### Requirements(installed with generators)
257
+
217
258
  ```ruby
218
259
  group :test do
219
260
  gem 'rails-controller-testing'
220
261
  end
221
262
  ```
222
- ### Usage
263
+
264
+ ### Usage
265
+
223
266
  Controllers:
267
+
224
268
  ```ruby
225
269
  require 'rails_helper'
226
270
  RSpec.describe "ServiceControllers", type: :request do
@@ -229,7 +273,9 @@ RSpec.describe "ServiceControllers", type: :request do
229
273
  end
230
274
  end
231
275
  ```
276
+
232
277
  Model:
278
+
233
279
  ```ruby
234
280
  require 'rails_helper'
235
281
  RSpec.describe Service, type: :model do
@@ -238,7 +284,9 @@ RSpec.describe Service, type: :model do
238
284
  ransack_permitted_associations: []
239
285
  end
240
286
  ```
287
+
241
288
  Policy
289
+
242
290
  ```ruby
243
291
  require 'rails_helper'
244
292
  ##
@@ -264,10 +312,11 @@ end
264
312
  ```
265
313
 
266
314
  ## Message translations
315
+
267
316
  I messaggi di generati per il flash provengono dal metodo BaseEditingBootstrap::ActionTranslation.human_action_message
268
317
  e seguono una logica simile ad human_attribute_name.
269
318
  Sono già presenti i messaggi di default, a cui viene passato il nome del modello,
270
- ma è possibile fare override del messaggio con la classe:
319
+ ma è possibile fare override del messaggio con la classe:
271
320
 
272
321
  ```yaml
273
322
  LANG:
@@ -281,22 +330,24 @@ LANG:
281
330
  created: "customized %{model} created"
282
331
  unsuccessful:
283
332
  messages:
284
- created:
333
+ created:
285
334
  updated:
286
335
  ```
287
336
 
337
+ ## Contributing
288
338
 
339
+ 1. Setup env with:
289
340
 
290
- ## Contributing
291
- 1. Setup env with:
292
341
  ```shell
293
342
  docker compose run app spec/dummy/bin/setup
294
343
  ```
295
344
 
296
- 2. Start environment with:
345
+ 2. Start environment with:
346
+
297
347
  ```shell
298
348
  docker compose up
299
349
  ```
300
350
 
301
351
  ## License
352
+
302
353
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -24,14 +24,20 @@ module Utilities
24
24
  type = :enum
25
25
  generic_field = "enum"
26
26
  elsif form.object.class.respond_to?(:reflect_on_association) &&
27
- form.object.class.reflect_on_association(field.to_s).is_a?(ActiveRecord::Reflection::BelongsToReflection) &&
28
- !form.object.class.reflect_on_association(field.to_s).polymorphic? # non deve essere polymorphic
27
+ form.object.class.reflect_on_association(field.to_s).is_a?(ActiveRecord::Reflection::BelongsToReflection) &&
28
+ !form.object.class.reflect_on_association(field.to_s).polymorphic? # non deve essere polymorphic
29
29
  # Abbiamo una relazione belongs_to da gestire
30
30
  reflection = form.object.class.reflect_on_association(field.to_s)
31
31
  type = :belongs_to
32
32
  generic_field = "belongs_to_select"
33
33
  locals[:relation_class] = reflection.klass
34
34
  locals[:foreign_key] = reflection.foreign_key
35
+ elsif form.object.class.respond_to?(:nested_attributes_options) &&
36
+ form.object.class.nested_attributes_options.key?(field.to_sym)
37
+ type= :nested_attributes
38
+ generic_field = "accept_nested_field"
39
+ reflection = form.object.class.reflect_on_association(field.to_s)
40
+ locals[:new_object] = reflection.klass.new(reflection.foreign_key => form.object)
35
41
  else
36
42
  if form.object.class.respond_to?(:type_for_attribute)
37
43
  type = form.object.class.type_for_attribute(field).type
@@ -0,0 +1,16 @@
1
+ <tr class="nested-form-wrapper" data-new-record="<%= form.object.new_record? %>" id="<%= dom_id(form.object) %>">
2
+
3
+ <% policy(form.object).editable_attributes.each do |field| %>
4
+ <td>
5
+ <%= form_print_field(form, field) %>
6
+ <%= error_messages_for(form.object, field) %>
7
+ </td>
8
+
9
+ <% end %>
10
+
11
+ <td class="d-flex justify-content-end">
12
+ <button type="button" class="btn btn-outline-danger" data-action="nested-form#remove"><%= icon("trash") %></button>
13
+ <%= form.hidden_field :_destroy %>
14
+ </td>
15
+
16
+ </tr>
@@ -0,0 +1,48 @@
1
+ <%# locals: (form:, field:,new_object:) -%>
2
+ <%= content_tag :table, class: "table", data: {controller: 'nested-form'} do %>
3
+
4
+ <thead>
5
+ <tr>
6
+ <% policy(new_object).editable_attributes.each do |field| %>
7
+ <th>
8
+ <%= new_object.class.human_attribute_name(field) %>
9
+ </th>
10
+ <% end %>
11
+ <th class="text-end">
12
+ <button type="button" data-action="nested-form#add" class="btn btn-outline-success"><%= icon("plus-lg") %></button>
13
+ </th>
14
+ </tr>
15
+ </thead>
16
+
17
+ <template data-nested-form-target="template">
18
+ <%= form.fields_for field, new_object, child_index: 'NEW_RECORD' do |form_for_sections| %>
19
+ <%= render "nested_row_form", form: form_for_sections %>
20
+ <% end %>
21
+ </template>
22
+
23
+ <tbody>
24
+
25
+ <%= form.fields_for field do |form_for_sections| %>
26
+ <%= render "nested_row_form", form: form_for_sections %>
27
+ <% end %>
28
+
29
+ <!-- Inserted elements will be injected before that target. -->
30
+ <tr data-nested-form-target="target"></tr>
31
+
32
+ </tbody>
33
+
34
+
35
+ <% end %>
36
+
37
+ <% if form.object.errors.key?(field) %>
38
+
39
+ <div class="row">
40
+ <div class="col-12">
41
+ <div class="alert alert-danger">
42
+ <%= form.object.errors[field].join(", ") %>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <% end %>
48
+
@@ -1 +1 @@
1
- 1.9.0
1
+ 1.10.0
@@ -30,6 +30,16 @@ module BaseEditingBootstrap
30
30
  gem 'rails-controller-testing', group: :test, comment: "Required if used with controllers spec"
31
31
  end
32
32
  end
33
+
34
+ def install_nested_attributes_dependencies
35
+ # attualmente penso sia più sensato semplicemente scrivere a video i passaggi necessari, dato che
36
+ # potrebbe essere già presente importmap, nested_attribute_controller e le varie configurazioni
37
+
38
+ say "Install dependencies for nested attributes:"
39
+ say " bin/importmap pin @stimulus-components/rails-nested-form"
40
+ say "Attiva quindi come spiegato qua: https://www.stimulus-components.com/docs/stimulus-rails-nested-form il controller"
41
+
42
+ end
33
43
  end
34
44
  end
35
45
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: base_editing_bootstrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marino Bonetti
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-30 00:00:00.000000000 Z
11
+ date: 2025-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -350,6 +350,7 @@ files:
350
350
  - app/views/base_editing/_index_main_buttons.html.erb
351
351
  - app/views/base_editing/_index_title_header.html.erb
352
352
  - app/views/base_editing/_navbar.html.erb
353
+ - app/views/base_editing/_nested_row_form.html.erb
353
354
  - app/views/base_editing/_new_page_title_header.html.erb
354
355
  - app/views/base_editing/_search.html.erb
355
356
  - app/views/base_editing/_search_field.erb
@@ -363,6 +364,7 @@ files:
363
364
  - app/views/base_editing/cell_field/_enum.html.erb
364
365
  - app/views/base_editing/cell_field/_timestamps.html.erb
365
366
  - app/views/base_editing/edit.html.erb
367
+ - app/views/base_editing/form_field/_accept_nested_field.html.erb
366
368
  - app/views/base_editing/form_field/_base.html.erb
367
369
  - app/views/base_editing/form_field/_belongs_to_select.html.erb
368
370
  - app/views/base_editing/form_field/_boolean.html.erb