super 0.0.14 → 0.18.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/assets/javascripts/super/application.js +148 -15
  4. data/app/assets/stylesheets/super/application.css +15 -22
  5. data/app/controllers/super/application_controller.rb +44 -42
  6. data/app/controllers/super/substructure_controller.rb +313 -0
  7. data/app/helpers/super/form_builder_helper.rb +7 -0
  8. data/app/views/layouts/super/application.html.erb +3 -21
  9. data/app/views/super/application/_collection_header.html.erb +2 -2
  10. data/app/views/super/application/_display_actions.html.erb +1 -1
  11. data/app/views/super/application/_display_index.html.erb +2 -2
  12. data/app/views/super/application/_display_show.html.erb +1 -1
  13. data/app/views/super/application/_filter.html.erb +62 -2
  14. data/app/views/super/application/_form_field.html.erb +5 -0
  15. data/app/views/super/application/_member_header.html.erb +2 -2
  16. data/app/views/super/application/_pagination.html.erb +1 -1
  17. data/app/views/super/application/_site_footer.html.erb +3 -0
  18. data/app/views/super/application/_site_header.html.erb +17 -0
  19. data/app/views/super/application/_sort_expression.html.erb +2 -2
  20. data/app/views/super/application/index.csv.erb +14 -0
  21. data/app/views/super/feather/README.md +0 -1
  22. data/frontend/super-frontend/dist/application.css +15 -22
  23. data/frontend/super-frontend/dist/application.js +148 -15
  24. data/lib/generators/super/install/install_generator.rb +0 -16
  25. data/lib/generators/super/install/templates/base_controller.rb.tt +0 -8
  26. data/lib/generators/super/resource/templates/resources_controller.rb.tt +4 -4
  27. data/lib/super/action_inquirer.rb +18 -3
  28. data/lib/super/badge.rb +60 -0
  29. data/lib/super/cheat.rb +6 -6
  30. data/lib/super/display/guesser.rb +1 -1
  31. data/lib/super/display/schema_types.rb +49 -1
  32. data/lib/super/display.rb +2 -1
  33. data/lib/super/error.rb +3 -0
  34. data/lib/super/filter/form_object.rb +74 -48
  35. data/lib/super/filter/guesser.rb +2 -0
  36. data/lib/super/filter/operator.rb +90 -64
  37. data/lib/super/filter/schema_types.rb +63 -80
  38. data/lib/super/filter.rb +1 -1
  39. data/lib/super/form/builder.rb +30 -39
  40. data/lib/super/form/field_transcript.rb +43 -0
  41. data/lib/super/form/guesser.rb +4 -4
  42. data/lib/super/form/schema_types.rb +66 -22
  43. data/lib/super/link.rb +2 -2
  44. data/lib/super/pagination.rb +2 -44
  45. data/lib/super/reset.rb +25 -0
  46. data/lib/super/schema.rb +4 -0
  47. data/lib/super/useful/builder.rb +4 -4
  48. data/lib/super/version.rb +1 -1
  49. data/lib/super.rb +3 -1
  50. data/lib/tasks/super/cheat.rake +1 -1
  51. metadata +25 -19
  52. data/app/views/super/application/_filter_type_select.html.erb +0 -21
  53. data/app/views/super/application/_filter_type_text.html.erb +0 -18
  54. data/app/views/super/application/_filter_type_timestamp.html.erb +0 -24
  55. data/app/views/super/application/_form_field_checkbox.html.erb +0 -1
  56. data/app/views/super/application/_form_field_flatpickr_date.html.erb +0 -8
  57. data/app/views/super/application/_form_field_flatpickr_datetime.html.erb +0 -8
  58. data/app/views/super/application/_form_field_flatpickr_time.html.erb +0 -8
  59. data/app/views/super/application/_form_field_rich_text_area.html.erb +0 -1
  60. data/app/views/super/application/_form_field_select.html.erb +0 -1
  61. data/app/views/super/application/_form_field_text.html.erb +0 -1
  62. data/app/views/super/feather/_chevron_down.html +0 -1
  63. data/docs/cheat.md +0 -41
  64. data/lib/super/controls/optional.rb +0 -113
  65. data/lib/super/controls/steps.rb +0 -106
  66. data/lib/super/controls/view.rb +0 -55
  67. data/lib/super/controls.rb +0 -22
