active_element 0.0.10 → 0.0.12
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/.rubocop.yml +12 -2
- data/.strong_versions.yml +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +115 -75
- data/Makefile +10 -0
- data/active_element.gemspec +1 -1
- data/app/assets/javascripts/active_element/application.js +1 -0
- data/app/assets/javascripts/active_element/form.js +16 -32
- data/app/assets/javascripts/active_element/json_field.js +391 -135
- data/app/assets/javascripts/active_element/setup.js +13 -8
- data/app/assets/javascripts/active_element/text_search_field.js +38 -27
- data/app/assets/javascripts/active_element/theme.js +1 -1
- data/app/assets/javascripts/active_element/timezones.js +6 -0
- data/app/assets/stylesheets/active_element/_dark.scss +86 -0
- data/app/assets/stylesheets/active_element/_variables.scss +2 -1
- data/app/assets/stylesheets/active_element/application.scss +166 -33
- data/app/controllers/active_element/application_controller.rb +5 -0
- data/app/controllers/concerns/active_element/default_controller_actions.rb +38 -0
- data/app/views/active_element/_user.html.erb +20 -0
- data/app/views/active_element/components/fields/_json.html.erb +24 -0
- data/app/views/active_element/components/form/_check_box.html.erb +1 -0
- data/app/views/active_element/components/form/_check_boxes.html.erb +1 -1
- data/app/views/active_element/components/form/_datetime_range_field.html.erb +14 -0
- data/app/views/active_element/components/form/_field.html.erb +10 -7
- data/app/views/active_element/components/form/_generic_field.html.erb +1 -0
- data/app/views/active_element/components/form/_json.html.erb +10 -2
- data/app/views/active_element/components/form/_label.html.erb +12 -1
- data/app/views/active_element/components/form/_select.html.erb +4 -1
- data/app/views/active_element/components/form/_summary.html.erb +11 -1
- data/app/views/active_element/components/form/_templates.html.erb +42 -24
- data/app/views/active_element/components/form/_text_area.html.erb +2 -1
- data/app/views/active_element/components/form/_text_search.html.erb +8 -4
- data/app/views/active_element/components/form.html.erb +20 -17
- data/app/views/active_element/components/json.html.erb +1 -0
- data/app/views/active_element/components/navbar.html.erb +26 -0
- data/app/views/active_element/components/table/_collection_row.html.erb +2 -1
- data/app/views/active_element/components/table/_field.html.erb +8 -0
- data/app/views/active_element/components/table/_ungrouped_collection.html.erb +1 -0
- data/app/views/active_element/components/table/collection.html.erb +1 -1
- data/app/views/active_element/components/table/item.html.erb +6 -4
- data/app/views/active_element/default_views/edit.html.erb +5 -0
- data/app/views/active_element/default_views/forbidden.html.erb +7 -0
- data/app/views/active_element/default_views/index.html.erb +15 -0
- data/app/views/active_element/default_views/new.html.erb +4 -0
- data/app/views/active_element/default_views/show.html.erb +7 -0
- data/app/views/active_element/navbar/_menu.html.erb +1 -30
- data/app/views/active_element/theme/_select.html.erb +1 -1
- data/app/views/layouts/active_element.html.erb +16 -1
- data/config/brakeman.ignore +48 -0
- data/config/locales/en.yml +3 -0
- data/example_app/.gitattributes +7 -0
- data/example_app/.gitignore +35 -0
- data/example_app/.ruby-version +1 -0
- data/example_app/Gemfile +34 -0
- data/example_app/Gemfile.lock +296 -0
- data/example_app/README.md +24 -0
- data/example_app/Rakefile +6 -0
- data/example_app/app/assets/config/manifest.js +4 -0
- data/example_app/app/assets/images/.keep +0 -0
- data/example_app/app/assets/stylesheets/application.css +15 -0
- data/example_app/app/channels/application_cable/channel.rb +4 -0
- data/example_app/app/channels/application_cable/connection.rb +4 -0
- data/example_app/app/controllers/application_controller.rb +12 -0
- data/example_app/app/controllers/concerns/.keep +0 -0
- data/example_app/app/controllers/pets_controller.rb +7 -0
- data/example_app/app/controllers/users_controller.rb +7 -0
- data/example_app/app/helpers/application_helper.rb +2 -0
- data/example_app/app/javascript/application.js +3 -0
- data/example_app/app/javascript/controllers/application.js +9 -0
- data/example_app/app/javascript/controllers/hello_controller.js +7 -0
- data/example_app/app/javascript/controllers/index.js +11 -0
- data/example_app/app/jobs/application_job.rb +7 -0
- data/example_app/app/mailers/application_mailer.rb +4 -0
- data/example_app/app/models/application_record.rb +3 -0
- data/example_app/app/models/concerns/.keep +0 -0
- data/example_app/app/models/pet.rb +3 -0
- data/example_app/app/models/user.rb +8 -0
- data/example_app/app/views/layouts/application.html.erb +16 -0
- data/example_app/app/views/layouts/mailer.html.erb +13 -0
- data/example_app/app/views/layouts/mailer.text.erb +1 -0
- data/example_app/app/views/pets/index.html.erb +3 -0
- data/example_app/app/views/users/show.html.erb +3 -0
- data/example_app/bin/bundle +109 -0
- data/example_app/bin/importmap +4 -0
- data/example_app/bin/rails +4 -0
- data/example_app/bin/rake +4 -0
- data/example_app/bin/setup +33 -0
- data/example_app/config/application.rb +22 -0
- data/example_app/config/boot.rb +4 -0
- data/example_app/config/cable.yml +10 -0
- data/example_app/config/credentials.yml.enc +1 -0
- data/example_app/config/database.yml +25 -0
- data/example_app/config/environment.rb +5 -0
- data/example_app/config/environments/development.rb +70 -0
- data/example_app/config/environments/production.rb +93 -0
- data/example_app/config/environments/test.rb +60 -0
- data/example_app/config/importmap.rb +7 -0
- data/example_app/config/initializers/assets.rb +12 -0
- data/example_app/config/initializers/content_security_policy.rb +25 -0
- data/example_app/config/initializers/devise.rb +16 -0
- data/example_app/config/initializers/filter_parameter_logging.rb +8 -0
- data/example_app/config/initializers/inflections.rb +16 -0
- data/example_app/config/initializers/permissions_policy.rb +11 -0
- data/example_app/config/locales/devise.en.yml +65 -0
- data/example_app/config/locales/en.yml +33 -0
- data/example_app/config/puma.rb +43 -0
- data/example_app/config/routes.rb +8 -0
- data/example_app/config/storage.yml +34 -0
- data/example_app/config.ru +6 -0
- data/example_app/db/migrate/20230616210539_create_pet.rb +12 -0
- data/example_app/db/migrate/20230616211328_devise_create_users.rb +46 -0
- data/example_app/db/schema.rb +37 -0
- data/example_app/db/seeds.rb +33 -0
- data/example_app/lib/assets/.keep +0 -0
- data/example_app/lib/tasks/.keep +0 -0
- data/example_app/log/.keep +0 -0
- data/example_app/public/404.html +67 -0
- data/example_app/public/422.html +67 -0
- data/example_app/public/500.html +66 -0
- data/example_app/public/apple-touch-icon-precomposed.png +0 -0
- data/example_app/public/apple-touch-icon.png +0 -0
- data/example_app/public/favicon.ico +0 -0
- data/example_app/public/robots.txt +1 -0
- data/example_app/storage/.keep +0 -0
- data/example_app/test/application_system_test_case.rb +5 -0
- data/example_app/test/channels/application_cable/connection_test.rb +11 -0
- data/example_app/test/controllers/.keep +0 -0
- data/example_app/test/fixtures/files/.keep +0 -0
- data/example_app/test/fixtures/users.yml +11 -0
- data/example_app/test/helpers/.keep +0 -0
- data/example_app/test/integration/.keep +0 -0
- data/example_app/test/mailers/.keep +0 -0
- data/example_app/test/models/.keep +0 -0
- data/example_app/test/models/user_test.rb +7 -0
- data/example_app/test/system/.keep +0 -0
- data/example_app/test/test_helper.rb +13 -0
- data/example_app/tmp/.keep +0 -0
- data/example_app/tmp/pids/.keep +0 -0
- data/example_app/tmp/storage/.keep +0 -0
- data/example_app/vendor/.keep +0 -0
- data/example_app/vendor/javascript/.keep +0 -0
- data/lib/active_element/component.rb +9 -2
- data/lib/active_element/components/collection_table.rb +9 -2
- data/lib/active_element/components/email_fields.rb +14 -0
- data/lib/active_element/components/form.rb +48 -17
- data/lib/active_element/components/navbar.rb +64 -0
- data/lib/active_element/components/phone_fields.rb +14 -0
- data/lib/active_element/components/text_search/authorization.rb +9 -6
- data/lib/active_element/components/text_search/component.rb +4 -2
- data/lib/active_element/components/text_search.rb +13 -0
- data/lib/active_element/components/util/association_mapping.rb +74 -19
- data/lib/active_element/components/util/display_value_mapping.rb +13 -4
- data/lib/active_element/components/util/form_field_mapping.rb +139 -10
- data/lib/active_element/components/util/form_value_mapping.rb +3 -3
- data/lib/active_element/components/util/i18n.rb +1 -1
- data/lib/active_element/components/util/numeric_field.rb +73 -0
- data/lib/active_element/components/util/record_mapping.rb +43 -11
- data/lib/active_element/components/util/record_path.rb +21 -4
- data/lib/active_element/components/util.rb +13 -5
- data/lib/active_element/components.rb +3 -0
- data/lib/active_element/controller_action.rb +8 -2
- data/lib/active_element/controller_interface.rb +56 -18
- data/lib/active_element/controller_state.rb +44 -0
- data/lib/active_element/default_controller.rb +137 -0
- data/lib/active_element/default_record_params.rb +62 -0
- data/lib/active_element/default_search.rb +110 -0
- data/lib/active_element/json_field_schema.rb +59 -0
- data/lib/active_element/pre_render_processors/json.rb +98 -0
- data/lib/active_element/pre_render_processors.rb +11 -0
- data/lib/active_element/route.rb +12 -0
- data/lib/active_element/routes.rb +2 -1
- data/lib/active_element/version.rb +1 -1
- data/lib/active_element.rb +15 -32
- data/lib/tasks/active_element.rake +12 -1
- data/rspec-documentation/_head.html.erb +34 -0
- data/rspec-documentation/pages/000-Introduction.md +18 -0
- data/rspec-documentation/pages/005-Setup.md +75 -0
- data/rspec-documentation/pages/010-Components/Form Fields/Check Boxes.md +1 -0
- data/rspec-documentation/pages/010-Components/Form Fields/JSON/Controller Params.md +97 -0
- data/rspec-documentation/pages/010-Components/Form Fields/JSON/Schema.md +283 -0
- data/rspec-documentation/pages/010-Components/Form Fields/JSON/Types.md +36 -0
- data/rspec-documentation/pages/010-Components/Form Fields/JSON.md +70 -0
- data/rspec-documentation/pages/010-Components/Form Fields/Text Search.md +133 -0
- data/rspec-documentation/pages/010-Components/Form Fields.md +46 -0
- data/rspec-documentation/pages/010-Components/Forms.md +44 -0
- data/rspec-documentation/pages/010-Components/JSON Data.md +23 -0
- data/rspec-documentation/pages/010-Components/Navbar.md +56 -0
- data/rspec-documentation/pages/010-Components/Page Section Title.md +13 -0
- data/rspec-documentation/pages/010-Components/Page Subtitle.md +11 -0
- data/rspec-documentation/pages/010-Components/Page Title.md +11 -0
- data/rspec-documentation/pages/010-Components/Tables/Collection Table.md +29 -0
- data/rspec-documentation/pages/010-Components/Tables/Item Table.md +18 -0
- data/rspec-documentation/pages/010-Components/Tables/Options.md +19 -0
- data/rspec-documentation/pages/010-Components/Tables.md +29 -0
- data/rspec-documentation/pages/010-Components.md +15 -0
- data/rspec-documentation/pages/020-Access Control/010-Authentication.md +20 -0
- data/rspec-documentation/pages/020-Access Control/020-Authorization/Environments.md +9 -0
- data/rspec-documentation/pages/020-Access Control/020-Authorization/Permissions/Custom Routes.md +41 -0
- data/rspec-documentation/pages/020-Access Control/020-Authorization/Permissions.md +58 -0
- data/rspec-documentation/pages/020-Access Control/020-Authorization/Setup.md +27 -0
- data/rspec-documentation/pages/020-Access Control/020-Authorization.md +11 -0
- data/rspec-documentation/pages/020-Access Control.md +31 -0
- data/rspec-documentation/pages/040-Decorators/Inline Decorators.md +24 -0
- data/rspec-documentation/pages/040-Decorators/View Decorators.md +55 -0
- data/rspec-documentation/pages/040-Decorators.md +12 -0
- data/rspec-documentation/pages/300-Alternatives.md +21 -0
- data/rspec-documentation/pages/900-License.md +11 -0
- data/rspec-documentation/spec_helper.rb +53 -16
- data/rspec-documentation/support.rb +84 -0
- metadata +159 -14
- data/rspec-documentation/pages/Components/Forms.md +0 -1
- data/rspec-documentation/pages/Components/Tables.md +0 -47
- data/rspec-documentation/pages/Components.md +0 -1
- data/rspec-documentation/pages/Decorators/Inline Decorators.md +0 -1
- data/rspec-documentation/pages/Decorators/View Decorators.md +0 -1
- data/rspec-documentation/pages/Index.md +0 -3
- data/rspec-documentation/pages/Util/I18n.md +0 -1
- /data/rspec-documentation/pages/{Components → 010-Components}/Tabs.md +0 -0
@@ -4,13 +4,17 @@ module ActiveElement
|
|
4
4
|
module Components
|
5
5
|
module Util
|
6
6
|
# Normalizes Form `fields` parameter from various supported input formats.
|
7
|
-
class FormFieldMapping
|
7
|
+
class FormFieldMapping # rubocop:disable Metrics/ClassLength
|
8
8
|
include SecretFields
|
9
|
+
include PhoneFields
|
10
|
+
include EmailFields
|
9
11
|
|
10
|
-
def initialize(record
|
12
|
+
def initialize(record:, fields:, controller:, i18n:, search: false)
|
11
13
|
@record = record
|
12
14
|
@fields = fields
|
15
|
+
@controller = controller
|
13
16
|
@i18n = i18n
|
17
|
+
@search = search
|
14
18
|
end
|
15
19
|
|
16
20
|
def fields_with_types_and_options
|
@@ -28,7 +32,7 @@ module ActiveElement
|
|
28
32
|
|
29
33
|
private
|
30
34
|
|
31
|
-
attr_reader :fields, :i18n, :record
|
35
|
+
attr_reader :fields, :i18n, :record, :controller, :search
|
32
36
|
|
33
37
|
def normalized_field?(field)
|
34
38
|
(field.size == 3) && field.last.is_a?(Hash)
|
@@ -43,29 +47,116 @@ module ActiveElement
|
|
43
47
|
end
|
44
48
|
|
45
49
|
def field_with_default_type_and_default_options(field)
|
46
|
-
[field,
|
50
|
+
return [field, type_from_file(field).to_sym, options_from_file(field)] if file_configuration?(field)
|
51
|
+
return relation_text_search_field(field) if relation?(field) && record.present? && !search?
|
52
|
+
|
53
|
+
[field, default_type_from_model(field), default_options(field)]
|
47
54
|
end
|
48
55
|
|
49
56
|
def field_with_type(field)
|
50
|
-
[field.first, field.last,
|
57
|
+
[field.first, field.last, default_options(field)]
|
58
|
+
end
|
59
|
+
|
60
|
+
def association_mapping(field)
|
61
|
+
@association_mapping ||= AssociationMapping.new(
|
62
|
+
controller: controller,
|
63
|
+
field: field,
|
64
|
+
record: record,
|
65
|
+
associated_record: record.public_send(field)
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def file_configuration?(field)
|
70
|
+
# XXX: json_field schema is loaded separately so we ignore file configuration here.
|
71
|
+
# This does prevent specifying an alternative field type in a config file but can still
|
72
|
+
# be defined inline if e.g. a user wants a text_area instead of a json_field:
|
73
|
+
#
|
74
|
+
# active_element.component.form fields: [:foo, :bar, [:some_json_field, :text_area]]
|
75
|
+
#
|
76
|
+
file_configuration_path(field).file? && default_type_from_model(field) != :json_field
|
77
|
+
end
|
78
|
+
|
79
|
+
def type_from_file(field)
|
80
|
+
YAML.safe_load(file_configuration_path(field).read, symbolize_names: true)
|
81
|
+
.fetch(:type, default_type_from_model(field))
|
82
|
+
end
|
83
|
+
|
84
|
+
def options_from_file(field)
|
85
|
+
YAML.safe_load(file_configuration_path(field).read, symbolize_names: true).fetch(:options, {})
|
86
|
+
end
|
87
|
+
|
88
|
+
def file_configuration_path(field)
|
89
|
+
record_field_configuration_path(field) || sti_field_configuration_path(field)
|
90
|
+
end
|
91
|
+
|
92
|
+
def record_field_configuration_path(field)
|
93
|
+
record_name = Util.record_name(record)
|
94
|
+
return nil if record_name.blank?
|
95
|
+
|
96
|
+
Rails.root.join('config/forms', record_name, "#{field}.yml")
|
97
|
+
end
|
98
|
+
|
99
|
+
def sti_record_field_configuration_path(field)
|
100
|
+
sti_record_name = Util.sti_record_name(record)
|
101
|
+
return nil if sti_record_name.blank?
|
102
|
+
|
103
|
+
Rails.root.join('config/forms', sti_record_name, "#{field}.yml")
|
51
104
|
end
|
52
105
|
|
53
106
|
def default_type_from_model(field)
|
54
107
|
return default_field_type(field) if record.blank?
|
108
|
+
return default_field_type(field) if column(field).blank?
|
109
|
+
|
110
|
+
default_type_from_column_type(field, column(field).type)
|
111
|
+
end
|
112
|
+
|
113
|
+
def column(field)
|
114
|
+
model&.columns&.find { |model_column| model_column.name.to_s == field.to_s }
|
115
|
+
end
|
116
|
+
|
117
|
+
def model
|
118
|
+
record&.class || controller.controller_name.classify.safe_constantize
|
119
|
+
end
|
120
|
+
|
121
|
+
def relation?(field)
|
122
|
+
relation(field).present?
|
123
|
+
end
|
124
|
+
|
125
|
+
def relation(field)
|
126
|
+
model&.reflect_on_association(field)
|
127
|
+
end
|
55
128
|
|
56
|
-
|
57
|
-
|
129
|
+
def relation_text_search_field(field)
|
130
|
+
[field, :text_search_field,
|
131
|
+
TextSearch.text_search_options(
|
132
|
+
model: relation(field).klass,
|
133
|
+
with: searchable_fields(field),
|
134
|
+
providing: relation(field).klass.primary_key
|
135
|
+
).merge({ display_value: association_mapping(field).display_value, label: i18n.label(field) })]
|
136
|
+
end
|
137
|
+
|
138
|
+
def searchable_fields(field)
|
139
|
+
(Util.relation_controller(model, controller, field)&.active_element&.state&.searchable_fields || [])
|
140
|
+
.reject { |field| field.to_s.end_with?('_at') } # FIXME: Select strings/numbers only.
|
141
|
+
end
|
58
142
|
|
59
|
-
|
143
|
+
def relation_primary_key(field)
|
144
|
+
relation(field).options.fetch(:primary_key) { relation_model.primary_key }
|
60
145
|
end
|
61
146
|
|
62
|
-
def default_type_from_column_type(field, column_type)
|
147
|
+
def default_type_from_column_type(field, column_type) # rubocop:disable Metrics/MethodLength
|
63
148
|
{
|
64
149
|
string: default_field_type(field),
|
65
150
|
boolean: :check_box,
|
66
151
|
json: :json_field,
|
67
152
|
jsonb: :json_field,
|
68
|
-
geometry: :text_area
|
153
|
+
geometry: :text_area,
|
154
|
+
datetime: :datetime_field,
|
155
|
+
date: :date_field,
|
156
|
+
time: :time_field,
|
157
|
+
integer: :number_field,
|
158
|
+
decimal: :number_field,
|
159
|
+
float: :number_field
|
69
160
|
}.fetch(column_type.to_sym, default_field_type(field))
|
70
161
|
end
|
71
162
|
|
@@ -94,10 +185,48 @@ module ActiveElement
|
|
94
185
|
end
|
95
186
|
|
96
187
|
def default_field_type(field)
|
188
|
+
return default_search_field_type(field) if search?
|
97
189
|
return :password_field if secret_field?(field)
|
190
|
+
return :email_field if email_field?(field)
|
191
|
+
return :phone_field if phone_field?(field)
|
192
|
+
|
193
|
+
:text_field
|
194
|
+
end
|
195
|
+
|
196
|
+
def search?
|
197
|
+
search
|
198
|
+
end
|
199
|
+
|
200
|
+
def default_search_field_type(field)
|
201
|
+
return :datetime_range_field if column(field)&.type == :datetime
|
98
202
|
|
99
203
|
:text_field
|
100
204
|
end
|
205
|
+
|
206
|
+
def default_options(field)
|
207
|
+
{
|
208
|
+
required: required?(field)
|
209
|
+
}.merge(field_options(field))
|
210
|
+
end
|
211
|
+
|
212
|
+
def required?(field)
|
213
|
+
return false if record.blank?
|
214
|
+
return false unless record.class.respond_to?(:validators)
|
215
|
+
|
216
|
+
record.class.validators.find do |validator|
|
217
|
+
validator.kind == :presence && validator.attributes.include?(field.to_sym)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def field_options(field)
|
222
|
+
return NumericField.new(field: field, column: column(field)).options if numeric?(field)
|
223
|
+
|
224
|
+
{}
|
225
|
+
end
|
226
|
+
|
227
|
+
def numeric?(field)
|
228
|
+
%i[float decimal integer].include?(column(field)&.type)
|
229
|
+
end
|
101
230
|
end
|
102
231
|
end
|
103
232
|
end
|
@@ -20,15 +20,15 @@ module ActiveElement
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def datetime_value
|
23
|
-
value_from_record.strftime('%Y-%m-%d %H:%M:%S')
|
23
|
+
with_timezone_offset(value_from_record).strftime('%Y-%m-%d %H:%M:%S')
|
24
24
|
end
|
25
25
|
|
26
26
|
def time_value
|
27
|
-
value_from_record.strftime('%H:%M:%S')
|
27
|
+
with_timezone_offset(value_from_record).strftime('%H:%M:%S')
|
28
28
|
end
|
29
29
|
|
30
30
|
def date_value
|
31
|
-
value_from_record.strftime('%Y-%m-%d')
|
31
|
+
with_timezone_offset(value_from_record).strftime('%Y-%m-%d')
|
32
32
|
end
|
33
33
|
|
34
34
|
def boolean_value
|
@@ -7,7 +7,7 @@ module ActiveElement
|
|
7
7
|
# descriptions, placeholders, etc. for various components in locales files.
|
8
8
|
class I18n
|
9
9
|
def self.class_name(val, plural: false)
|
10
|
-
base = val&.to_s&.underscore&.tr('
|
10
|
+
base = val&.to_s&.underscore&.tr('/', '_')
|
11
11
|
plural ? base&.pluralize : base
|
12
12
|
end
|
13
13
|
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveElement
|
4
|
+
module Components
|
5
|
+
module Util
|
6
|
+
# Provides options for a `<input type="number" />` element based on database column properties.
|
7
|
+
class NumericField
|
8
|
+
def initialize(field:, column:)
|
9
|
+
@field = field
|
10
|
+
@column = column
|
11
|
+
end
|
12
|
+
|
13
|
+
def options
|
14
|
+
{
|
15
|
+
step: step,
|
16
|
+
min: min,
|
17
|
+
max: max
|
18
|
+
}.compact
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :field, :column
|
24
|
+
|
25
|
+
def step
|
26
|
+
return 'any' if column.blank?
|
27
|
+
return '1' if column.type == :integer
|
28
|
+
return 'any' if column.try(:scale).blank?
|
29
|
+
|
30
|
+
"0.#{'1'.rjust(column.scale, '0')}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def min
|
34
|
+
return min_decimal if column.try(:precision).present?
|
35
|
+
return min_integer if column.try(:limit).present?
|
36
|
+
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def max
|
41
|
+
return max_decimal if column.try(:precision).present?
|
42
|
+
return max_integer if column.try(:limit).present?
|
43
|
+
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# XXX: This is the theoretical maximum value for a column with a given precision but,
|
48
|
+
# since the maximum database value is constrained by total significant figures (i.e.
|
49
|
+
# before and after the decimal point), an input can still be provided that would cause an
|
50
|
+
# error, so the default controller rescues `ActiveRecord::RangeError` to deal with this.
|
51
|
+
def max_decimal
|
52
|
+
'9' * column.precision
|
53
|
+
end
|
54
|
+
|
55
|
+
def min_decimal
|
56
|
+
"-#{'9' * column.precision}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# `limit` represents available bytes for storing a signed integer e.g.
|
60
|
+
# 2**(8 * 8) / 2 - 1 == 9223372036854775807
|
61
|
+
# which matches PostgreSQL's `bigint` max value:
|
62
|
+
# https://www.postgresql.org/docs/current/datatype-numeric.html
|
63
|
+
def max_integer
|
64
|
+
((2**(column.limit * 8)) / 2) - 1
|
65
|
+
end
|
66
|
+
|
67
|
+
def min_integer
|
68
|
+
-2**(column.limit * 8) / 2
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -49,41 +49,60 @@ module ActiveElement
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def mapped_value_from_record
|
52
|
-
return mapped_association_from_record if
|
52
|
+
return mapped_association_from_record if association?
|
53
53
|
return nil if value_from_record.nil?
|
54
54
|
return value_from_record unless DATABASE_TYPES.include?(column.type) || value_from_record.blank?
|
55
55
|
|
56
56
|
send("#{column.type}_value")
|
57
57
|
end
|
58
58
|
|
59
|
-
def
|
60
|
-
AssociationMapping.new(
|
61
|
-
|
59
|
+
def association_mapping
|
60
|
+
@association_mapping ||= AssociationMapping.new(
|
61
|
+
controller: component.controller,
|
62
62
|
field: field,
|
63
63
|
record: record,
|
64
64
|
associated_record: value_from_record,
|
65
65
|
options: options
|
66
|
-
)
|
66
|
+
)
|
67
67
|
end
|
68
68
|
|
69
69
|
# Override these methods as required in a class that includes this module:
|
70
70
|
|
71
|
+
def mapped_association_from_record
|
72
|
+
association_mapping.relation_id
|
73
|
+
end
|
74
|
+
|
71
75
|
def numeric_value
|
72
76
|
value_from_record
|
73
77
|
end
|
74
|
-
|
75
|
-
|
76
|
-
|
78
|
+
|
79
|
+
def integer_value
|
80
|
+
numeric_value
|
81
|
+
end
|
82
|
+
|
83
|
+
def decimal_value
|
84
|
+
numeric_value
|
85
|
+
end
|
86
|
+
|
87
|
+
def float_value
|
88
|
+
numeric_value
|
89
|
+
end
|
77
90
|
|
78
91
|
def json_value
|
79
92
|
value_from_record
|
80
93
|
end
|
81
|
-
|
94
|
+
|
95
|
+
def jsonb_value
|
96
|
+
json_value
|
97
|
+
end
|
82
98
|
|
83
99
|
def string_value
|
84
100
|
value_from_record
|
85
101
|
end
|
86
|
-
|
102
|
+
|
103
|
+
def text_value
|
104
|
+
string_value
|
105
|
+
end
|
87
106
|
|
88
107
|
def datetime_value
|
89
108
|
value_from_record
|
@@ -100,11 +119,24 @@ module ActiveElement
|
|
100
119
|
def boolean_value
|
101
120
|
value_from_record
|
102
121
|
end
|
103
|
-
|
122
|
+
|
123
|
+
def binary_value
|
124
|
+
boolean_value
|
125
|
+
end
|
104
126
|
|
105
127
|
def geometry_value
|
106
128
|
value_from_record
|
107
129
|
end
|
130
|
+
|
131
|
+
def timezone_offset
|
132
|
+
component.controller.request.cookies['timezone_offset'].to_i.minutes
|
133
|
+
end
|
134
|
+
|
135
|
+
def with_timezone_offset(val)
|
136
|
+
return val if val.blank?
|
137
|
+
|
138
|
+
val - timezone_offset
|
139
|
+
end
|
108
140
|
end
|
109
141
|
end
|
110
142
|
end
|
@@ -13,6 +13,10 @@ module ActiveElement
|
|
13
13
|
|
14
14
|
def path
|
15
15
|
record_path || sti_record_path
|
16
|
+
rescue NoMethodError
|
17
|
+
raise Error,
|
18
|
+
"Unable to map #{record.inspect} to a Rails route. Tried:\n" \
|
19
|
+
"#{[default_record_path, sti_record_path].compact.join("\n")}"
|
16
20
|
end
|
17
21
|
|
18
22
|
private
|
@@ -36,9 +40,20 @@ module ActiveElement
|
|
36
40
|
def record_path
|
37
41
|
return nil if record.nil?
|
38
42
|
|
39
|
-
controller.helpers.public_send(default_record_path,
|
43
|
+
controller.helpers.public_send(default_record_path, *path_arguments)
|
40
44
|
rescue NoMethodError
|
41
|
-
|
45
|
+
raise NoMethodError if sti_record_name.nil?
|
46
|
+
|
47
|
+
controller.helpers.public_send(sti_record_path, *path_arguments)
|
48
|
+
end
|
49
|
+
|
50
|
+
def path_arguments
|
51
|
+
case type
|
52
|
+
when :edit, :update, :show, :destroy
|
53
|
+
[record]
|
54
|
+
when :new, :index
|
55
|
+
[]
|
56
|
+
end
|
42
57
|
end
|
43
58
|
|
44
59
|
def default_record_path
|
@@ -46,6 +61,8 @@ module ActiveElement
|
|
46
61
|
end
|
47
62
|
|
48
63
|
def sti_record_path
|
64
|
+
return nil if sti_record_name.nil?
|
65
|
+
|
49
66
|
"#{record_path_prefix}#{namespace_prefix}#{sti_record_name}_path"
|
50
67
|
end
|
51
68
|
|
@@ -63,9 +80,9 @@ module ActiveElement
|
|
63
80
|
|
64
81
|
def record_path_prefix
|
65
82
|
case type
|
66
|
-
when :edit
|
83
|
+
when :edit
|
67
84
|
'edit_'
|
68
|
-
when :new
|
85
|
+
when :new
|
69
86
|
'new_'
|
70
87
|
end
|
71
88
|
end
|
@@ -9,6 +9,7 @@ require_relative 'util/form_value_mapping'
|
|
9
9
|
require_relative 'util/display_value_mapping'
|
10
10
|
require_relative 'util/association_mapping'
|
11
11
|
require_relative 'util/decorator'
|
12
|
+
require_relative 'util/numeric_field'
|
12
13
|
|
13
14
|
module ActiveElement
|
14
15
|
module Components
|
@@ -29,18 +30,25 @@ module ActiveElement
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def self.default_record_name(record)
|
32
|
-
record.class.name.demodulize.underscore
|
33
|
+
(record.is_a?(Class) ? record.name : record.class.name).demodulize.underscore
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.relation_controller(model, controller, relation)
|
37
|
+
namespace = controller.controller_path.rpartition('/').first.presence
|
38
|
+
base = "#{model.reflect_on_association(relation).klass.name.pluralize}Controller"
|
39
|
+
return base.safe_constantize if namespace.nil?
|
40
|
+
|
41
|
+
"#{namespace.classify}::#{base}".safe_constantize || base.safe_constantize
|
33
42
|
end
|
34
43
|
|
35
44
|
def self.json_pretty_print(json)
|
36
|
-
|
37
|
-
formatter = Rouge::Formatters::HTMLLinewise.new(Rouge::Formatters::HTMLInline.new(theme))
|
45
|
+
formatter = Rouge::Formatters::HTML.new
|
38
46
|
lexer = Rouge::Lexers::JSON.new
|
39
47
|
content = JSON.pretty_generate(json.is_a?(String) ? JSON.parse(json) : json)
|
40
|
-
formatted = formatter.format(lexer.lex(content)).gsub(' ', ' ')
|
48
|
+
formatted = formatter.format(lexer.lex(content)).gsub(' ', ' ').gsub("\n", '<br/>')
|
41
49
|
# rubocop:disable Rails/OutputSafety
|
42
50
|
# TODO: Move to a template.
|
43
|
-
"<div style='font-family: monospace;'>#{formatted}</div>".html_safe
|
51
|
+
"<div class='json-highlight' style='font-family: monospace;'>#{formatted}</div>".html_safe
|
44
52
|
# rubocop:enable Rails/OutputSafety
|
45
53
|
end
|
46
54
|
end
|
@@ -2,8 +2,11 @@
|
|
2
2
|
|
3
3
|
require_relative 'components/translations'
|
4
4
|
require_relative 'components/secret_fields'
|
5
|
+
require_relative 'components/email_fields'
|
6
|
+
require_relative 'components/phone_fields'
|
5
7
|
require_relative 'components/util'
|
6
8
|
require_relative 'components/link_helpers'
|
9
|
+
require_relative 'components/navbar'
|
7
10
|
require_relative 'components/page_description'
|
8
11
|
require_relative 'components/form'
|
9
12
|
require_relative 'components/collection_table'
|
@@ -11,7 +11,7 @@ module ActiveElement
|
|
11
11
|
|
12
12
|
def process_action
|
13
13
|
Rails.logger.info("#{ActiveElement.log_tag} #{colorized_permissions_message}")
|
14
|
-
return if verified_permissions?
|
14
|
+
process_pre_render and return if verified_permissions?
|
15
15
|
|
16
16
|
warn "#{ActiveElement.log_tag} #{colorized_permissions_message}" if Rails.env.test?
|
17
17
|
return controller.redirect_to redirect_path if redirect_to_default_landing_page?
|
@@ -30,6 +30,12 @@ module ActiveElement
|
|
30
30
|
(@verified_permissions = permissions_check.permitted?)
|
31
31
|
end
|
32
32
|
|
33
|
+
def process_pre_render
|
34
|
+
PreRenderProcessors::Json.new(controller: controller).process
|
35
|
+
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
33
39
|
def redirect_path
|
34
40
|
routes.alternative_routes.first.path
|
35
41
|
end
|
@@ -72,7 +78,7 @@ module ActiveElement
|
|
72
78
|
color = if permissions_check.permitted?
|
73
79
|
:green
|
74
80
|
else
|
75
|
-
|
81
|
+
rails_component.environment == 'test' ? :yellow : :red
|
76
82
|
end
|
77
83
|
paintbrush { public_send(color, permissions_check.message) }
|
78
84
|
end
|
@@ -5,7 +5,7 @@ module ActiveElement
|
|
5
5
|
# Encapsulates core functionality such as `authenticate_with`, `permit_action`, and `component`
|
6
6
|
# without polluting application controller namespace.
|
7
7
|
class ControllerInterface
|
8
|
-
attr_reader :missing_template_store, :current_user
|
8
|
+
attr_reader :missing_template_store, :current_user, :assigned_editable_fields
|
9
9
|
|
10
10
|
@state = {}
|
11
11
|
|
@@ -16,7 +16,7 @@ module ActiveElement
|
|
16
16
|
def initialize(controller_class, controller_instance = nil)
|
17
17
|
@controller_class = controller_class
|
18
18
|
@controller_instance = controller_instance
|
19
|
-
initialize_state
|
19
|
+
initialize_state(controller_class)
|
20
20
|
@missing_template_store = {}
|
21
21
|
@authorize = false
|
22
22
|
end
|
@@ -25,22 +25,64 @@ module ActiveElement
|
|
25
25
|
@authorize
|
26
26
|
end
|
27
27
|
|
28
|
+
def listable_fields(*args)
|
29
|
+
state.listable_fields.concat(args.map(&:to_sym)).uniq!
|
30
|
+
end
|
31
|
+
|
32
|
+
def viewable_fields(*args)
|
33
|
+
state.viewable_fields.concat(args.map(&:to_sym)).uniq!
|
34
|
+
end
|
35
|
+
|
36
|
+
def editable_fields(*args)
|
37
|
+
state.editable_fields.concat(args.map(&:to_sym)).uniq!
|
38
|
+
end
|
39
|
+
|
40
|
+
def searchable_fields(*args)
|
41
|
+
state.searchable_fields.concat(args.map(&:to_sym)).uniq!
|
42
|
+
end
|
43
|
+
|
44
|
+
def deletable
|
45
|
+
state.deletable = true
|
46
|
+
end
|
47
|
+
|
28
48
|
def application_name
|
29
49
|
RailsComponent.new(::Rails).application_name
|
30
50
|
end
|
31
51
|
|
32
52
|
def authenticate_with(&block)
|
33
|
-
state
|
53
|
+
state.authenticator = block
|
34
54
|
end
|
35
55
|
|
36
56
|
def authorize_with(&block)
|
37
57
|
@authorize = true
|
38
|
-
state
|
58
|
+
state.authorizor = block
|
59
|
+
end
|
60
|
+
|
61
|
+
def sign_out_with(method: :get, &block)
|
62
|
+
state.sign_out_method = method
|
63
|
+
state.sign_out_path = block
|
64
|
+
end
|
65
|
+
|
66
|
+
def sign_out_path
|
67
|
+
state.sign_out_path&.call
|
68
|
+
end
|
69
|
+
|
70
|
+
delegate :sign_out_method, to: :state
|
71
|
+
|
72
|
+
def sign_in_with(method: :get, &block)
|
73
|
+
state.sign_in_method = method
|
74
|
+
state.sign_in_path = block
|
75
|
+
end
|
76
|
+
|
77
|
+
def sign_in_path
|
78
|
+
state.sign_in_path&.call
|
39
79
|
end
|
40
80
|
|
81
|
+
delegate :sign_in_method, to: :state
|
82
|
+
|
41
83
|
def authenticate
|
42
84
|
authenticator&.call
|
43
|
-
@current_user = state
|
85
|
+
@current_user = state.authorizor&.call
|
44
86
|
|
45
87
|
nil
|
46
88
|
end
|
@@ -49,16 +91,12 @@ module ActiveElement
|
|
49
91
|
raise ArgumentError, "Must specify `with: '<permission>'` or `always: true`" unless with.present? || always
|
50
92
|
raise ArgumentError, 'Cannot specify both `with` and `always: true`' if with.present? && always
|
51
93
|
|
52
|
-
state
|
94
|
+
state.permissions << { with: with, always: always, action: action }
|
53
95
|
end
|
54
96
|
|
55
|
-
|
56
|
-
state[:authenticator]
|
57
|
-
end
|
97
|
+
delegate :authenticator, to: :state
|
58
98
|
|
59
|
-
|
60
|
-
state.fetch(:permissions)
|
61
|
-
end
|
99
|
+
delegate :permissions, to: :state
|
62
100
|
|
63
101
|
def component
|
64
102
|
return (@component ||= ActiveElement::Component.new(controller_instance)) unless controller_instance.nil?
|
@@ -66,16 +104,16 @@ module ActiveElement
|
|
66
104
|
raise ArgumentError, 'Attempted to use ActiveElement component from a controller class method.'
|
67
105
|
end
|
68
106
|
|
107
|
+
def state
|
108
|
+
self.class.state[controller_class]
|
109
|
+
end
|
110
|
+
|
69
111
|
private
|
70
112
|
|
71
113
|
attr_reader :controller_class, :controller_instance
|
72
114
|
|
73
|
-
def initialize_state
|
74
|
-
self.class.state[
|
75
|
-
end
|
76
|
-
|
77
|
-
def state
|
78
|
-
self.class.state[controller_class]
|
115
|
+
def initialize_state(key)
|
116
|
+
self.class.state[key] ||= ControllerState.new(controller: controller_instance)
|
79
117
|
end
|
80
118
|
end
|
81
119
|
end
|