forest_admin_datasource_customizer 1.0.0.pre.beta.66 → 1.0.0.pre.beta.67

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed2a621940d1bc0acc61521a6559b2357d2294f5050d58d98b00df9ad7de422f
4
- data.tar.gz: b245c28042d245bfdf4b158b24bc95493005457e8d16794799c79b45f954a4c0
3
+ metadata.gz: dc7be9cd681f1268f79a3196e2df52f823bf27435addf2bafd6149d54c2cccae
4
+ data.tar.gz: 4ec5d39db429e15805cd46c0ae414a1486c24c42df2f2bd89c00b171e01bb7ab
5
5
  SHA512:
6
- metadata.gz: ce6d1c37e588d34f1e5c2d8bb1f705e7dc473af933ecda25c0fcf1789c9738b79fe7209a4d094ee854b66d641d10d92976fd0baa3d03e9508e182fd4624bcca6
7
- data.tar.gz: aec40ab7a12be38ea3c39e9a39f1f0a94af1b2e865e71a822f98b332f34eeeb8fb4e9fd17de9f260da3050f7326ddcc08e8006eec30af321597abd299d6a2bc0
6
+ metadata.gz: d7f31dd4e49979ecd1b488b505eaf7c87ed8a41f2301578a2b4b26458075e64fd491c15b58638663d166da004d2c1f972412c5c4418809e77533b4bdc736de9c
7
+ data.tar.gz: 928ae535652f6eba9f2ccf93686d09e71b0a51d0d8a98e24fd70a702e37f8734f5f0358b2b2c30bfc30d5972135b31ff02b37fadda364d9116f12304986e98f9
@@ -10,7 +10,7 @@ module ForestAdminDatasourceCustomizer
10
10
  end
11
11
 
12
12
  def add_action(name, action)
13
- action.build_fields
13
+ action.build_elements
14
14
  @actions[name] = action
15
15
 
16
16
  mark_schema_as_dirty
@@ -51,6 +51,8 @@ module ForestAdminDatasourceCustomizer
51
51
  fields = drop_deferred(context, metas[:search_values], dynamic_fields)
52
52
 
53
53
  fields.each do |field|
54
+ next if field.type == 'Layout'
55
+
54
56
  if field.value.nil?
55
57
  # customer did not define a handler to rewrite the previous value => reuse current one.
56
58
  field.value = form_values[field.label]
@@ -72,13 +74,20 @@ module ForestAdminDatasourceCustomizer
72
74
  private
73
75
 
74
76
  def drop_defaults(context, fields, data)
75
- unvalued_fields = fields.reject { |field| data.key?(field.label) }
76
- defaults = unvalued_fields.map { |field| evaluate(context, field.default_value) }
77
- unvalued_fields.each_with_index { |field, index| data[field.label] = defaults[index] }
77
+ fields.map do |field|
78
+ if field.type == 'Layout'
79
+ field
80
+ else
81
+ drop_default(context, field, data)
82
+ end
83
+ end
84
+ end
78
85
 
79
- fields.each { |field| field.default_value = nil }
86
+ def drop_default(context, field, data)
87
+ data[field.label] = evaluate(context, field.default_value) unless data.key?(field.label)
88
+ field.default_value = nil
80
89
 
81
- fields
90
+ field
82
91
  end
83
92
 
84
93
  def drop_ifs(context, fields)
@@ -105,7 +114,8 @@ module ForestAdminDatasourceCustomizer
105
114
  value = field.send(key)
106
115
  key = key.to_s.concat('=').to_sym
107
116
 
108
- field.send(key, evaluate(context, value, search_values&.dig(field.label)))
117
+ search_value = field.type == 'Layout' ? nil : search_values&.dig(field.label)
118
+ field.send(key, evaluate(context, value, search_value))
109
119
  end
110
120
 
111
121
  new_fields << Actions::ActionFieldFactory.build(field.to_h)
@@ -11,51 +11,71 @@ module ForestAdminDatasourceCustomizer
11
11
  @execute = execute
12
12
  end
13
13
 