@@ -2,112 +2,95 @@
2
2
 
3
3
  module Super
4
4
  class Filter
5
- # This schema type is used to configure the filtering form on your +#index+
6
- # action.
7
- #
8
- # The +operators:+ keyword argument can be left out in each case. There is
9
- # a default set of operators that are provided.
10
- #
11
5
  # Note: The constants under "Defined Under Namespace" are considered
12
6
  # private.
13
- #
14
- # class MemberDashboard
15
- # # ...
16
- #
17
- # def filter_schema
18
- # Super::Filter.new do |fields, type|
19
- # fields[:name] = type.text(operators: [
20
- # Super::Filter::Operator.eq,
21
- # Super::Filter::Operator.contain,
22
- # Super::Filter::Operator.ncontain,
23
- # Super::Filter::Operator.start,
24
- # Super::Filter::Operator.end,
25
- # ])
26
- # fields[:rank] = type.select(collection: Member.ranks.values)
27
- # fields[:position] = type.text(operators: [
28
- # Super::Filter::Operator.eq,
29
- # Super::Filter::Operator.neq,
30
- # Super::Filter::Operator.contain,
31
- # Super::Filter::Operator.ncontain,
32
- # ])
33
- # fields[:ship_id] = type.select(
34
- # collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
35
- # )
36
- # fields[:created_at] = type.timestamp
37
- # fields[:updated_at] = type.timestamp
38
- # end
39
- # end
40
- #
41
- # # ...
42
- # end
43
7
  class SchemaTypes
44
- class Text
45
- def initialize(partial_path:, operators:)
46
- @partial_path = partial_path
47
- @operators = operators
48
- end
8
+ class OperatorList
9
+ include Enumerable
49
10
 
50
- attr_reader :operators
11
+ def initialize(*new_operators)
12
+ @operators = {}
13
+ @operator_transcript = {}
14
+ @fallback_transcript = nil
51
15
 
52
- def to_partial_path
53
- @partial_path
16
+ push(*new_operators)
54
17
  end
55
18
 
56
- def q
57
- [:q]
58
- end
59
- end
19
+ def push(*new_operators)
20
+ new_operators.flatten.map(&:dup).each do |new_operator|
21
+ new_identifier = new_operator.identifier.to_s
22
+
23
+ raise Error::AlreadyRegistered if @operators.key?(new_identifier)
60
24
 
61
- class Select
62
- def initialize(collection:, operators:)
63
- @collection = collection
64
- @operators = operators
25
+ @operators[new_identifier] = new_operator
26
+ end
27
+
28
+ nil
65
29
  end
66
30
 
67
- attr_reader :collection
68
- attr_reader :operators
31
+ alias add push
32
+
33
+ def each
34
+ return enum_for(:each) if !block_given?
69
35
 
70
- def to_partial_path
71
- "filter_type_select"
36
+ @operators.each do |identifier, operator|
37
+ yield(
38
+ OperatorWithFieldTranscript.new(
39
+ operator,
40
+ @operator_transcript[identifier] || @fallback_transcript
41
+ )
42
+ )
43
+ end
72
44
  end
73
45
 
74
- def q
75
- [:q]
46
+ def transcribe(operator_identifier = nil)
47
+ transcript = Form::FieldTranscript.new
48
+ yield transcript
49
+
50
+ if operator_identifier.nil?
51
+ @fallback_transcript = transcript
52
+ else
53
+ @operator_transcript[operator_identifier.to_s] = transcript
54
+ end
55
+
56
+ self
76
57
  end
77
58
  end
78
59
 
79
- class Timestamp
80
- def initialize(operators:)
81
- @operators = operators
60
+ class OperatorWithFieldTranscript
61
+ def initialize(operator, field_transcript)
62
+ @operator = operator
63
+ @field_transcript = field_transcript
82
64
  end
83
65
 
84
- attr_reader :operators
85
-
86
- def to_partial_path
87
- "filter_type_timestamp"
66
+ Super::Filter::Operator.instance_methods(false).each do |name|
67
+ delegate name, to: :@operator
88
68
  end
89
69
 
