active_element 0.0.14 → 0.0.16
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/Gemfile.lock +9 -24
- data/app/assets/javascripts/active_element/form.js +2 -1
- data/app/assets/javascripts/active_element/json_field.js +8 -1
- data/app/views/active_element/components/button.html.erb +6 -4
- data/app/views/active_element/components/form/_check_boxes.html.erb +6 -4
- data/app/views/active_element/components/form/_date_range_field.html.erb +14 -0
- data/app/views/active_element/components/form/_field.html.erb +3 -0
- data/app/views/active_element/components/form/_summary.html.erb +10 -0
- data/app/views/active_element/components/form.html.erb +12 -2
- data/app/views/active_element/components/table/_collection_row.html.erb +3 -3
- data/app/views/active_element/components/table/_field.html.erb +2 -2
- data/app/views/active_element/components/table/collection.html.erb +20 -11
- data/app/views/active_element/components/table/item.html.erb +4 -0
- data/app/views/active_element/default_views/forbidden.html.erb +17 -3
- data/app/views/active_element/default_views/index.html.erb +14 -6
- data/config/locales/en.yml +3 -0
- data/example_app/.ruby-version +1 -1
- data/example_app/Gemfile +0 -2
- data/example_app/Gemfile.lock +5 -4
- data/lib/active_element/components/button.rb +6 -4
- data/lib/active_element/components/collection_table.rb +10 -3
- data/lib/active_element/components/form.rb +27 -2
- data/lib/active_element/components/item_table.rb +5 -3
- data/lib/active_element/components/navbar.rb +1 -1
- data/lib/active_element/components/util/association_mapping.rb +48 -26
- data/lib/active_element/components/util/default_display_value.rb +51 -0
- data/lib/active_element/components/util/display_value_mapping.rb +10 -0
- data/lib/active_element/components/util/field_mapping.rb +2 -2
- data/lib/active_element/components/util/form_field_mapping.rb +68 -15
- data/lib/active_element/components/util/record_mapping.rb +24 -3
- data/lib/active_element/components/util/record_path.rb +52 -20
- data/lib/active_element/components/util.rb +13 -0
- data/lib/active_element/controller_interface.rb +15 -1
- data/lib/active_element/controller_state.rb +5 -2
- data/lib/active_element/default_controller/controller.rb +1 -1
- data/lib/active_element/default_controller/params.rb +9 -1
- data/lib/active_element/default_controller/search.rb +22 -16
- data/lib/active_element/field_options.rb +20 -0
- data/lib/active_element/version.rb +1 -1
- data/lib/active_element.rb +1 -0
- data/rspec-documentation/pages/016-Default Controller.md +10 -1
- metadata +5 -2
@@ -16,6 +16,7 @@ module ActiveElement
|
|
16
16
|
|
17
17
|
def link_tag
|
18
18
|
verify_display_attribute
|
19
|
+
return nil if associated_record.blank?
|
19
20
|
return associated_record.map { |value| link_to(value) } if multiple_association?
|
20
21
|
return link_to(associated_record) if single_association?
|
21
22
|
end
|
@@ -25,7 +26,7 @@ module ActiveElement
|
|
25
26
|
when :has_one
|
26
27
|
associated_record&.public_send(relation_key)
|
27
28
|
when :has_many
|
28
|
-
associated_record&.map(&relation_key)
|
29
|
+
associated_record&.map(&relation_key.to_sym)
|
29
30
|
when :belongs_to
|
30
31
|
record&.public_send(relation_key)
|
31
32
|
end
|
@@ -47,6 +48,42 @@ module ActiveElement
|
|
47
48
|
value.public_send(associated_model.primary_key)
|
48
49
|
end
|
49
50
|
|
51
|
+
def display_field
|
52
|
+
@display_field ||= options.fetch(:attribute) do
|
53
|
+
next associated_model.default_display_attribute if defined_display_attribute?
|
54
|
+
next default_display_attribute if default_display_attribute.present?
|
55
|
+
|
56
|
+
associated_model.primary_key
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def total_count
|
61
|
+
associated_model&.count
|
62
|
+
end
|
63
|
+
|
64
|
+
def options_for_select(scope: nil)
|
65
|
+
return [] if associated_model.blank?
|
66
|
+
|
67
|
+
base = scope.nil? ? associated_model : associated_model.public_send(scope)
|
68
|
+
base.all.pluck(display_field, associated_model.primary_key).sort.map do |title, value|
|
69
|
+
next [title, value] if display_field == associated_model.primary_key
|
70
|
+
|
71
|
+
["#{title} (#{value})", value]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def single_association?
|
76
|
+
%i[has_one belongs_to].include?(relation.macro)
|
77
|
+
end
|
78
|
+
|
79
|
+
def multiple_association?
|
80
|
+
[:has_and_belongs_to_many, :has_many].include?(relation.macro)
|
81
|
+
end
|
82
|
+
|
83
|
+
def associated_model
|
84
|
+
record.association(field).klass
|
85
|
+
end
|
86
|
+
|
50
87
|
private
|
51
88
|
|
52
89
|
attr_reader :controller, :field, :record, :associated_record, :options
|
@@ -56,7 +93,7 @@ module ActiveElement
|
|
56
93
|
end
|
57
94
|
|
58
95
|
def associated_record_path(path_for)
|
59
|
-
return nil
|
96
|
+
return nil if path_helper.nil?
|
60
97
|
|
61
98
|
controller.helpers.public_send(path_helper, path_for)
|
62
99
|
end
|
@@ -69,15 +106,6 @@ module ActiveElement
|
|
69
106
|
"`#{associated_record.class.name}.default_display_attribute`"
|
70
107
|
end
|
71
108
|
|
72
|
-
def display_field
|
73
|
-
@display_field ||= options.fetch(:attribute) do
|
74
|
-
next associated_model.default_display_attribute if defined_display_attribute?
|
75
|
-
next default_display_attribute if default_display_attribute.present?
|
76
|
-
|
77
|
-
associated_model.primary_key
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
109
|
def defined_display_attribute?
|
82
110
|
associated_model.respond_to?(:default_display_attribute)
|
83
111
|
end
|
@@ -91,7 +119,7 @@ module ActiveElement
|
|
91
119
|
end
|
92
120
|
|
93
121
|
def default_display_attribute
|
94
|
-
%i[name
|
122
|
+
%i[email name display_name username].find do |display_field|
|
95
123
|
next true if associated_model_callable_method?(display_field.to_sym)
|
96
124
|
next true if associated_model.columns.map(&:name).map(&:to_sym).include?(display_field.to_sym)
|
97
125
|
|
@@ -99,10 +127,6 @@ module ActiveElement
|
|
99
127
|
end
|
100
128
|
end
|
101
129
|
|
102
|
-
def associated_model
|
103
|
-
record.association(field).klass
|
104
|
-
end
|
105
|
-
|
106
130
|
def associated_model_callable_method?(name)
|
107
131
|
return false unless associated_model.public_instance_methods.include?(name)
|
108
132
|
return false unless associated_model.public_instance_method(name).arity.zero?
|
@@ -110,18 +134,16 @@ module ActiveElement
|
|
110
134
|
true
|
111
135
|
end
|
112
136
|
|
113
|
-
def single_association?
|
114
|
-
%i[has_one belongs_to].include?(relation.macro)
|
115
|
-
end
|
116
|
-
|
117
|
-
def multiple_association?
|
118
|
-
relation.macro == :has_many
|
119
|
-
end
|
120
|
-
|
121
137
|
def path_helper
|
122
|
-
|
138
|
+
names = [associated_record&.model_name&.singular].compact + Util.sti_record_names(associated_record)
|
139
|
+
names.compact.each do |name|
|
140
|
+
base_path = "#{name}_path"
|
141
|
+
namespace_path = "#{namespace}_#{name}_path" if namespace.present?
|
142
|
+
return base_path if namespace.blank? && controller.helpers.respond_to?(base_path)
|
143
|
+
return namespace_path if namespace_path.present? && controller.helpers.respond_to?(namespace_path)
|
144
|
+
end
|
123
145
|
|
124
|
-
|
146
|
+
nil
|
125
147
|
end
|
126
148
|
|
127
149
|
def link_to(value)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveElement
|
4
|
+
module Components
|
5
|
+
module Util
|
6
|
+
# Infers a default display value from any given object using multiple strategies.
|
7
|
+
class DefaultDisplayValue
|
8
|
+
DEFAULT_FIELDS = %i[display_name email name username].freeze
|
9
|
+
|
10
|
+
def initialize(object:)
|
11
|
+
@object = object
|
12
|
+
end
|
13
|
+
|
14
|
+
def value
|
15
|
+
DEFAULT_FIELDS.each do |field|
|
16
|
+
return object.public_send(field) if active_record_value?(field)
|
17
|
+
return object[field] if hash_key(field) if hash_value?(field)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :object
|
24
|
+
|
25
|
+
def associated_model
|
26
|
+
object.model
|
27
|
+
end
|
28
|
+
|
29
|
+
def active_record_value?(field)
|
30
|
+
return false unless object.is_a?(ActiveRecord::Base)
|
31
|
+
return false unless object.respond_to?(field)
|
32
|
+
return false unless object.public_send(field).present?
|
33
|
+
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def hash_value?(field)
|
38
|
+
return false unless object.respond_to?(:[])
|
39
|
+
return false unless object[field].present? || object[field.to_s].present?
|
40
|
+
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def hash_key(field)
|
45
|
+
return field if object[field].present?
|
46
|
+
return field.to_s if object[field.to_s].present?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -11,6 +11,12 @@ module ActiveElement
|
|
11
11
|
association_mapping.link_tag
|
12
12
|
end
|
13
13
|
|
14
|
+
def mapped_value_from_record
|
15
|
+
return value_record_path if value_from_record.is_a?(ActiveRecord::Base)
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
14
20
|
def numeric_value
|
15
21
|
value_from_record
|
16
22
|
end
|
@@ -49,6 +55,10 @@ module ActiveElement
|
|
49
55
|
require 'rgeo/geo_json'
|
50
56
|
Util.json_pretty_print(RGeo::GeoJSON.encode(value_from_record))
|
51
57
|
end
|
58
|
+
|
59
|
+
def value_record_path
|
60
|
+
RecordPath.new(record: value_from_record, controller: component.controller, type: :show).link
|
61
|
+
end
|
52
62
|
end
|
53
63
|
end
|
54
64
|
end
|
@@ -73,8 +73,8 @@ module ActiveElement
|
|
73
73
|
def default_value_mapper(field, options = nil)
|
74
74
|
proc do |item|
|
75
75
|
next default_record_value(field, item, options) if item.class.is_a?(ActiveModel::Naming)
|
76
|
-
next item.public_send(field) if item.respond_to?(field)
|
77
|
-
next
|
76
|
+
next item.public_send(field) if item.class.is_a?(ActiveModel::Naming) && item.respond_to?(field)
|
77
|
+
next default_record_value(field, item, options) if hash_field?(item, field)
|
78
78
|
|
79
79
|
nil
|
80
80
|
end
|
@@ -10,9 +10,9 @@ module ActiveElement
|
|
10
10
|
include EmailFields
|
11
11
|
|
12
12
|
def initialize(record:, fields:, controller:, i18n:, search: false)
|
13
|
-
@record = record
|
14
|
-
@fields = fields
|
15
13
|
@controller = controller
|
14
|
+
@record = record || default_record
|
15
|
+
@fields = fields
|
16
16
|
@i18n = i18n
|
17
17
|
@search = search
|
18
18
|
end
|
@@ -20,7 +20,7 @@ module ActiveElement
|
|
20
20
|
def fields_with_types_and_options
|
21
21
|
compiled_fields = fields.map do |field|
|
22
22
|
next field_with_default_type_and_default_options(field) unless field.is_a?(Array)
|
23
|
-
next field if normalized_field?(field)
|
23
|
+
next field_with_provided_type_and_provided_options(field) if normalized_field?(field)
|
24
24
|
next field_with_default_type_and_provided_options(field) if field_name_with_options?(field)
|
25
25
|
next field_with_type(field) if field_name_with_type?(field)
|
26
26
|
|
@@ -47,18 +47,36 @@ module ActiveElement
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def field_with_default_type_and_default_options(field)
|
50
|
+
return inline_configured_field(field) if inline_configuration?(field)
|
50
51
|
return [field, type_from_file(field).to_sym, options_from_file(field)] if file_configuration?(field)
|
51
|
-
return
|
52
|
+
return relation_field(field) if relation?(field) && record.present?
|
52
53
|
|
53
54
|
[field, default_type_from_model(field), default_options(field)]
|
54
55
|
end
|
55
56
|
|
57
|
+
def inline_configuration?(field)
|
58
|
+
inline_configured_field(field).present?
|
59
|
+
end
|
60
|
+
|
61
|
+
def inline_configured_field(field)
|
62
|
+
field_options = FieldOptions.from_state(field, controller.active_element.state, record)
|
63
|
+
return nil if field_options.blank?
|
64
|
+
|
65
|
+
[field, field_options.type, field_options.options]
|
66
|
+
end
|
67
|
+
|
68
|
+
def field_with_provided_type_and_provided_options(field)
|
69
|
+
return relation_select_field(field.first) if relation?(field.first) && field[1] == :select
|
70
|
+
|
71
|
+
field
|
72
|
+
end
|
73
|
+
|
56
74
|
def field_with_type(field)
|
57
|
-
[field.first, field.last, default_options(field)]
|
75
|
+
[field.first, field.last, default_options(field.first)]
|
58
76
|
end
|
59
77
|
|
60
78
|
def association_mapping(field)
|
61
|
-
|
79
|
+
AssociationMapping.new(
|
62
80
|
controller: controller,
|
63
81
|
field: field,
|
64
82
|
record: record,
|
@@ -73,7 +91,7 @@ module ActiveElement
|
|
73
91
|
#
|
74
92
|
# active_element.component.form fields: [:foo, :bar, [:some_json_field, :text_area]]
|
75
93
|
#
|
76
|
-
file_configuration_path(field).
|
94
|
+
file_configuration_path(field).present? && default_type_from_model(field) != :json_field
|
77
95
|
end
|
78
96
|
|
79
97
|
def type_from_file(field)
|
@@ -86,7 +104,16 @@ module ActiveElement
|
|
86
104
|
end
|
87
105
|
|
88
106
|
def file_configuration_path(field)
|
89
|
-
|
107
|
+
file_configuration_paths(field).compact.find(&:file?)
|
108
|
+
end
|
109
|
+
|
110
|
+
def file_configuration_paths(field)
|
111
|
+
[
|
112
|
+
record_field_configuration_path(field),
|
113
|
+
sti_record_field_configuration_paths(field),
|
114
|
+
controller_field_configuration_path(field),
|
115
|
+
controller_field_configuration_path(field, scope: false)
|
116
|
+
].flatten
|
90
117
|
end
|
91
118
|
|
92
119
|
def record_field_configuration_path(field)
|
@@ -96,11 +123,18 @@ module ActiveElement
|
|
96
123
|
Rails.root.join('config/forms', record_name, "#{field}.yml")
|
97
124
|
end
|
98
125
|
|
99
|
-
def
|
100
|
-
|
101
|
-
return nil if
|
126
|
+
def sti_record_field_configuration_paths(field)
|
127
|
+
sti_record_names = Util.sti_record_names(record)
|
128
|
+
return nil if sti_record_names.blank?
|
129
|
+
|
130
|
+
sti_record_names.map { |name| Rails.root.join('config/forms', name, "#{field}.yml") }
|
131
|
+
end
|
132
|
+
|
133
|
+
def controller_field_configuration_path(field, scope: true)
|
134
|
+
return nil if controller.blank?
|
102
135
|
|
103
|
-
|
136
|
+
controller_segment = (scope ? controller.controller_path : controller.controller_name).singularize
|
137
|
+
Rails.root.join('config/forms', controller_segment, "#{field}.yml")
|
104
138
|
end
|
105
139
|
|
106
140
|
def default_type_from_model(field)
|
@@ -126,6 +160,20 @@ module ActiveElement
|
|
126
160
|
model&.reflect_on_association(field)
|
127
161
|
end
|
128
162
|
|
163
|
+
def relation_field(field)
|
164
|
+
return relation_text_search_field(field) if association_mapping(field).associated_model.count > 1000
|
165
|
+
|
166
|
+
relation_select_field(field)
|
167
|
+
end
|
168
|
+
|
169
|
+
def relation_select_field(field)
|
170
|
+
association = association_mapping(field)
|
171
|
+
columns = [association.display_field, association.associated_model.primary_key].compact
|
172
|
+
[association.relation_key, :select,
|
173
|
+
{ multiple: association_mapping(field).multiple_association?,
|
174
|
+
options: association.associated_model.pluck(*columns) }]
|
175
|
+
end
|
176
|
+
|
129
177
|
def relation_text_search_field(field)
|
130
178
|
[field, :text_search_field,
|
131
179
|
TextSearch.text_search_options(
|
@@ -136,9 +184,10 @@ module ActiveElement
|
|
136
184
|
end
|
137
185
|
|
138
186
|
def searchable_fields(field)
|
187
|
+
fields = Util.relation_controller(model, controller, field)&.active_element&.state&.searchable_fields || []
|
139
188
|
# FIXME: Use database column type to only include strings/numbers.
|
140
|
-
|
141
|
-
|
189
|
+
searchable = fields.reject { |searchable_field| searchable_field.to_s.end_with?('_at') }
|
190
|
+
searchable.presence || [:id, :name].reject { |column| model.columns.map(&:name).include?(column) }
|
142
191
|
end
|
143
192
|
|
144
193
|
def relation_primary_key(field)
|
@@ -189,7 +238,7 @@ module ActiveElement
|
|
189
238
|
return default_search_field_type(field) if search?
|
190
239
|
return :password_field if secret_field?(field)
|
191
240
|
return :email_field if email_field?(field)
|
192
|
-
return :
|
241
|
+
return :telephone_field if phone_field?(field)
|
193
242
|
|
194
243
|
:text_field
|
195
244
|
end
|
@@ -210,6 +259,10 @@ module ActiveElement
|
|
210
259
|
}.merge(field_options(field))
|
211
260
|
end
|
212
261
|
|
262
|
+
def default_record
|
263
|
+
controller&.controller_name&.classify&.safe_constantize&.new
|
264
|
+
end
|
265
|
+
|
213
266
|
def required?(field)
|
214
267
|
return false if record.blank?
|
215
268
|
return false unless record.class.respond_to?(:validators)
|
@@ -17,7 +17,8 @@ module ActiveElement
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def value
|
20
|
-
return
|
20
|
+
return value_from_config if value_from_config.present?
|
21
|
+
return mapped_value_from_record if active_record? || column.present?
|
21
22
|
|
22
23
|
value_from_record
|
23
24
|
end
|
@@ -36,16 +37,28 @@ module ActiveElement
|
|
36
37
|
@column ||= record.class.columns.find { |model_column| model_column.name.to_s == field.to_s }
|
37
38
|
end
|
38
39
|
|
40
|
+
def active_record?
|
41
|
+
association? || value_from_record.is_a?(ActiveRecord::Base)
|
42
|
+
end
|
43
|
+
|
39
44
|
def association?
|
40
45
|
return false unless record.is_a?(ActiveRecord::Base)
|
41
46
|
|
42
|
-
record.association(field).
|
47
|
+
!record.association(field).nil?
|
43
48
|
rescue ActiveRecord::AssociationNotFoundError
|
44
49
|
false
|
45
50
|
end
|
46
51
|
|
47
52
|
def value_from_record
|
48
|
-
|
53
|
+
return nil if field.blank?
|
54
|
+
|
55
|
+
@value_from_record ||= if record.respond_to?(field) && record.is_a?(ActiveRecord::Base)
|
56
|
+
record.public_send(field)
|
57
|
+
elsif record.respond_to?(:key?) && record.respond_to?(:[]) && record.key?(field)
|
58
|
+
record[field]
|
59
|
+
elsif record.respond_to?(field)
|
60
|
+
record.public_send(field)
|
61
|
+
end
|
49
62
|
end
|
50
63
|
|
51
64
|
def mapped_value_from_record
|
@@ -66,6 +79,14 @@ module ActiveElement
|
|
66
79
|
)
|
67
80
|
end
|
68
81
|
|
82
|
+
def value_from_config
|
83
|
+
field_options = FieldOptions.from_state(field, component.controller.active_element.state, record)
|
84
|
+
return nil if field_options.blank?
|
85
|
+
return nil unless DATABASE_TYPES.include?(field_options.type.to_sym)
|
86
|
+
|
87
|
+
send("#{field_options.type}_value")
|
88
|
+
end
|
89
|
+
|
69
90
|
# Override these methods as required in a class that includes this module:
|
70
91
|
|
71
92
|
def mapped_association_from_record
|
@@ -11,19 +11,27 @@ module ActiveElement
|
|
11
11
|
@type = type&.to_sym || controller.action_name&.to_sym
|
12
12
|
end
|
13
13
|
|
14
|
-
def path
|
15
|
-
record_path
|
14
|
+
def path(**kwargs)
|
15
|
+
record_path(**kwargs)
|
16
16
|
rescue NoMethodError
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
ActiveElement.warning("Unable to map #{record.inspect} to a Rails route (#{@controller.class.name}##{@type}). Tried:\n" \
|
18
|
+
"#{all_record_paths.join("\n")}")
|
19
|
+
nil
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
def link(**kwargs)
|
23
|
+
controller.helpers.link_to(DefaultDisplayValue.new(object: record).value, path(**kwargs))
|
24
|
+
end
|
23
25
|
|
24
|
-
|
26
|
+
def model
|
27
|
+
all_names.find do |name|
|
28
|
+
controller.helpers.public_send(record_path_for(name), *path_arguments)
|
29
|
+
rescue NoMethodError
|
30
|
+
nil
|
31
|
+
end&.classify&.constantize || default_model
|
32
|
+
end
|
25
33
|
|
26
|
-
def
|
34
|
+
def namespace
|
27
35
|
# XXX: We guess the namespace from the current controller's module name. This will work
|
28
36
|
# most of the time but will break if the current record's controller exists in a different
|
29
37
|
# namespace to the current controller, e.g. `BackEndAdmin::UsersController` and
|
@@ -31,20 +39,45 @@ module ActiveElement
|
|
31
39
|
# collection of `User` objects, the "show" path will be wrong:
|
32
40
|
# `front_end_admin_user_path`. Maybe descend through the full controller class tree to
|
33
41
|
# find a best match ?
|
34
|
-
|
42
|
+
controller.class.name.deconstantize.underscore.to_sym
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def default_model
|
48
|
+
controller.controller_name.classify&.constantize
|
49
|
+
end
|
50
|
+
|
51
|
+
def all_names
|
52
|
+
@all_names ||= ([record_name] + sti_record_names).compact
|
53
|
+
end
|
54
|
+
|
55
|
+
def all_record_paths
|
56
|
+
@all_record_paths ||= all_names.map { |name| record_path_for(name) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def namespace_prefix
|
35
60
|
return nil if namespace.blank?
|
36
61
|
|
37
62
|
"#{namespace}_"
|
38
63
|
end
|
39
64
|
|
40
|
-
|
65
|
+
attr_reader :record, :controller, :type
|
66
|
+
|
67
|
+
def record_path(**kwargs) # rubocop:disable Metrics/AbcSize
|
41
68
|
return nil if record.nil?
|
42
69
|
|
43
|
-
controller.helpers.public_send(default_record_path, *path_arguments)
|
70
|
+
controller.helpers.public_send(default_record_path, *path_arguments, **kwargs)
|
44
71
|
rescue NoMethodError
|
45
|
-
raise
|
72
|
+
raise if sti_record_names.blank?
|
46
73
|
|
47
|
-
|
74
|
+
sti_record_names.each do |sti_record_name|
|
75
|
+
return controller.helpers.public_send(record_path_for(sti_record_name), *path_arguments, **kwargs)
|
76
|
+
rescue NoMethodError
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
raise
|
48
81
|
end
|
49
82
|
|
50
83
|
def path_arguments
|
@@ -57,13 +90,12 @@ module ActiveElement
|
|
57
90
|
end
|
58
91
|
|
59
92
|
def default_record_path
|
93
|
+
return record.controller_name.constantize.controller_path.underscore.singularize.tr('/', '_') + '_path' if record.try(:controller_name).present?
|
60
94
|
"#{record_path_prefix}#{namespace_prefix}#{record_name}_path"
|
61
95
|
end
|
62
96
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
"#{record_path_prefix}#{namespace_prefix}#{sti_record_name}_path"
|
97
|
+
def record_path_for(name)
|
98
|
+
"#{record_path_prefix}#{namespace_prefix}#{name}_path"
|
67
99
|
end
|
68
100
|
|
69
101
|
def record_name
|
@@ -72,10 +104,10 @@ module ActiveElement
|
|
72
104
|
Util.record_name(record)&.pluralize
|
73
105
|
end
|
74
106
|
|
75
|
-
def
|
76
|
-
return Util.
|
107
|
+
def sti_record_names
|
108
|
+
return Util.sti_record_names(record) unless pluralize?
|
77
109
|
|
78
|
-
Util.
|
110
|
+
Util.sti_record_names(record).map(&:pluralize)
|
79
111
|
end
|
80
112
|
|
81
113
|
def record_path_prefix
|
@@ -8,6 +8,7 @@ require_relative 'util/form_field_mapping'
|
|
8
8
|
require_relative 'util/form_value_mapping'
|
9
9
|
require_relative 'util/display_value_mapping'
|
10
10
|
require_relative 'util/association_mapping'
|
11
|
+
require_relative 'util/default_display_value'
|
11
12
|
require_relative 'util/decorator'
|
12
13
|
require_relative 'util/numeric_field'
|
13
14
|
|
@@ -23,12 +24,24 @@ module ActiveElement
|
|
23
24
|
record&.try(:model_name)&.try(&:singular) || default_record_name(record)
|
24
25
|
end
|
25
26
|
|
27
|
+
# TODO: Remove and use .sti_record_names everywhere instead.
|
26
28
|
def self.sti_record_name(record)
|
27
29
|
return default_record_name(record) unless record.class.respond_to?(:inheritance_column)
|
28
30
|
|
29
31
|
record&.class&.superclass&.model_name&.singular if record&.try(record.class.inheritance_column).present?
|
30
32
|
end
|
31
33
|
|
34
|
+
def self.sti_record_names(record) # rubocop:disable Metrics/CyclomaticComplexity
|
35
|
+
record.class.ancestors.select do |ancestor|
|
36
|
+
next false if ancestor == record.class
|
37
|
+
next false if ancestor.try(:inheritance_column).blank?
|
38
|
+
next false unless ancestor < ActiveRecord::Base
|
39
|
+
next false if ancestor.abstract_class?
|
40
|
+
|
41
|
+
true
|
42
|
+
end.map(&:model_name).map(&:singular)
|
43
|
+
end
|
44
|
+
|
32
45
|
def self.default_record_name(record)
|
33
46
|
(record.is_a?(Class) ? record.name : record.class.name).demodulize.underscore
|
34
47
|
end
|
@@ -38,18 +38,32 @@ module ActiveElement
|
|
38
38
|
state.editable_fields.concat(args.map(&:to_sym)).uniq!
|
39
39
|
end
|
40
40
|
|
41
|
-
def searchable_fields(*args)
|
41
|
+
def searchable_fields(*args, required: false)
|
42
42
|
state.searchable_fields.concat(args.map(&:to_sym)).uniq!
|
43
|
+
state.search_required = required
|
43
44
|
end
|
44
45
|
|
46
|
+
def field_options(field, &block)
|
47
|
+
state.field_options[field] = block
|
48
|
+
end
|
49
|
+
alias field field_options
|
50
|
+
|
45
51
|
def deletable
|
46
52
|
state.deletable = true
|
47
53
|
end
|
48
54
|
|
55
|
+
def model=(model)
|
56
|
+
state.model = model.constantize
|
57
|
+
end
|
58
|
+
|
49
59
|
def application_name
|
50
60
|
RailsComponent.new(::Rails).application_name
|
51
61
|
end
|
52
62
|
|
63
|
+
def t(identifier, **kwargs)
|
64
|
+
::I18n.t("active_element.#{identifier}", **kwargs)
|
65
|
+
end
|
66
|
+
|
53
67
|
def authenticate_with(&block)
|
54
68
|
state.authenticator = block
|
55
69
|
end
|
@@ -5,9 +5,10 @@ module ActiveElement
|
|
5
5
|
# configuration. Used throughout ActiveElement for generating dynamic content based on
|
6
6
|
# controller configuration.
|
7
7
|
class ControllerState
|
8
|
-
attr_reader :permissions, :listable_fields, :viewable_fields, :editable_fields, :searchable_fields
|
8
|
+
attr_reader :permissions, :listable_fields, :viewable_fields, :editable_fields, :searchable_fields,
|
9
|
+
:field_options
|
9
10
|
attr_accessor :sign_in_path, :sign_in, :sign_in_method, :sign_out_path, :sign_out_method,
|
10
|
-
:deletable, :authorizor, :authenticator, :list_order
|
11
|
+
:deletable, :authorizor, :authenticator, :list_order, :search_required, :model
|
11
12
|
|
12
13
|
def initialize(controller:)
|
13
14
|
@controller = controller
|
@@ -19,6 +20,8 @@ module ActiveElement
|
|
19
20
|
@viewable_fields = []
|
20
21
|
@editable_fields = []
|
21
22
|
@searchable_fields = []
|
23
|
+
@field_options = {}
|
24
|
+
@model = nil
|
22
25
|
end
|
23
26
|
|
24
27
|
def deletable?
|
@@ -33,7 +33,15 @@ module ActiveElement
|
|
33
33
|
scalar, json = controller.active_element.state.editable_fields.partition do |field|
|
34
34
|
scalar?(field)
|
35
35
|
end
|
36
|
-
scalar + [json_params(json)]
|
36
|
+
scalar + relation_fields + [json_params(json)]
|
37
|
+
end
|
38
|
+
|
39
|
+
def relation_fields
|
40
|
+
controller.active_element.state.editable_fields.map do |field|
|
41
|
+
next nil unless relation?(field)
|
42
|
+
|
43
|
+
relation(field).try(:foreign_key)
|
44
|
+
end.compact
|
37
45
|
end
|
38
46
|
|
39
47
|
def scalar?(field)
|