14
- def build_fields
14
+ def build_elements
15
15
  @form = @form&.map do |field|
16
- case field[:widget]
17
- when 'AddressAutocomplete'
18
- WidgetField::AddressAutocompleteField.new(**field)
19
- when 'Checkbox'
20
- WidgetField::CheckboxField.new(**field)
21
- when 'CheckboxGroup'
22
- WidgetField::CheckboxGroupField.new(**field)
23
- when 'ColorPicker'
24
- WidgetField::ColorPickerField.new(**field)
25
- when 'CurrencyInput'
26
- WidgetField::CurrencyInputField.new(**field)
27
- when 'DatePicker'
28
- WidgetField::DatePickerField.new(**field)
29
- when 'Dropdown'
30
- WidgetField::DropdownField.new(**field)
31
- when 'FilePicker'
32
- WidgetField::FilePickerField.new(**field)
33
- when 'JsonEditor'
34
- WidgetField::JsonEditorField.new(**field)
35
- when 'NumberInput'
36
- WidgetField::NumberInputField.new(**field)
37
- when 'NumberInputList'
38
- WidgetField::NumberInputListField.new(**field)
39
- when 'RadioGroup'
40
- WidgetField::RadioGroupField.new(**field)
41
- when 'RichText'
42
- WidgetField::RichTextField.new(**field)
43
- when 'TextArea'
44
- WidgetField::TextAreaField.new(**field)
45
- when 'TextInput'
46
- WidgetField::TextInputField.new(**field)
47
- when 'TextInputList'
48
- WidgetField::TextInputListField.new(**field)
49
- when 'TimePicker'
50
- WidgetField::TimePickerField.new(**field)
51
- when 'UserDropdown'
52
- WidgetField::UserDropdownField.new(**field)
16
+ if field.key? :widget
17
+ build_widget(field)
18
+ elsif field[:type] == 'Layout'
19
+ build_layout_element(field)
53
20
  else
54
21
  DynamicField.new(**field)
55
22
  end
56
23
  end
57
24
  end
58
25
 
26
+ def build_widget(field)
27
+ case field[:widget]
28
+ when 'AddressAutocomplete'
29
+ WidgetField::AddressAutocompleteField.new(**field)
30
+ when 'Checkbox'
31
+ WidgetField::CheckboxField.new(**field)
32
+ when 'CheckboxGroup'
33
+ WidgetField::CheckboxGroupField.new(**field)
34
+ when 'ColorPicker'
35
+ WidgetField::ColorPickerField.new(**field)
36
+ when 'CurrencyInput'
37
+ WidgetField::CurrencyInputField.new(**field)
38
+ when 'DatePicker'
39
+ WidgetField::DatePickerField.new(**field)
40
+ when 'Dropdown'
41
+ WidgetField::DropdownField.new(**field)
42
+ when 'FilePicker'
43
+ WidgetField::FilePickerField.new(**field)
44
+ when 'JsonEditor'
45
+ WidgetField::JsonEditorField.new(**field)
46
+ when 'NumberInput'
47
+ WidgetField::NumberInputField.new(**field)
48
+ when 'NumberInputList'
49
+ WidgetField::NumberInputListField.new(**field)
50
+ when 'RadioGroup'
51
+ WidgetField::RadioGroupField.new(**field)
52
+ when 'RichText'
53
+ WidgetField::RichTextField.new(**field)
54
+ when 'TextArea'
55
+ WidgetField::TextAreaField.new(**field)
56
+ when 'TextInput'
57
+ WidgetField::TextInputField.new(**field)
58
+ when 'TextInputList'
59
+ WidgetField::TextInputListField.new(**field)
60
+ when 'TimePicker'
61
+ WidgetField::TimePickerField.new(**field)
62
+ when 'UserDropdown'
63
+ WidgetField::UserDropdownField.new(**field)
64
+ else
65
+ raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Unknow widget type: #{field[:widget]}"
66
+ end
67
+ end
68
+
69
+ def build_layout_element(field)
70
+ case field[:component]
71
+ when 'Separator'
72
+ FormLayoutElement::SeparatorElement.new(**field)
73
+ else
74
+ raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
75
+ "Unknow component type: #{field[:component]}"
76
+ end
77
+ end
78
+
59
79
  def static_form?