90
- def q
91
- [:q0, :q1]
92
- end
70
+ attr_reader :field_transcript
71
+ end
72
+
73
+ def use(*identifiers)
74
+ found_operators = identifiers.flatten.map { |id| Operator[id] }
75
+ OperatorList.new(*found_operators)
76
+ end
77
+
78
+ def select(collection)
79
+ use("eq", "null", "nnull")
80
+ .transcribe { |f| f.super.select(collection) }
93
81
  end
94
82
 
95
- def select(collection:, operators: Filter::Operator.select_defaults)
96
- Select.new(
97
- collection: collection,
98
- operators: operators
99
- )
83
+ def text
84
+ use("contain", "ncontain", "blank", "nblank")
100
85
  end
101
86
 
102
- def text(operators: Filter::Operator.text_defaults)
103
- Text.new(
104
- partial_path: "filter_type_text",
105
- operators: operators
106
- )
87
+ def timestamp
88
+ use("between", "null", "nnull")
89
+ .transcribe { |f| f.super.datetime_flatpickr }
107
90
  end
108
91
 
109
- def timestamp(operators: Filter::Operator.range_defaults)
110
- Timestamp.new(operators: operators)
92
+ def boolean
93
+ use("true", "false", "null", "nnull")
111
94
  end
112
95
  end
113
96
  end
data/lib/super/filter.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Super
4
4
  class Filter
5
5
  def initialize
6
- @schema_type = Filter::SchemaTypes.new
6
+ @schema_type = SchemaTypes.new
7
7
  @fields = Schema::Fields.new
8
8
 
9
9
  yield(@fields, @schema_type)
@@ -58,7 +58,7 @@ module Super
58
58
  @builder.check_box(attribute, options, checked_value, unchecked_value)
59
59
  end
60
60
 
61
- def flatpickr_date(attribute, options = {})
61
+ def date_flatpickr(attribute, options = {})
62
62
  options, defaults = split_defaults(
63
63
  options,
64
64
  class: "super-input w-full",
@@ -71,11 +71,13 @@ module Super
71
71
  )
72
72
  options[:class] = join_classes(defaults[:class], options[:class])
73
73
  options[:data] = defaults[:data].deep_merge(options[:data] || {})
74
+ options[:value] = @builder.object.public_send(attribute).presence
75
+ options[:value] = options[:value].iso8601 if options[:value].respond_to?(:iso8601)
74
76
 
75
77
  @builder.text_field(attribute, options)
76
78
  end
77
79
 
78
- def flatpickr_datetime(attribute, options = {})
80
+ def datetime_flatpickr(attribute, options = {})
79
81
  options, defaults = split_defaults(
80
82
  options,
81
83
  class: "super-input w-full",
@@ -90,11 +92,13 @@ module Super
90
92
  )
91
93
  options[:class] = join_classes(defaults[:class], options[:class])
92
94
  options[:data] = defaults[:data].deep_merge(options[:data] || {})
95
+ options[:value] = @builder.object.public_send(attribute).presence
96
+ options[:value] = options[:value].iso8601 if options[:value].respond_to?(:iso8601)
93
97
 
94
98
  @builder.text_field(attribute, options)
95
99
  end
96
100
 
97
- def flatpickr_time(attribute, options = {})
101
+ def time_flatpickr(attribute, options = {})
98
102
  options, defaults = split_defaults(
99
103
  options,
100
104
  class: "super-input w-full",
@@ -110,6 +114,8 @@ module Super
110
114
  )
111
115
  options[:class] = join_classes(defaults[:class], options[:class])
112
116
  options[:data] = defaults[:data].deep_merge(options[:data] || {})
117
+ options[:value] = @builder.object.public_send(attribute).presence
118
+ options[:value] = options[:value].strftime("%H:%M:%S") if options[:value].respond_to?(:strftime)
113
119
 
114
120
  @builder.text_field(attribute, options)
115
121
  end
@@ -131,25 +137,10 @@ module Super
131
137
  def select(attribute, choices, options = {}, html_options = {}, &block)
132
138
  options, defaults = split_defaults(options, include_blank: true)
133
139
  options = defaults.merge(options)
134
- html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select-field")
140
+ html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select")
135
141
  html_options[:class] = join_classes(html_defaults[:class], html_options[:class])
136
142
 
