base_editing_bootstrap 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +56 -0
- data/CHANGELOG.md +19 -0
- data/README.md +5 -0
- data/app/controllers/base_editing_controller.rb +16 -0
- data/app/helpers/utilities/form_helper.rb +16 -82
- data/app/policies/base_model_policy.rb +12 -0
- data/app/views/base_editing/_form_field.html.erb +7 -2
- data/app/views/base_editing/_form_field_container.html.erb +1 -1
- data/app/views/base_editing/_form_full_components.html.erb +7 -1
- data/app/views/base_editing/form_field/_decimal.html.erb +5 -1
- data/app/views/base_editing/form_field/_has_one_attachment.html.erb +10 -7
- data/lib/base_editing_bootstrap/VERSION +1 -1
- data/lib/base_editing_bootstrap/forms/base.rb +2 -0
- data/lib/base_editing_bootstrap/forms/field_renderer.rb +129 -0
- data/spec/support/external_shared/base_editing_controller_helpers.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8bd43711d49d65d4d703fb15361b66e41699844894e210b6509e7f6d35699b73
|
|
4
|
+
data.tar.gz: e22898cbe35cfe6c2545c4f5fe711ef4354e6943376699057e9a814da6f778e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad5021b0cdc14d54b8c88e3f35e9d2f9ad897b977f245553959fb1b6a560b7773a085c40b3a42cb1cccdc307c3b3d87e0586679b1032515dae67e75c3b14beb2
|
|
7
|
+
data.tar.gz: 4bd0b6718b83b4c8b7041e3ad94b229059909e7962ca361659230ac96b11df57c89c04e205ef6dd9fc3fd6d92e2f23445711197d0df0c3ec0d8da784fc7c5d2f
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides context and instructions for AI coding agents working on the **BaseEditingBootstrap** project.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
BaseEditingBootstrap is a Rails engine designed to provide a standardized, Bootstrap-based administrative interface for Rails models. It integrates with Pundit for authorization and Ransack for searching/sorting.
|
|
7
|
+
|
|
8
|
+
- **Main Components**:
|
|
9
|
+
- `BaseEditingController`: The base controller for all CRUD operations.
|
|
10
|
+
- `BaseModel`: A concern for models that should support standard editing.
|
|
11
|
+
- `BaseModelPolicy`: A base Pundit policy with specific methods for controlling UI elements.
|
|
12
|
+
|
|
13
|
+
## Build & Test
|
|
14
|
+
The project uses Docker for environment setup and testing.
|
|
15
|
+
|
|
16
|
+
- **Setup Environment**:
|
|
17
|
+
```bash
|
|
18
|
+
docker compose run app spec/dummy/bin/setup
|
|
19
|
+
```
|
|
20
|
+
- **Run Tests**:
|
|
21
|
+
```bash
|
|
22
|
+
docker compose run app bundle exec rspec
|
|
23
|
+
```
|
|
24
|
+
- **Start Development Server**:
|
|
25
|
+
```bash
|
|
26
|
+
docker compose up
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Development Workflow
|
|
30
|
+
- **Naming Conventions**: Follow standard Rails/Ruby naming conventions (snake_case for files and methods, CamelCase for classes).
|
|
31
|
+
- **Git Flow**: Use descriptive commit messages. No specific branching strategy is enforced, but use feature branches.
|
|
32
|
+
- **Testing**:
|
|
33
|
+
- **Controllers**: Use `it_behaves_like "base editing controller"`.
|
|
34
|
+
- **Models**: Use `it_behaves_like "a base model"`.
|
|
35
|
+
- **Policies**: Use `it_behaves_like "a standard base model policy"`.
|
|
36
|
+
- Ensure all new features or bug fixes include corresponding RSpec tests.
|
|
37
|
+
|
|
38
|
+
## Coding Conventions & Patterns
|
|
39
|
+
- **Pundit Policies**:
|
|
40
|
+
- Implement `editable_attributes` to define fields visible/editable in forms.
|
|
41
|
+
- Implement `search_result_fields` to define columns in the index table.
|
|
42
|
+
- Implement `search_fields` for Ransack search form fields.
|
|
43
|
+
- Implement `permitted_attributes` for Pundit strong parameters.
|
|
44
|
+
- **Factories**:
|
|
45
|
+
- Every model factory should include a trait `:with_invalid_attributes` for testing validation failures.
|
|
46
|
+
- **Form/Cell Overrides**:
|
|
47
|
+
- Use the provided generators for field/cell overrides:
|
|
48
|
+
```bash
|
|
49
|
+
rails g base_editing_bootstrap:field_override ModelName field1 field2:type
|
|
50
|
+
rails g base_editing_bootstrap:cell_override ModelName field1 field2:type
|
|
51
|
+
```
|
|
52
|
+
- **I18n**: Follow the project's translation structure for messages and labels (see `README.md`).
|
|
53
|
+
|
|
54
|
+
## Security
|
|
55
|
+
- Always use Pundit for authorization (`include Pundit::Authorization` in `ApplicationController`).
|
|
56
|
+
- Ensure `permitted_attributes` in policies correctly restrict user input.
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
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.16.0 - 2026-06-24
|
|
6
|
+
#### Features
|
|
7
|
+
- Aggiunta possibilità di customizzare label in checkbox - (9c96ddf) - Marino Bonetti
|
|
8
|
+
- Add option to hide fields in form by policy - (ec2a103) - Marino Bonetti
|
|
9
|
+
#### Bug Fixes
|
|
10
|
+
- Rimossa versione i18n non valida - (8e52029) - Marino Bonetti
|
|
11
|
+
- Visualizzazione validazione su file upload - (69223c1) - Marino Bonetti
|
|
12
|
+
- Update expectation to use be_a for search instance type - (3cc4acc) - Marino Bonetti
|
|
13
|
+
#### Miscellaneous Chores
|
|
14
|
+
- Fix Broken cogs configs - (da27444) - Marino Bonetti
|
|
15
|
+
|
|
16
|
+
- - -
|
|
17
|
+
|
|
18
|
+
## 1.15.1 - 2026-04-13
|
|
19
|
+
#### Bug Fixes
|
|
20
|
+
- Correzione pattern per decimali - (08847c0) - Marino Bonetti
|
|
21
|
+
|
|
22
|
+
- - -
|
|
23
|
+
|
|
5
24
|
## 1.15.0 - 2026-04-09
|
|
6
25
|
#### Features
|
|
7
26
|
- Add Read only mode (#25) - (0ba3da2) - Marino Bonetti
|
data/README.md
CHANGED
|
@@ -305,6 +305,11 @@ Al generator per i campi standard basta aggiungere il parametro `--readonly` per
|
|
|
305
305
|
rails g base_editing_bootstrap:field_override ModelName field1 field2:type --readonly
|
|
306
306
|
```
|
|
307
307
|
|
|
308
|
+
### Hidden Fields
|
|
309
|
+
E' possibile renderizzare i campi di un modello in modalità nascosta,
|
|
310
|
+
andando a ridefinire nella policy il metodo `attribute_is_hidden(attribute_name)`.
|
|
311
|
+
I campi nascosti verranno renderizzati tutti assieme all'inizio della form.
|
|
312
|
+
|
|
308
313
|
|
|
309
314
|
### Translations
|
|
310
315
|
|
|
@@ -5,7 +5,9 @@ class BaseEditingController < RestrictedAreaController
|
|
|
5
5
|
:edit_custom_polymorphic_path,
|
|
6
6
|
:form_attributes,
|
|
7
7
|
:form_builder,
|
|
8
|
+
:field_renderer_class,
|
|
8
9
|
:readonly_attribute?,
|
|
10
|
+
:hidden_attribute?,
|
|
9
11
|
:index_custom_polymorphic_path,
|
|
10
12
|
:new_custom_polymorphic_path,
|
|
11
13
|
:show_custom_polymorphic_path
|
|
@@ -122,6 +124,10 @@ class BaseEditingController < RestrictedAreaController
|
|
|
122
124
|
BaseEditingBootstrap::Forms::Base
|
|
123
125
|
end
|
|
124
126
|
|
|
127
|
+
def field_renderer_class
|
|
128
|
+
BaseEditingBootstrap::Forms::FieldRenderer
|
|
129
|
+
end
|
|
130
|
+
|
|
125
131
|
def form_attributes(model = base_class.new, action = override_pundit_action_name)
|
|
126
132
|
policy = policy(model)
|
|
127
133
|
method_name = if policy.respond_to?("editable_attributes_for_#{action}")
|
|
@@ -142,6 +148,16 @@ class BaseEditingController < RestrictedAreaController
|
|
|
142
148
|
policy.public_send(method_name, attribute)
|
|
143
149
|
end
|
|
144
150
|
|
|
151
|
+
def hidden_attribute?(attribute, model = base_class.new, action = override_pundit_action_name)
|
|
152
|
+
policy = policy(model)
|
|
153
|
+
method_name = if policy.respond_to?("attribute_is_hidden_for_#{action}")
|
|
154
|
+
"attribute_is_hidden_for_#{action}"
|
|
155
|
+
else
|
|
156
|
+
"attribute_is_hidden"
|
|
157
|
+
end
|
|
158
|
+
policy.public_send(method_name, attribute)
|
|
159
|
+
end
|
|
160
|
+
|
|
145
161
|
def load_object
|
|
146
162
|
@object = base_class.find(params[:id])
|
|
147
163
|
|
|
@@ -15,95 +15,29 @@ module Utilities
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
##
|
|
18
|
-
# Metodo per il partial corretto per
|
|
18
|
+
# Metodo per il partial corretto per restituire l'oggetto della form con le logiche per
|
|
19
|
+
# trovare il template e le informazioni necessarie
|
|
19
20
|
#
|
|
20
21
|
# @param [Forms::Base] form
|
|
21
22
|
# @param [Symbol] field
|
|
22
23
|
# @param [Boolean] readonly -> rende possibile nelle nested form, nel caso arrivi da un field padre che definisce
|
|
23
24
|
# il campo come readonly di non controllare nemmeno la policy(il padre ha priorità su figlio)
|
|
24
|
-
# @return [
|
|
25
|
-
def
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
type = :custom
|
|
29
|
-
elsif form.object.class.respond_to?(:defined_enums) && form.object.class.defined_enums.key?(field.to_s)
|
|
30
|
-
type = :enum
|
|
31
|
-
generic_field = "enum"
|
|
32
|
-
elsif form.object.class.respond_to?(:reflect_on_association) &&
|
|
33
|
-
form.object.class.reflect_on_association(field.to_s).is_a?(ActiveRecord::Reflection::BelongsToReflection) &&
|
|
34
|
-
!form.object.class.reflect_on_association(field.to_s).polymorphic? # non deve essere polymorphic
|
|
35
|
-
# Abbiamo una relazione belongs_to da gestire
|
|
36
|
-
reflection = form.object.class.reflect_on_association(field.to_s)
|
|
37
|
-
type = :belongs_to
|
|
38
|
-
generic_field = "belongs_to_select"
|
|
39
|
-
locals[:relation_class] = reflection.klass
|
|
40
|
-
locals[:foreign_key] = reflection.foreign_key
|
|
41
|
-
elsif form.object.class.respond_to?(:nested_attributes_options) &&
|
|
42
|
-
form.object.class.nested_attributes_options.key?(field.to_sym)
|
|
43
|
-
type = :nested_attributes
|
|
44
|
-
reflection = form.object.class.reflect_on_association(field.to_s)
|
|
45
|
-
case reflection
|
|
46
|
-
when ActiveRecord::Reflection::HasManyReflection
|
|
47
|
-
locals[:new_object] = reflection.klass.new(reflection.foreign_key => form.object)
|
|
48
|
-
generic_field = "accept_has_many_nested_field"
|
|
49
|
-
when ActiveRecord::Reflection::HasOneReflection
|
|
50
|
-
form.object.send(:"build_#{field}") unless form.object.send(field).present?
|
|
51
|
-
generic_field = "accept_has_one_nested_field"
|
|
52
|
-
else
|
|
53
|
-
raise "Unknown reflection for nested attributes #{field}->#{reflection.class}"
|
|
54
|
-
end
|
|
55
|
-
else
|
|
56
|
-
if form.object.class.respond_to?(:type_for_attribute)
|
|
57
|
-
type = form.object.class.type_for_attribute(field).type
|
|
58
|
-
|
|
59
|
-
# Se non abbiamo ancora il type tentiamo di capire se è di tipo attachment SINGOLO
|
|
60
|
-
if type.nil? and form.object.respond_to?(:"#{field}_attachment")
|
|
61
|
-
type = :has_one_attachment
|
|
62
|
-
end
|
|
63
|
-
else
|
|
64
|
-
type = :string
|
|
65
|
-
end
|
|
25
|
+
# @return [BaseEditingBootstrap::Forms::FormFieldRenderer]
|
|
26
|
+
def form_print_field_object(form, field, readonly: nil)
|
|
27
|
+
field_renderer_class.new(self, form, field, readonly: readonly)
|
|
28
|
+
end
|
|
66
29
|
|
|
67
|
-
case type
|
|
68
|
-
when :datetime
|
|
69
|
-
generic_field = "datetime"
|
|
70
|
-
when :date
|
|
71
|
-
generic_field = "date"
|
|
72
|
-
when :decimal
|
|
73
|
-
locals[:scale] = form.object.class.type_for_attribute(field).scale || 2
|
|
74
|
-
generic_field = "decimal"
|
|
75
|
-
when :float
|
|
76
|
-
locals[:scale] = 2 # usiamo il default dato che non abbiamo questa informazione negli attributes di rails
|
|
77
|
-
generic_field = "decimal"
|
|
78
|
-
when :integer
|
|
79
|
-
generic_field = "integer"
|
|
80
|
-
when :boolean
|
|
81
|
-
generic_field = "boolean"
|
|
82
|
-
when :has_one_attachment
|
|
83
|
-
generic_field = "has_one_attachment"
|
|
84
|
-
when :text
|
|
85
|
-
generic_field = "textarea"
|
|
86
|
-
else
|
|
87
|
-
generic_field = "base"
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
30
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
GENERIC_FIELD: #{generic_field}
|
|
102
|
-
TEMPLATE: #{template.short_identifier}
|
|
103
|
-
LOCALS:#{locals}
|
|
104
|
-
TEXT
|
|
105
|
-
end
|
|
106
|
-
template.render(self, locals)
|
|
31
|
+
##
|
|
32
|
+
# Metodo per il partial corretto per eseguire il render del campo della form
|
|
33
|
+
#
|
|
34
|
+
# @param [Forms::Base] form
|
|
35
|
+
# @param [Symbol] field
|
|
36
|
+
# @param [Boolean] readonly -> rende possibile nelle nested form, nel caso arrivi da un field padre che definisce
|
|
37
|
+
# il campo come readonly di non controllare nemmeno la policy(il padre ha priorità su figlio)
|
|
38
|
+
# @return [ActiveSupport::SafeBuffer]
|
|
39
|
+
def form_print_field(...)
|
|
40
|
+
form_print_field_object(...).render
|
|
107
41
|
end
|
|
108
42
|
|
|
109
43
|
end
|
|
@@ -36,6 +36,18 @@ class BaseModelPolicy < ApplicationPolicy
|
|
|
36
36
|
# @return [Boolean] true se l'attributo è di sola lettura, false altrimenti
|
|
37
37
|
def attribute_is_readonly(_attribute) = false
|
|
38
38
|
|
|
39
|
+
##
|
|
40
|
+
# Permette di specificare se un attributo deve essere trattato come hidden durante il rendering della form.
|
|
41
|
+
# Analogamente a attribute_is_readonly è possibile definire la versione specifica per azione:
|
|
42
|
+
# - attribute_is_hidden_for_create?
|
|
43
|
+
# - attribute_is_hidden_for_update?
|
|
44
|
+
# - attribute_is_hidden_for_ACTION_NAME?
|
|
45
|
+
#
|
|
46
|
+
# @param attribute [Symbol] nome dell'attributo
|
|
47
|
+
# @param action_name [String] nome dell'azione
|
|
48
|
+
# @return [Boolean] true se l'attributo è hidden (deve essere renderizzato come hidden_field), false altrimenti
|
|
49
|
+
def attribute_is_hidden(_attribute) = false
|
|
50
|
+
|
|
39
51
|
def permitted_attributes_for_ransack
|
|
40
52
|
record.class.column_names + record.class._ransackers.keys
|
|
41
53
|
end
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
<%# locals: (form:, form_field:,readonly:nil) -%>
|
|
8
8
|
<%
|
|
9
9
|
input_group_classes = ["mb-1"]
|
|
10
|
-
|
|
10
|
+
renderer = form_print_field_object(form, form_field, readonly: readonly)
|
|
11
|
+
content = renderer.render
|
|
11
12
|
unless content.match "checkbox"
|
|
12
13
|
input_group_classes << "input-group"
|
|
13
14
|
end
|
|
@@ -18,7 +19,11 @@
|
|
|
18
19
|
if content.match "form-switch"
|
|
19
20
|
label_content = nil
|
|
20
21
|
else
|
|
21
|
-
|
|
22
|
+
if renderer.type == :belongs_to
|
|
23
|
+
label_content = form.label(renderer.locals[:foreign_key], class: ["form-label", "form-#{form_field}-label"] )
|
|
24
|
+
else
|
|
25
|
+
label_content = form.label(form_field, class: ["form-label", "form-#{form_field}-label"])
|
|
26
|
+
end
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
%>
|
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
<%= render partial: "form_field_header", locals: {form:} %>
|
|
8
8
|
<%= render partial: "form_base_errors", locals: {form:} if form.object.errors.key?(:base) %>
|
|
9
9
|
<%= render layout: "form_fields_container" do %>
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
<% hidden_fields = form_attributes(form.object).select { |field| hidden_attribute?(field) } %>
|
|
12
|
+
<% hidden_fields.each do |form_field| %>
|
|
13
|
+
<%= form.hidden_field(form_field) %>
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
<%= render collection: form_attributes(form.object) - hidden_fields,
|
|
11
17
|
layout: "form_field_container",
|
|
12
18
|
partial: "form_field", locals: {form:, readonly: readonly} %>
|
|
13
19
|
<% end %>
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
<%# locals: (form:, field:, scale:2) -%>
|
|
2
|
-
|
|
2
|
+
<% if scale >= 14 %>
|
|
3
|
+
<%= form.text_field(field, step: "any", pattern: "[0-9]*[.,]?[0-9]{1,#{scale}}") %>
|
|
4
|
+
<% else %>
|
|
5
|
+
<%= form.number_field(field, step: "0.#{"0" * (scale - 1)}1") %>
|
|
6
|
+
<% end %>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<%# locals: (form:, field:) -%>
|
|
2
2
|
<%
|
|
3
|
-
|
|
3
|
+
attachment = form.object.send(field)
|
|
4
|
+
is_attached = attachment.attached?
|
|
5
|
+
blob = attachment.attachment&.blob
|
|
6
|
+
is_persisted_blob = blob&.persisted?
|
|
4
7
|
hidden_field_id = dom_id(form.object, "hidden_#{field}")
|
|
5
8
|
preview_image_id = dom_id(form.object, "preview_image_#{field}")
|
|
6
9
|
filename_id = dom_id(form.object, "filename_#{field}")
|
|
@@ -13,22 +16,22 @@
|
|
|
13
16
|
JAVASCRIPT
|
|
14
17
|
|
|
15
18
|
%>
|
|
16
|
-
<div class="d-flex">
|
|
17
|
-
<%= form.hidden_field field, value:
|
|
19
|
+
<div class="d-flex <%= form.object.errors.include?(field) ? "is-invalid" : "" %>">
|
|
20
|
+
<%= form.hidden_field field, value: attachment.signed_id, id: hidden_field_id if is_attached && is_persisted_blob %>
|
|
18
21
|
|
|
19
22
|
<%= form.file_field field, direct_upload: true %>
|
|
20
23
|
|
|
21
24
|
<% if is_attached %>
|
|
22
25
|
|
|
23
|
-
<%= content_tag :span,
|
|
26
|
+
<%= content_tag :span, blob.filename, class: "input-group-text flex-grow-1", id: filename_id if blob.present? %>
|
|
24
27
|
<%= content_tag :button, icon(:trash),
|
|
25
28
|
onclick: javascript_clear_event,
|
|
26
29
|
class: "btn btn-outline-secondary rounded-0" %>
|
|
27
|
-
<%= link_to icon(:download),
|
|
30
|
+
<%= link_to icon(:download), attachment, class: "btn btn-outline-secondary", target: :_blank if is_persisted_blob %>
|
|
28
31
|
|
|
29
|
-
<% if
|
|
32
|
+
<% if is_persisted_blob && attachment.representable? %>
|
|
30
33
|
<% content_for :form_field_ending, flush: true do %>
|
|
31
|
-
<%= content_tag :div, image_tag(
|
|
34
|
+
<%= content_tag :div, image_tag(attachment.representation(resize_to_limit: [100, 100])), id: preview_image_id %>
|
|
32
35
|
<% end %>
|
|
33
36
|
<% end %>
|
|
34
37
|
<% end %>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.16.0
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module BaseEditingBootstrap::Forms
|
|
2
|
+
# Responsible for deciding which template and locals to use for form fields
|
|
3
|
+
# Usage: Utilities::FormFieldRenderer.new(view_context, form, field, readonly:).render
|
|
4
|
+
class FieldRenderer
|
|
5
|
+
attr_reader :view, :form, :field, :locals, :type, :generic_field
|
|
6
|
+
|
|
7
|
+
def initialize(view, form, field, readonly: nil)
|
|
8
|
+
@view = view
|
|
9
|
+
@form = form
|
|
10
|
+
@field = field
|
|
11
|
+
@readonly = readonly
|
|
12
|
+
@locals = { form: form, field: field }
|
|
13
|
+
@type = nil
|
|
14
|
+
@generic_field = nil
|
|
15
|
+
determine_field_and_locals
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Perform full rendering and return SafeBuffer
|
|
19
|
+
def render
|
|
20
|
+
tmpl = template
|
|
21
|
+
view.bs_logger.debug do
|
|
22
|
+
<<~TEXT
|
|
23
|
+
TYPE: #{type}
|
|
24
|
+
GENERIC_FIELD: #{generic_field}
|
|
25
|
+
TEMPLATE: #{tmpl.short_identifier}
|
|
26
|
+
LOCALS:#{locals}
|
|
27
|
+
TEXT
|
|
28
|
+
end
|
|
29
|
+
tmpl.render(view, locals)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Public helpers for tests or external callers
|
|
33
|
+
def template
|
|
34
|
+
view.find_template_with_fallbacks(
|
|
35
|
+
form.object,
|
|
36
|
+
field,
|
|
37
|
+
"form_field",
|
|
38
|
+
generic_field,
|
|
39
|
+
readonly: readonly_value
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def readonly_value
|
|
44
|
+
@readonly.nil? ? view.readonly_attribute?(field, form.object) : @readonly
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def determine_field_and_locals
|
|
50
|
+
if form.object.class.respond_to?(:field_to_form_partial) &&
|
|
51
|
+
(generic = form.object.class.field_to_form_partial(field)
|
|
52
|
+
)
|
|
53
|
+
@type = :custom
|
|
54
|
+
@generic_field = generic
|
|
55
|
+
elsif form.object.class.respond_to?(:defined_enums) && form.object.class.defined_enums.key?(field.to_s)
|
|
56
|
+
@type = :enum
|
|
57
|
+
@generic_field = "enum"
|
|
58
|
+
elsif belongs_to_reflection?
|
|
59
|
+
reflection = form.object.class.reflect_on_association(field.to_s)
|
|
60
|
+
@type = :belongs_to
|
|
61
|
+
@generic_field = "belongs_to_select"
|
|
62
|
+
locals[:relation_class] = reflection.klass
|
|
63
|
+
locals[:foreign_key] = reflection.foreign_key
|
|
64
|
+
elsif nested_attributes?
|
|
65
|
+
@type = :nested_attributes
|
|
66
|
+
reflection = form.object.class.reflect_on_association(field.to_s)
|
|
67
|
+
case reflection
|
|
68
|
+
when ActiveRecord::Reflection::HasManyReflection
|
|
69
|
+
locals[:new_object] = reflection.klass.new(reflection.foreign_key => form.object)
|
|
70
|
+
@generic_field = "accept_has_many_nested_field"
|
|
71
|
+
when ActiveRecord::Reflection::HasOneReflection
|
|
72
|
+
form.object.send(:"build_#{field}") unless form.object.send(field).present?
|
|
73
|
+
@generic_field = "accept_has_one_nested_field"
|
|
74
|
+
else
|
|
75
|
+
raise "Unknown reflection for nested attributes #{field}->#{reflection.class}"
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
determine_by_attribute_type
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def belongs_to_reflection?
|
|
83
|
+
form.object.class.respond_to?(:reflect_on_association) &&
|
|
84
|
+
(ref = form.object.class.reflect_on_association(field.to_s)).is_a?(ActiveRecord::Reflection::BelongsToReflection) &&
|
|
85
|
+
!ref.polymorphic?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def nested_attributes?
|
|
89
|
+
form.object.class.respond_to?(:nested_attributes_options) &&
|
|
90
|
+
form.object.class.nested_attributes_options.key?(field.to_sym)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def determine_by_attribute_type
|
|
94
|
+
if form.object.class.respond_to?(:type_for_attribute)
|
|
95
|
+
@type = form.object.class.type_for_attribute(field).type
|
|
96
|
+
|
|
97
|
+
# If nil, check for single attachment
|
|
98
|
+
if @type.nil? && form.object.respond_to?(:"#{field}_attachment")
|
|
99
|
+
@type = :has_one_attachment
|
|
100
|
+
end
|
|
101
|
+
else
|
|
102
|
+
@type = :string
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
case type
|
|
106
|
+
when :datetime
|
|
107
|
+
@generic_field = "datetime"
|
|
108
|
+
when :date
|
|
109
|
+
@generic_field = "date"
|
|
110
|
+
when :decimal
|
|
111
|
+
locals[:scale] = form.object.class.type_for_attribute(field).scale || 2
|
|
112
|
+
@generic_field = "decimal"
|
|
113
|
+
when :float
|
|
114
|
+
locals[:scale] = 2
|
|
115
|
+
@generic_field = "decimal"
|
|
116
|
+
when :integer
|
|
117
|
+
@generic_field = "integer"
|
|
118
|
+
when :boolean
|
|
119
|
+
@generic_field = "boolean"
|
|
120
|
+
when :has_one_attachment
|
|
121
|
+
@generic_field = "has_one_attachment"
|
|
122
|
+
when :text
|
|
123
|
+
@generic_field = "textarea"
|
|
124
|
+
else
|
|
125
|
+
@generic_field = "base"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -114,7 +114,7 @@ RSpec.shared_examples "base editing controller" do |factory: nil, only: [], exce
|
|
|
114
114
|
params = {q: {"foo_eq": "foo"}}
|
|
115
115
|
get url_for_index, params: params
|
|
116
116
|
expect(response).to have_http_status(200)
|
|
117
|
-
expect(assigns[:search_instance]).to
|
|
117
|
+
expect(assigns[:search_instance]).to be_a(BaseEditingBootstrap::Searches::Base)
|
|
118
118
|
.and(have_attributes(
|
|
119
119
|
user: user,
|
|
120
120
|
params: ActionController::Parameters.new(params).permit!,
|
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.
|
|
4
|
+
version: 1.16.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: 2026-
|
|
11
|
+
date: 2026-06-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -313,6 +313,7 @@ executables: []
|
|
|
313
313
|
extensions: []
|
|
314
314
|
extra_rdoc_files: []
|
|
315
315
|
files:
|
|
316
|
+
- AGENTS.md
|
|
316
317
|
- CHANGELOG.md
|
|
317
318
|
- CODE_OF_CONDUCT.md
|
|
318
319
|
- LICENSE.txt
|
|
@@ -413,6 +414,7 @@ files:
|
|
|
413
414
|
- lib/base_editing_bootstrap/base_model.rb
|
|
414
415
|
- lib/base_editing_bootstrap/engine.rb
|
|
415
416
|
- lib/base_editing_bootstrap/forms/base.rb
|
|
417
|
+
- lib/base_editing_bootstrap/forms/field_renderer.rb
|
|
416
418
|
- lib/base_editing_bootstrap/generators_helpers.rb
|
|
417
419
|
- lib/base_editing_bootstrap/is_validated.rb
|
|
418
420
|
- lib/base_editing_bootstrap/logging.rb
|