60
80
  return form&.all?(&:static?) if form
61
81
 
@@ -0,0 +1,29 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Action
4
+ class BaseFormElement
5
+ attr_accessor :type
6
+
7
+ def initialize(
8
+ type:,
9
+ **_kwargs
10
+ )
11
+ @type = type
12
+ end
13
+
14
+ def static?
15
+ instance_variables.all? { |attribute| !instance_variable_get(attribute).respond_to?(:call) }
16
+ end
17
+
18
+ def to_h
19
+ result = {}
20
+ instance_variables.each do |attribute|
21
+ result[attribute.to_s.delete('@').to_sym] = instance_variable_get(attribute)
22
+ end
23
+
24
+ result
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,7 +1,7 @@
1
1
  module ForestAdminDatasourceCustomizer
2
2
  module Decorators
3
3
  module Action
4
- class DynamicField
4
+ class DynamicField < BaseFormElement
5
5
  attr_accessor :type, :label, :description, :is_required, :is_read_only, :if_condition, :value, :default_value,
6
6
  :collection_name, :enum_values, :placeholder
7
7
 
@@ -19,7 +19,8 @@ module ForestAdminDatasourceCustomizer
19
19
  placeholder: nil,
20
20
  **_kwargs
21
21
  )
22
- @type = type
22
+ super(type: type)
23
+
23
24
  @label = label
24
25
  @description = description
25
26
  @is_required = is_required
@@ -31,19 +32,6 @@ module ForestAdminDatasourceCustomizer
31
32
  @enum_values = enum_values
32
33
  @placeholder = placeholder
33
34
  end
34
-
35
- def static?
36
- instance_variables.all? { |attribute| !instance_variable_get(attribute).respond_to?(:call) }
37
- end
38
-
39
- def to_h
40
- result = {}
41
- instance_variables.each do |attribute|
42
- result[attribute.to_s.delete('@').to_sym] = instance_variable_get(attribute)
43
- end
44
-
45
- result
46
- end
47
35
  end
48
36
  end
49
37
  end