137
- parts = [
138
- %(<div class="super-input-select">).html_safe,
139
- @builder.select(attribute, choices, options, html_options, &block),
140
- <<~HTML.html_safe,
141
- <div class="super-input-select-icon text-blue-700">
142
- <span class="h-4 w-4">
143
- HTML
144
- @template.render("super/feather/chevron_down"),
145
- <<~HTML.html_safe,
146
- </span>
147
- </div>
148
- HTML
149
- %(</div>).html_safe,
150
- ]
151
-
152
- @template.safe_join(parts)
143
+ @builder.select(attribute, choices, options, html_options, &block)
153
144
  end
154
145
 
155
146
  def submit(value = nil, options = {})
@@ -171,60 +162,60 @@ module Super
171
162
  @template.content_tag(:div, class: "super-field-group", &block)
172
163
  end
173
164
 
174
- def check_box!(attribute, checked_value = "1", unchecked_value = "0", label: {}, field: {}, show_errors: true)
165
+ def check_box!(attribute, checked_value: "1", unchecked_value: "0", label_text: nil, label: {}, field: {}, show_errors: true)
175
166
  label[:super] ||= {}
176
167
  label[:super] = { class: "select-none ml-1" }.merge(label[:super])
177
168
  container do
178
169
  compact_join([
179
170
  "<div>".html_safe,
180
171
  public_send(:check_box, attribute, field, checked_value, unchecked_value),
181
- public_send(:label, attribute, nil, label),
172
+ public_send(:label, attribute, label_text, label),
182
173
  "</div>".html_safe,
183
174
  show_errors && inline_errors(attribute),
184
175
  ])
185
176
  end
186
177
  end
187
178
 
188
- def flatpickr_date!(attribute, label: {}, field: {}, show_errors: true)
179
+ def date_flatpickr!(attribute, label_text: nil, label: {}, field: {}, show_errors: true)
189
180
  container do
190
181
  compact_join([
191
- public_send(:label, attribute, label),
182
+ public_send(:label, attribute, label_text, label),
192
183
  %(<div class="mt-1">).html_safe,
193
- public_send(:flatpickr_date, attribute, field),
184
+ public_send(:date_flatpickr, attribute, field),
194
185
  show_errors && inline_errors(attribute),
195
186
  %(</div>).html_safe,
196
187
  ])
197
188
  end
198
189
  end
199
190
 
200
- def flatpickr_datetime!(attribute, label: {}, field: {}, show_errors: true)
191
+ def datetime_flatpickr!(attribute, label_text: nil, label: {}, field: {}, show_errors: true)
201
192
  container do
202
193
  compact_join([
203
- public_send(:label, attribute, label),
194
+ public_send(:label, attribute, label_text, label),
204
195
  %(<div class="mt-1">).html_safe,
205
- public_send(:flatpickr_datetime, attribute, field),
196
+ public_send(:datetime_flatpickr, attribute, field),
206
197
  show_errors && inline_errors(attribute),
207
198
  %(</div>).html_safe,
208
199
  ])
209
200
  end
210
201
  end
211
202
 
212
- def flatpickr_time!(attribute, label: {}, field: {}, show_errors: true)
203
+ def time_flatpickr!(attribute, label_text: nil, label: {}, field: {}, show_errors: true)
213
204
  container do
214
205
  compact_join([
215
- public_send(:label, attribute, label),
206
+ public_send(:label, attribute, label_text, label),
216
207
  %(<div class="mt-1">).html_safe,
217
- public_send(:flatpickr_time, attribute, field),
208
+ public_send(:time_flatpickr, attribute, field),
218
209
  show_errors && inline_errors(attribute),
219
210
  %(</div>).html_safe,
220
211
  ])
221
212
  end
222
213
  end
223
214
 
224
- def password_field!(attribute, label: {}, field: {}, show_errors: true)
215
+ def password_field!(attribute, label_text: nil, label: {}, field: {}, show_errors: true)
225
216
  container do
226
217
  compact_join([
227
- public_send(:label, attribute, label),
218
+ public_send(:label, attribute, label_text, label),
228
219
  %(<div class="mt-1">).html_safe,
229
220
  public_send(:password_field, attribute, field),
230
221
  show_errors && inline_errors(attribute),
@@ -233,10 +224,10 @@ module Super
233
224
  end
234
225
  end
235
226
 
236
- def rich_text_area!(attribute, label: {}, field: {}, show_errors: true)
227
+ def rich_text_area!(attribute, label_text: nil, label: {}, field: {}, show_errors: true)
237
228
  container do
238
229
  compact_join([
239
- public_send(:label, attribute, label),
230
+ public_send(:label, attribute, label_text, label),
240
231
  %(<div class="mt-1">).html_safe,
241
232
  public_send(:rich_text_area, attribute, field),
242
233
  show_errors && inline_errors(attribute),
@@ -245,10 +236,10 @@ module Super
245
236
  end
246
237
  end
247
238
 
248
- def select!(attribute, collection, label: {}, field: {}, show_errors: true)
239
+ def select!(attribute, collection, label_text: nil, label: {}, field: {}, show_errors: true)
249
240
  container do
250
241
  compact_join([
251
- public_send(:label, attribute, label),
242
+ public_send(:label, attribute, label_text, label),
252
243
  %(<div class="mt-1">).html_safe,
253
244
  public_send(:select, attribute, collection, field),
254
245
  show_errors && inline_errors(attribute),
@@ -257,10 +248,10 @@ module Super
257
248
  end
258
249
  end
259
250
 
260
- def text_field!(attribute, label: {}, field: {}, show_errors: true)
251
+ def text_field!(attribute, label_text: nil, label: {}, field: {}, show_errors: true)
261
252
  container do
262
253
  compact_join([
263
- public_send(:label, attribute, label),
254
+ public_send(:label, attribute, label_text, label),
264
255
  %(<div class="mt-1">).html_safe,
265
256
  public_send(:text_field, attribute, field),
266
257
  show_errors && inline_errors(attribute),
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Form
5
+ # Holds a recording of a form field definition
6
+ class FieldTranscript
7
+ def initialize
8
+ @super = false
9
+ end
10
+
11
+ attr_reader :method_name
12
+ attr_reader :args
13
+ attr_reader :kwargs
14
+
15
+ def super
16
+ @super = true
17
+ self
18
+ end
19
+
20
+ def super?
21
+ @super
22
+ end
23
+
24
+ def method_missing(new_method_name, *args, **kwargs)
25
+ if @method_name.present?
26
+ method_chain =
27
+ if super?
28
+ "super.#{@method_name}"
29
+ else
30
+ @method_name.to_s
31
+ end
32
+
33
+ raise Error::AlreadyTranscribed, "This instance already holds a transcription for: #{method_chain}"
34
+ end
35
+
36
+ @method_name = new_method_name
37
+ @args = args
38
+ @kwargs = kwargs
39
+ @method_name
40
+ end
41
+ end
42
+ end
43
+ end
@@ -24,13 +24,13 @@ module Super
24
24
  def attribute_type_for(attribute_name)
25
25
  case @model.type_for_attribute(attribute_name).type
26
26
  when :datetime
27
- @type.flatpickr_datetime
27
+ @type.datetime_flatpickr
28
28
  when :time
29
- @type.flatpickr_time
29
+ @type.time_flatpickr
30
30
  when :date
31
- @type.flatpickr_date
31
+ @type.date_flatpickr
32
32
  else
33
- @type.string
33
+ @type.text_field
34
34
  end
35
35
  end
36
36
  end
@@ -3,6 +3,38 @@
3
3
  module Super
4
4
  class Form
5
5
  class SchemaTypes
6
+ class Direct
7
+ def initialize(super_builder:, method_name:, args:, kwargs:)
8
+ @super_builder = super_builder
9
+ @method_name = method_name
10
+ @args = args
11
+ @kwargs = kwargs
12
+ end
13
+
14
+ attr_reader :super_builder
15
+ attr_reader :method_name
16
+ attr_reader :args
17
+ attr_reader :kwargs
18
+
19
+ def nested_fields
20
+ {}
21
+ end
22
+
23
+ def to_partial_path
24
+ "form_field"
25
+ end
26
+
27
+ def ==(other)
28
+ return false if other.class != self.class
29
+ return false if other.super_builder != super_builder
30
+ return false if other.method_name != method_name
31
+ return false if other.args != args
32
+ return false if other.kwargs != kwargs
33
+
34
+ true
35
+ end
36
+ end
37
+
6
38
  class Generic
7
39
  def initialize(partial_path:, extras:, nested:)
8
40
  @partial_path = partial_path
@@ -65,61 +97,73 @@ module Super
65
97
  @fields = fields
66
98
  end
67
99
 
68
- def generic(partial_path, **extras)
100
+ def partial(partial_path, **extras)
69
101
  Generic.new(partial_path: partial_path, extras: extras, nested: {})
70
102
  end
71
103
 
72
- def select(**extras)
73
- Generic.new(partial_path: "form_field_select", extras: extras, nested: {})
104
+ def direct(method_name, *args, super_builder: true, **kwargs)
105
+ Direct.new(super_builder: super_builder, method_name: method_name, args: args, kwargs: kwargs)
74
106
  end
75
107
 
76
- def string(**extras)
77
- Generic.new(partial_path: "form_field_text", extras: extras, nested: {})
108
+ def select(*args, **kwargs)
109
+ Direct.new(super_builder: true, method_name: :select!, args: args, kwargs: kwargs)
78
110
  end
79
111
 
80
- alias text string
112
+ def text_field(*args, **kwargs)
113
+ Direct.new(super_builder: true, method_name: :text_field!, args: args, kwargs: kwargs)
114
+ end
115
+
116
+ def rich_text_area(*args, **kwargs)
117
+ Direct.new(super_builder: true, method_name: :rich_text_area!, args: args, kwargs: kwargs)
118
+ end
119
+
120
+ def check_box(*args, **kwargs)
121
+ Direct.new(super_builder: true, method_name: :check_box!, args: args, kwargs: kwargs)
122
+ end
81
123
 
82
- def rich_text_area(**extras)
83
- Generic.new(partial_path: "form_field_rich_text_area", extras: extras, nested: {})
124
+ def date_flatpickr(*args, **kwargs)
125
+ Direct.new(super_builder: true, method_name: :date_flatpickr!, args: args, kwargs: kwargs)
84
126
  end
85
127
 
86
- def checkbox(**extras)
87
- Generic.new(partial_path: "form_field_checkbox", extras: extras, nested: {})
128
+ def datetime_flatpickr(*args, **kwargs)
129
+ Direct.new(super_builder: true, method_name: :datetime_flatpickr!, args: args, kwargs: kwargs)
88
130
  end
89
131
 
90
- def flatpickr_date(**extras)
91
- Generic.new(partial_path: "form_field_flatpickr_date", extras: extras, nested: {})
132
+ def hidden_field(*args, **kwargs)
133
+ Direct.new(super_builder: false, method_name: :hidden_field, args: args, kwargs: kwargs)
92
134
  end
93
135
 
94
- def flatpickr_datetime(**extras)
95
- Generic.new(partial_path: "form_field_flatpickr_datetime", extras: extras, nested: {})
136
+ def password_field(*args, **kwargs)
137
+ Direct.new(super_builder: true, method_name: :password_field!, args: args, kwargs: kwargs)
96
138
  end
97
139
 
98
- def flatpickr_time(**extras)
99
- Generic.new(partial_path: "form_field_flatpickr_time", extras: extras, nested: {})
140
+ def time_flatpickr(*args, **kwargs)
141
+ Direct.new(super_builder: true, method_name: :time_flatpickr!, args: args, kwargs: kwargs)
100
142
  end
101
143
 
102
144
  def has_many(reader, **extras)
103
- nested = @fields.nested do
104
- yield
145
+ subfields = Schema::Fields.new
146
+ @fields.nested do
147
+ yield subfields
105
148
  end
106
149
 
107
150
  Generic.new(
108
151
  partial_path: "form_has_many",
109
152
  extras: extras.merge(reader: reader),
110
- nested: nested
153
+ nested: subfields.to_h
111
154
  )
112
155
  end
113
156
 
114
157
  def has_one(reader, **extras)
115
- nested = @fields.nested do
116
- yield
158
+ subfields = Schema::Fields.new
159
+ @fields.nested do
160
+ yield subfields
117
161
  end
118
162
 
119
163
  Generic.new(
120
164
  partial_path: "form_has_one",
121
165
  extras: extras.merge(reader: reader),
122
- nested: nested
166
+ nested: subfields.to_h
123
167
  )
124
168
  end
125
169