@@ -0,0 +1,26 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Action
4
+ module FormLayoutElement
5
+ include Types
6
+
7
+ class LayoutElement < BaseFormElement
8
+ attr_accessor :if_condition, :component
9
+
10
+ def initialize(component:, if_condition: nil, **kwargs)
11
+ super(type: 'Layout', **kwargs)
12
+
13
+ @component = component
14
+ @if_condition = if_condition
15
+ end
16
+ end
17
+
18
+ class SeparatorElement < LayoutElement
19
+ def initialize(options)
20
+ super(component: 'Separator', **options)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,343 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Action
4
+ module LayoutElement
5
+ include Types
6
+
7
+ class TimePickerField < DynamicField
8
+ attr_accessor :widget
9
+
10
+ def initialize(options)
11
+ super(**options)
12
+ WidgetField.validate_arg(options, :type, { type: 'contains', value: ['Time'] })
13
+ @widget = 'TimePicker'
14
+ end
15
+ end
16
+
17
+ class AddressAutocompleteField < DynamicField
18
+ attr_accessor :widget
19
+
20
+ def initialize(options)
21
+ super(**options)
22
+ WidgetField.validate_arg(options, :type, { type: 'contains', value: ['String'] })
23
+
24
+ @widget = 'AddressAutocomplete'
25
+ end
26
+ end
27
+
28
+ class CheckboxField < DynamicField
29
+ attr_accessor :widget
30
+
31
+ def initialize(options)
32
+ super(**options)
33
+ WidgetField.validate_arg(
34
+ options,
35
+ :type,
36
+ { type: 'contains', value: [Types::FieldType::BOOLEAN] }
37
+ )
38
+
39
+ @widget = 'Checkbox'
40
+ end
41
+ end
42
+
43
+ class CheckboxGroupField < DynamicField
44
+ attr_accessor :widget, :options
45
+
46
+ def initialize(options)
47
+ super(**options)
48
+
49
+ WidgetField.validate_arg(options, :options, { type: 'present' })
50
+ WidgetField.validate_arg(
51
+ options,
52
+ :type,
53
+ { type: 'contains', value: [Types::FieldType::STRING_LIST, Types::FieldType::NUMBER_LIST] }
54
+ )
55
+
56
+ @widget = 'CheckboxGroup'
57
+ @options = options[:options]
58
+ end
59
+ end
60
+
61
+ class ColorPickerField < DynamicField
62
+ attr_accessor :widget, :enable_opacity, :quick_palette
63
+
64
+ def initialize(options)
65
+ super(**options)
66
+
67
+ WidgetField.validate_arg(options, :enable_opacity, { type: 'contains', value: [Types::FieldType::STRING] })
68
+
69
+ @widget = 'ColorPicker'
70
+ @enable_opacity = options[:enable_opacity] || nil
71
+ @quick_palette = options[:quick_palette] || nil
72
+ end
73
+ end
74
+
75
+ class CurrencyInputField < DynamicField
76
+ attr_accessor :widget, :currency, :base, :min, :max, :step
77
+
78
+ def initialize(options)
79
+ super(**options)
80
+
81
+ WidgetField.validate_arg(options, :type, { type: 'contains', value: [Types::FieldType::NUMBER] })
82
+ WidgetField.validate_arg(options, :currency, { type: 'present' })
83
+
84
+ @widget = 'CurrencyInput'
85
+ @currency = options[:currency]
86
+ @base = options[:base] || 'Unit'
87
+ @min = options[:min] || nil
88
+ @max = options[:max] || nil
89
+ @step = options[:step] || nil
90
+ end
91
+ end
92
+
93
+ class DatePickerField < DynamicField
94
+ attr_accessor :widget, :min, :max, :format, :step
95
+
96
+ def initialize(options)
97
+ super(**options)
98
+
99
+ WidgetField.validate_arg(
100
+ options,
101
+ 'type',
102
+ { type: 'contains',
103
+ value: [Types::FieldType::DATE, Types::FieldType::DATE_ONLY, Types::FieldType::STRING] }
104
+ )
105
+
106
+ @widget = 'DatePicker'
107
+ @format = options[:format] || nil
108
+ @min = options[:min] || nil
109
+ @max = options[:max] || nil
110
+ @step = options[:step] || nil
111
+ end
112
+ end
113
+
114
+ class DropdownField < DynamicField
115
+ attr_accessor :widget, :options, :search
116
+
117
+ def initialize(options)
118
+ super(**options)
119
+ WidgetField.validate_arg(options, :options, { type: 'present' })
120
+ WidgetField.validate_arg(
121
+ options,
122
+ 'type',
123
+ {
124
+ type: 'contains',
125
+ value: [Types::FieldType::DATE, Types::FieldType::DATE_ONLY, Types::FieldType::STRING,
126
+ Types::FieldType::STRING_LIST]
127
+ }
128
+ )
129
+
130
+ @widget = 'Dropdown'
131
+ @options = options[:options]
132
+ @search = options[:search] || nil
133
+ end
134
+ end
135
+
136
+ class FilePickerField < DynamicField
137
+ attr_accessor :widget, :extensions, :max_count, :max_size_mb
138
+
139
+ def initialize(options)
140
+ super(**options)
141
+ WidgetField.validate_arg(options, :options, { type: 'present' })
142
+ WidgetField.validate_arg(
143
+ options,
144
+ 'type',
145
+ {
146
+ type: 'contains',
147
+ value: [Types::FieldType::FILE, Types::FieldType::FILE_LIST]
148
+ }
149
+ )
150
+
151
+ @widget = 'FilePicker'
152
+ @extensions = options[:extensions] || nil
153
+ @max_size_mb = options[:max_size_mb] || nil
154
+ @max_count = options[:max_count] || nil
155
+ end
156
+ end
157
+
158
+ class NumberInputField < DynamicField
159
+ attr_accessor :widget, :step, :min, :max
160
+
161
+ def initialize(options)
162
+ super(**options)
163
+ WidgetField.validate_arg(options, :options, { type: 'present' })
164
+ WidgetField.validate_arg(
165
+ options,
166
+ 'type',
167
+ {
168
+ type: 'contains',
169
+ value: [Types::FieldType::NUMBER]
170
+ }
171
+ )
172
+
173
+ @widget = 'NumberInput'
174
+ @step = options[:step] || nil
175
+ @min = options[:min] || nil
176
+ @max = options[:max] || nil
177
+ end
178
+ end
179
+
180
+ class JsonEditorField < DynamicField
181
+ attr_accessor :widget
182
+
183
+ def initialize(options)
184
+ super(**options)
185
+ WidgetField.validate_arg(
186
+ options,
187
+ 'type',
188
+ {
189
+ type: 'contains',
190
+ value: [Types::FieldType::DATE, Types::FieldType::DATE_ONLY, Types::FieldType::STRING,
191
+ Types::FieldType::STRING_LIST]
192
+ }
193
+ )
194
+
195
+ @widget = 'JsonEditor'
196
+ end
197
+ end
198
+
199
+ class NumberInputListField < DynamicField
200
+ attr_accessor :widget, :allow_duplicates, :allow_empty_values, :enable_reorder, :min, :max, :step
201
+
202
+ def initialize(options)
203
+ super(**options)
204
+ WidgetField.validate_arg(options, :options, { type: 'present' })
205
+ WidgetField.validate_arg(
206
+ options,
207
+ 'type',
208
+ {
209
+ type: 'contains',
210
+ value: [Types::FieldType::NUMBER_LIST]
211
+ }
212
+ )
213
+
214
+ @widget = 'NumberInputList'
215
+ @allow_duplicates = options[:allow_duplicates] || nil
216
+ @allow_empty_values = options[:allow_empty_values] || nil
217
+ @enable_reorder = options[:enable_reorder] || nil
218
+ @min = options[:min] || nil
219
+ @max = options[:max] || nil
220
+ @step = options[:step] || nil
221
+ end
222
+ end
223
+
224
+ class RadioGroupField < DynamicField
225
+ attr_accessor :widget, :options
226
+
227
+ def initialize(options)
228
+ super(**options)
229
+ WidgetField.validate_arg(options, :options, { type: 'present' })
230
+ WidgetField.validate_arg(
231
+ options,
232
+ 'type',
233
+ {
234
+ type: 'contains',
235
+ value: [Types::FieldType::DATE, Types::FieldType::DATEONLY, Types::FieldType::NUMBER,
236
+ Types::FieldType::STRING]
237
+ }
238
+ )
239
+
240
+ @widget = 'RadioGroup'
241
+ @options = options[:options]
242
+ end
243
+ end
244
+
245
+ class RichTextField < DynamicField
246
+ attr_accessor :widget
247
+
248
+ def initialize(options)
249
+ super(**options)
250
+ WidgetField.validate_arg(options, :options, { type: 'present' })
251
+ WidgetField.validate_arg(
252
+ options,
253
+ 'type',
254
+ {
255
+ type: 'contains',
256
+ value: [Types::FieldType::STRING]
257
+ }
258
+ )
259
+
260
+ @widget = 'RichText'
261
+ end
262
+ end
263
+
264
+ class TextAreaField < DynamicField
265
+ attr_accessor :widget, :rows
266
+
267
+ def initialize(options)
268
+ super(**options)
269
+ WidgetField.validate_arg(
270
+ options,
271
+ 'type',
272
+ {
273
+ type: 'contains',
274
+ value: [Types::FieldType::STRING]
275
+ }
276
+ )
277
+
278
+ @widget = 'TextArea'
279
+ @rows = options[:rows] || nil
280
+ end
281
+ end
282
+
283
+ class TextInputField < DynamicField
284
+ attr_accessor :widget
285
+
286
+ def initialize(options)
287
+ super(**options)
288
+ WidgetField.validate_arg(
289
+ options,
290
+ 'type',
291
+ {
292
+ type: 'contains',
293
+ value: [Types::FieldType::STRING]
294
+ }
295
+ )
296
+
297
+ @widget = 'TextInput'
298
+ end
299
+ end
300
+
301
+ class TextInputListField < DynamicField
302
+ attr_accessor :widget, :allow_duplicates, :allow_empty_values, :enable_reorder
303
+
304
+ def initialize(options)
305
+ super(**options)
306
+ WidgetField.validate_arg(
307
+ options,
308
+ 'type',
309
+ {
310
+ type: 'contains',
311
+ value: [Types::FieldType::STRING_LIST]
312
+ }
313
+ )
314
+
315
+ @widget = 'TextInput'
316
+ @allow_duplicates = options[:allow_duplicates] || nil
317
+ @allow_empty_values = options[:allow_empty_values] || nil
318
+ @enable_reorder = options[:enable_reorder] || nil
319
+ end
320
+ end
321
+
322
+ class UserDropdownField < DynamicField
323
+ attr_accessor :widget
324
+
325
+ def initialize(options)
326
+ super(**options)
327
+ WidgetField.validate_arg(options, :options, { type: 'present' })
328
+ WidgetField.validate_arg(
329
+ options,
330
+ 'type',
331
+ {
332
+ type: 'contains',
333
+ value: [Types::FieldType::STRING, Types::FieldType::STRING_LIST]
334
+ }
335
+ )
336
+
337
+ @widget = 'UserDropdown'
338
+ end
339
+ end
340
+ end
341
+ end
342
+ end
343
+ end
@@ -246,7 +246,7 @@ module ForestAdminDatasourceCustomizer
246
246
  'type',
247
247
  {
248
248
  type: 'contains',
249
- value: [Types::FieldType::DATE, Types::FieldType::DATEONLY, Types::FieldType::NUMBER,
249
+ value: [Types::FieldType::DATE, Types::FieldType::DATE_ONLY, Types::FieldType::NUMBER,
250
250
  Types::FieldType::STRING]
251
251
  }
252
252
  )
@@ -326,7 +326,7 @@ module ForestAdminDatasourceCustomizer
326
326
  }
327
327
  )
328
328
 
329
- @widget = 'TextInput'
329
+ @widget = 'TextInputList'
330
330
  @allow_duplicates = options[:allow_duplicates] || nil
331
331
  @allow_empty_values = options[:allow_empty_values] || nil
332
332
  @enable_reorder = options[:enable_reorder] || nil
@@ -1,3 +1,3 @@
1
1
  module ForestAdminDatasourceCustomizer
2
- VERSION = "1.0.0-beta.66"
2
+ VERSION = "1.0.0-beta.67"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_datasource_customizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.beta.66
4
+ version: 1.0.0.pre.beta.67
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2024-09-05 00:00:00.000000000 Z
12
+ date: 2024-09-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -81,9 +81,12 @@ files:
81
81
  - lib/forest_admin_datasource_customizer/datasource_customizer.rb
82
82
  - lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb
83
83
  - lib/forest_admin_datasource_customizer/decorators/action/base_action.rb
84
+ - lib/forest_admin_datasource_customizer/decorators/action/base_form_element.rb
84
85
  - lib/forest_admin_datasource_customizer/decorators/action/context/action_context.rb
85
86
  - lib/forest_admin_datasource_customizer/decorators/action/context/action_context_single.rb
86
87
  - lib/forest_admin_datasource_customizer/decorators/action/dynamic_field.rb
88
+ - lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb
89
+ - lib/forest_admin_datasource_customizer/decorators/action/layout_element.rb
87
90
  - lib/forest_admin_datasource_customizer/decorators/action/result_builder.rb
88
91
  - lib/forest_admin_datasource_customizer/decorators/action/types/action_scope.rb
89
92
  - lib/forest_admin_datasource_customizer/decorators/action/types/field_type.rb