formalist 0.2.2 → 0.2.3

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -3
  3. data/Gemfile.lock +36 -38
  4. data/README.md +8 -4
  5. data/Rakefile +0 -7
  6. data/lib/formalist/element/attributes.rb +54 -0
  7. data/lib/formalist/element/class_interface.rb +133 -0
  8. data/lib/formalist/element/definition.rb +55 -0
  9. data/lib/formalist/element/permitted_children.rb +46 -0
  10. data/lib/formalist/element.rb +51 -0
  11. data/lib/formalist/elements/attr.rb +74 -0
  12. data/lib/formalist/elements/compound_field.rb +49 -0
  13. data/lib/formalist/elements/field.rb +73 -0
  14. data/lib/formalist/elements/group.rb +50 -0
  15. data/lib/formalist/elements/many.rb +125 -0
  16. data/lib/formalist/elements/section.rb +58 -0
  17. data/lib/formalist/elements/standard/check_box.rb +20 -0
  18. data/lib/formalist/elements/standard/date_field.rb +12 -0
  19. data/lib/formalist/elements/standard/date_time_field.rb +12 -0
  20. data/lib/formalist/elements/standard/hidden_field.rb +11 -0
  21. data/lib/formalist/elements/standard/multi_selection_field.rb +16 -0
  22. data/lib/formalist/elements/standard/number_field.rb +17 -0
  23. data/lib/formalist/elements/standard/radio_buttons.rb +13 -0
  24. data/lib/formalist/elements/standard/select_box.rb +13 -0
  25. data/lib/formalist/elements/standard/selection_field.rb +16 -0
  26. data/lib/formalist/elements/standard/text_area.rb +15 -0
  27. data/lib/formalist/elements/standard/text_field.rb +14 -0
  28. data/lib/formalist/elements/standard.rb +11 -0
  29. data/lib/formalist/elements.rb +20 -0
  30. data/lib/formalist/form/definition_context.rb +58 -4
  31. data/lib/formalist/form/result.rb +5 -27
  32. data/lib/formalist/form.rb +15 -35
  33. data/lib/formalist/types.rb +30 -0
  34. data/lib/formalist/version.rb +1 -1
  35. data/lib/formalist.rb +0 -20
  36. data/spec/examples.txt +8 -7
  37. data/spec/integration/dependency_injection_spec.rb +54 -0
  38. data/spec/integration/form_spec.rb +86 -13
  39. data/spec/spec_helper.rb +12 -5
  40. data/spec/support/constants.rb +11 -0
  41. data/spec/unit/elements/standard/check_box_spec.rb +33 -0
  42. metadata +36 -63
  43. data/lib/formalist/definition_compiler.rb +0 -61
  44. data/lib/formalist/display_adapters/default.rb +0 -9
  45. data/lib/formalist/display_adapters/radio.rb +0 -19
  46. data/lib/formalist/display_adapters/select.rb +0 -19
  47. data/lib/formalist/display_adapters/textarea.rb +0 -14
  48. data/lib/formalist/display_adapters.rb +0 -16
  49. data/lib/formalist/form/definition/attr.rb +0 -20
  50. data/lib/formalist/form/definition/component.rb +0 -31
  51. data/lib/formalist/form/definition/field.rb +0 -29
  52. data/lib/formalist/form/definition/group.rb +0 -31
  53. data/lib/formalist/form/definition/many.rb +0 -41
  54. data/lib/formalist/form/definition/section.rb +0 -23
  55. data/lib/formalist/form/definition.rb +0 -37
  56. data/lib/formalist/form/result/attr.rb +0 -82
  57. data/lib/formalist/form/result/component.rb +0 -51
  58. data/lib/formalist/form/result/field.rb +0 -77
  59. data/lib/formalist/form/result/group.rb +0 -51
  60. data/lib/formalist/form/result/many.rb +0 -123
  61. data/lib/formalist/form/result/section.rb +0 -54
  62. data/lib/formalist/form/validated_result.rb +0 -35
  63. data/lib/formalist/output_compiler.rb +0 -43
  64. data/lib/formalist/validation/collection_rules_compiler.rb +0 -77
  65. data/lib/formalist/validation/predicate_list_compiler.rb +0 -73
  66. data/lib/formalist/validation/value_rules_compiler.rb +0 -96
  67. data/spec/integration/display_adapters_spec.rb +0 -55
  68. data/spec/integration/validation_spec.rb +0 -86
  69. data/spec/unit/output_compiler_spec.rb +0 -70
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 213025004cb07e1b9962e338b59d4170e2f20c9d
4
- data.tar.gz: f17a38dd7de60b6672ec18027b988119b73be9f3
3
+ metadata.gz: 96b43ff385384015c3c6c1cac64a8e9cccaa753c
4
+ data.tar.gz: 42acab631e93a3c52c73de8a5822c96622e7b2af
5
5
  SHA512:
6
- metadata.gz: dafc943fc98d96e5a671b81ebbe6be0eebc20a9ab3f9558bae54ae4c8535cd542cb1e56b304dc6c89494b3f4d410694063192e993a51c79748d3aea2fa9b6681
7
- data.tar.gz: b0bf744c37494e443e294f25d0a8cc4f549955149ffa205e44be3ccee791d0b53fc60ca5bfd3471ce6ed80c1dac277c5dcac36fa606eeccf82526e2e5d314707
6
+ metadata.gz: 1cdeb2f130a14bceb9a8af2a71fcf22c463c1fc1554b42fc98ccc20bb826804853004553a3cee82114500ba49139d4413adb0ab3855cae19f73c2fa2a06e4594
7
+ data.tar.gz: b87cd081725b0fe4556858d33b8648f0dead749122d74f2e5371eb720958d5610deb104e0de09a6d1341d4ec7825a01907aad807072ac2747d2536c852505b87
data/Gemfile CHANGED
@@ -1,11 +1,15 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- # Gem dependencies are specified in formalist.gemspec
4
3
  gemspec
5
4
 
6
- # Lock to a version of dry-v that'll offer the AST/error message structures we expect
7
- gem "dry-validation", git: "https://github.com/dryrb/dry-validation", ref: "6447302f3b53766b29f29230831890a5cc3822e0"
5
+ group :test do
6
+ gem "codeclimate-test-reporter", require: nil
7
+ gem "dry-auto_inject"
8
+ gem "dry-validation", git: "https://github.com/dryrb/dry-validation", branch: "master"
9
+ gem "dry-types", git: "https://github.com/dryrb/dry-types", branch: "master"
10
+ end
8
11
 
9
12
  group :tools do
10
13
  gem "pry"
14
+ gem "byebug", platform: :mri
11
15
  end
data/Gemfile.lock CHANGED
@@ -1,63 +1,67 @@
1
+ GIT
2
+ remote: https://github.com/dryrb/dry-types
3
+ revision: 9d72307cfaa33afa8b597646cd77a06de8b6bb88
4
+ branch: master
5
+ specs:
6
+ dry-types (0.7.1)
7
+ concurrent-ruby (~> 1.0)
8
+ dry-configurable (~> 0.1)
9
+ dry-container (~> 0.3)
10
+ dry-equalizer (~> 0.2)
11
+ dry-logic (~> 0.2, >= 0.2.0)
12
+ inflecto (~> 0.0.0, >= 0.0.2)
13
+ kleisli (~> 0.2)
14
+
1
15
  GIT
2
16
  remote: https://github.com/dryrb/dry-validation
3
- revision: 6447302f3b53766b29f29230831890a5cc3822e0
4
- ref: 6447302f3b53766b29f29230831890a5cc3822e0
17
+ revision: 6167d6f778faefe13916d3d8eee8bdb4197398d3
18
+ branch: master
5
19
  specs:
6
- dry-validation (0.6.0)
20
+ dry-validation (0.7.4)
21
+ concurrent-ruby (~> 1.0)
7
22
  dry-configurable (~> 0.1, >= 0.1.3)
8
23
  dry-container (~> 0.2, >= 0.2.8)
9
- dry-data (~> 0.5, >= 0.5.0)
10
24
  dry-equalizer (~> 0.2)
11
- dry-logic (~> 0.1, >= 0.1.4)
25
+ dry-logic (~> 0.2, >= 0.2.2)
26
+ dry-types (~> 0.6, >= 0.6.0)
12
27
 
13
28
  PATH
14
29
  remote: .
15
30
  specs:
16
- formalist (0.2.2)
31
+ formalist (0.2.3)
17
32
  dry-configurable
18
33
  dry-container
19
- dry-validation (~> 0.6.0)
34
+ dry-types
20
35
  inflecto
21
36
 
22
37
  GEM
23
38
  remote: https://rubygems.org/
24
39
  specs:
25
- ast (2.1.0)
26
- astrolabe (1.3.1)
27
- parser (~> 2.2)
28
- byebug (8.2.1)
29
- coderay (1.1.0)
40
+ byebug (8.2.3)
41
+ codeclimate-test-reporter (0.5.0)
42
+ simplecov (>= 0.7.1, < 1.0.0)
43
+ coderay (1.1.1)
44
+ concurrent-ruby (1.0.1)
30
45
  diff-lcs (1.2.5)
31
46
  docile (1.1.5)
32
- dry-configurable (0.1.3)
33
- thread_safe
34
- dry-container (0.2.8)
47
+ dry-auto_inject (0.2.0)
48
+ dry-configurable (0.1.4)
49
+ concurrent-ruby (~> 1.0)
50
+ dry-container (0.3.1)
51
+ concurrent-ruby (~> 1.0)
35
52
  dry-configurable (~> 0.1, >= 0.1.3)
36
- thread_safe
37
- dry-data (0.5.1)
38
- dry-configurable (~> 0.1)
39
- dry-container (~> 0.2)
40
- dry-equalizer (~> 0.2)
41
- dry-logic (~> 0.1)
42
- inflecto (~> 0.0.0, >= 0.0.2)
43
- kleisli (~> 0.2)
44
- thread_safe (~> 0.3)
45
53
  dry-equalizer (0.2.0)
46
- dry-logic (0.1.4)
54
+ dry-logic (0.2.2)
47
55
  dry-container (~> 0.2, >= 0.2.6)
48
56
  dry-equalizer (~> 0.2)
49
57
  inflecto (0.0.2)
50
58
  json (1.8.3)
51
59
  kleisli (0.2.7)
52
60
  method_source (0.8.2)
53
- parser (2.2.3.0)
54
- ast (>= 1.1, < 3.0)
55
- powerpack (0.1.1)
56
61
  pry (0.10.3)
57
62
  coderay (~> 1.1.0)
58
63
  method_source (~> 0.8.1)
59
64
  slop (~> 3.4)
60
- rainbow (2.0.0)
61
65
  rake (10.4.2)
62
66
  rspec (3.3.0)
63
67
  rspec-core (~> 3.3.0)
@@ -72,20 +76,12 @@ GEM
72
76
  diff-lcs (>= 1.2.0, < 2.0)
73
77
  rspec-support (~> 3.3.0)
74
78
  rspec-support (3.3.0)
75
- rubocop (0.34.2)
76
- astrolabe (~> 1.3)
77
- parser (>= 2.2.2.5, < 3.0)
78
- powerpack (~> 0.1)
79
- rainbow (>= 1.99.1, < 3.0)
80
- ruby-progressbar (~> 1.4)
81
- ruby-progressbar (1.7.5)
82
79
  simplecov (0.10.0)
83
80
  docile (~> 1.1.0)
84
81
  json (~> 1.8)
85
82
  simplecov-html (~> 0.10.0)
86
83
  simplecov-html (0.10.0)
87
84
  slop (3.6.0)
88
- thread_safe (0.3.5)
89
85
  yard (0.8.7.6)
90
86
 
91
87
  PLATFORMS
@@ -94,12 +90,14 @@ PLATFORMS
94
90
  DEPENDENCIES
95
91
  bundler (~> 1.10)
96
92
  byebug
93
+ codeclimate-test-reporter
94
+ dry-auto_inject
95
+ dry-types!
97
96
  dry-validation!
98
97
  formalist!
99
98
  pry
100
99
  rake (~> 10.4.2)
101
100
  rspec (~> 3.3.0)
102
- rubocop (~> 0.34.2)
103
101
  simplecov (~> 0.10.0)
104
102
  yard
105
103
 
data/README.md CHANGED
@@ -1,20 +1,24 @@
1
+ [gem]: https://rubygems.org/gems/formalist
1
2
  [travis]: https://travis-ci.org/icelab/formalist
3
+ [code_climate]: https://codeclimate.com/github/icelab/formalist
4
+ [inch]: http://inch-ci.org/github/icelab/formalist
2
5
 
3
6
  # Formalist
4
7
 
8
+ [![Gem Version](https://img.shields.io/gem/v/formalist.svg)][gem]
5
9
  [![Build Status](https://travis-ci.org/icelab/formalist.svg?branch=master)][travis]
10
+ [![Code Climate](https://img.shields.io/codeclimate/github/icelab/formalist.svg)][code_climate]
11
+ [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/icelab/formalist.svg)][code_climate]
12
+ [![API Documentation Coverage](http://inch-ci.org/github/icelab/formalist.svg)][inch]
6
13
 
7
14
  ## Installation
8
15
 
9
- Add these lines to your application’s `Gemfile`:
16
+ Add this line to your application’s `Gemfile`:
10
17
 
11
18
  ```ruby
12
- gem "dry-validation", git: "https://github.com/dryrb/dry-validation", ref: "6447302f3b53766b29f29230831890a5cc3822e0"
13
19
  gem "formalist"
14
20
  ```
15
21
 
16
- The dry-validation dependency is a temporary lock to a version that offers the AST/error message structures we expect. You should be able to remove this after future releases of formalist and dry-validation.
17
-
18
22
  Run `bundle` to install the gems.
19
23
 
20
24
  ## Contributing
data/Rakefile CHANGED
@@ -3,11 +3,4 @@ require "bundler/gem_tasks"
3
3
  require "rspec/core/rake_task"
4
4
  RSpec::Core::RakeTask.new
5
5
 
6
- require "rubocop/rake_task"
7
- RuboCop::RakeTask.new
8
-
9
6
  task default: :spec
10
- # task default: :ci
11
-
12
- desc "Run the test suite"
13
- task ci: %w(rubocop spec)
@@ -0,0 +1,54 @@
1
+ module Formalist
2
+ class Element
3
+ class Attributes
4
+ # Returns the attributes hash.
5
+ attr_reader :attrs
6
+
7
+ # Creates an attributes object from the supplied hash.
8
+ #
9
+ # @param attrs [Hash] hash of form element attributes
10
+ def initialize(attrs = {})
11
+ @attrs = attrs
12
+ end
13
+
14
+ # Returns the attributes as an abstract syntax tree.
15
+ #
16
+ # @return [Array] the abstract syntax tree
17
+ def to_ast
18
+ deep_to_ast(deep_simplify(attrs))
19
+ end
20
+
21
+ private
22
+
23
+ def deep_to_ast(value)
24
+ case value
25
+ when Hash
26
+ [:object, [value.map { |k,v| [k.to_sym, deep_to_ast(v)] }].reject(&:empty?).flatten(1)]
27
+ when Array
28
+ [:array, value.map { |v| deep_to_ast(v) }]
29
+ when String, Numeric, TrueClass, FalseClass, NilClass
30
+ [:value, [value]]
31
+ else
32
+ [:value, [value.to_s]]
33
+ end
34
+ end
35
+
36
+ def deep_simplify(value)
37
+ case value
38
+ when Hash
39
+ value.each_with_object({}) { |(k,v), output| output[k] = deep_simplify(v) }
40
+ when Array
41
+ value.map { |v| deep_simplify(v) }
42
+ when String, Numeric, TrueClass, FalseClass, NilClass
43
+ value
44
+ else
45
+ if value.respond_to?(:to_h)
46
+ deep_simplify(value.to_h)
47
+ else
48
+ value.to_s
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,133 @@
1
+ require "inflecto"
2
+ require "formalist/element/permitted_children"
3
+
4
+ module Formalist
5
+ class Element
6
+ # Class-level API for form elements.
7
+ module ClassInterface
8
+ # Returns the element's type, which is a symbolized, camlized
9
+ # representation of the element's class name.
10
+ #
11
+ # This is a critical hook for customising form rendering when using
12
+ # custom form elements, since the type in this case will be based on the
13
+ # name of form element's sublass.
14
+ #
15
+ # @example Basic element
16
+ # Formalist::Elements::Field.type # => :field
17
+ #
18
+ # @example Custom element
19
+ # class MyField < Formalist::Elements::Field
20
+ # end
21
+ #
22
+ # MyField.type # => :my_field
23
+ #
24
+ # @!scope class
25
+ # @return [Symbol] the element type.
26
+ def type
27
+ Inflecto.underscore(Inflecto.demodulize(name)).to_sym
28
+ end
29
+
30
+ # Define a form element attribute.
31
+ #
32
+ # Form element attributes can be set when the element is defined, and
33
+ # can be further populated by the form element object itself, when the
34
+ # form is being built, using both the user input and any dependencies
35
+ # passed to the element via the form.
36
+ #
37
+ # Attributes are the way to ensure the form renderer has all the
38
+ # information it needs to render the element appropriately. Attributes
39
+ # are type-checked in order to ensure they're being passed appropriate
40
+ # values. Attributes can hold any type of value as long as it can be
41
+ # reduced to an abstract syntax tree representation by
42
+ # `Form::Element::Attributes#to_ast`.
43
+ #
44
+ # @see Formalist::Element::Attributes#to_ast
45
+ #
46
+ # @!scope class
47
+ # @param name [Symbol] attribute name
48
+ # @param type [Dry::Data::Type, #call] value type coercer/checker
49
+ # @param default default value (applied when the attribute is not explicitly populated)
50
+ # @return void
51
+ def attribute(name, type, default: nil)
52
+ attributes(name => {type: type, default: default})
53
+ end
54
+
55
+ # Returns the attributes schema for the form element.
56
+ #
57
+ # Each item in the schema includes a type definition and a default value
58
+ # (`nil` if none specified).
59
+ #
60
+ # @example
61
+ # Formalist::Elements::Field.attributes_schema
62
+ # # => {
63
+ # :name => {:type => #<Dry::Data::Type>, :default => "Default name"},
64
+ # :email => {:type => #<Dry::Data::Type>, :default => "default email"}
65
+ # }
66
+ #
67
+ # @!scope class
68
+ # @return [Hash<Symbol, Hash>] the attributes schema
69
+ def attributes_schema
70
+ super_schema = superclass.respond_to?(:attributes_schema) ? superclass.attributes_schema : {}
71
+ super_schema.merge(@attributes_schema || {})
72
+ end
73
+
74
+ # Sets or fetches the policy for the element's permitted child form elements.
75
+ #
76
+ # @overload permitted_children(policy)
77
+ # Set a policy for whether the element permits child form elements.
78
+ #
79
+ # Specify `:all` to allow all children, or `:none` to permit no
80
+ # children.
81
+ #
82
+ # @example Permitting all children
83
+ # permitted_children :all
84
+ #
85
+ # @example Permitting no children
86
+ # permitted_children :none
87
+ #
88
+ # @return void
89
+ #
90
+ # @overload permitted_children(element_type, ...)
91
+ # Permit the element to contain only the specified element types as children.
92
+ #
93
+ # @example
94
+ # permit_children :section, :field
95
+ #
96
+ # @param element_type [Symbol] the name of a child element type to permit
97
+ # @param ... [Symbol] more child element types to permit
98
+ # @return void
99
+ #
100
+ # @overload permitted_children
101
+ # Returns the permitted child element types for the element.
102
+ #
103
+ # If no `permitted_children` policy was previously specified, then it
104
+ # allows all children by default.
105
+ #
106
+ # @return [#permitted?] permissions object.
107
+ #
108
+ # @see Formalist::Element::PermittedChildren
109
+ #
110
+ # @!scope class
111
+ def permitted_children(*args)
112
+ return @permitted_children ||= PermittedChildren.all if args.empty?
113
+
114
+ @permitted_children = if %i[all none].include?(args.first)
115
+ PermittedChildren.send(args.first)
116
+ else
117
+ PermittedChildren[args]
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ # @!scope class
124
+ # @api private
125
+ def attributes(new_schema)
126
+ prev_schema = @attributes_schema || {}
127
+ @attributes_schema = prev_schema.merge(new_schema)
128
+
129
+ self
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,55 @@
1
+ require "formalist/types"
2
+
3
+ module Formalist
4
+ class Element
5
+ class Definition
6
+ Deferred = Struct.new(:name)
7
+
8
+ attr_reader :type
9
+ attr_reader :args
10
+ attr_reader :attributes
11
+ attr_reader :children
12
+
13
+ def initialize(type, *args, attributes, children)
14
+ @type = type
15
+ @args = args
16
+ @attributes = attributes
17
+ @children = children
18
+ end
19
+
20
+ def ==(other)
21
+ # Require a matching type.
22
+ return false if type != other.type
23
+
24
+ # If there are no primary args, it means that the element has no real
25
+ # "identifier" that requires uniqueness, so it's safe to say they don't
26
+ # match here.
27
+ return false if args.empty?
28
+
29
+ # Otherwise, use the primary args as a marker of a definitions
30
+ # uniqueness. With the current set of base form elements, the primary
31
+ # args only ever contains a name, so this is effectively a uniqueness
32
+ # check on the element's name.
33
+ args == other.args
34
+ end
35
+
36
+ def resolve(scope)
37
+ resolved_args = args.map { |arg|
38
+ arg.is_a?(Deferred) ? scope.send(arg.name) : arg
39
+ }
40
+
41
+ resolved_attributes = attributes.each_with_object({}) { |(key, val), hsh|
42
+ hsh[key] = val.is_a?(Deferred) ? scope.send(val.name) : val
43
+ }
44
+
45
+ resolved_children = children.map { |c| c.resolve(scope) }
46
+
47
+ self.class.new(type, *resolved_args, resolved_attributes, resolved_children)
48
+ end
49
+
50
+ def call(input, messages)
51
+ type.new(*args, attributes, children, input, messages)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ require "inflecto"
2
+
3
+ module Formalist
4
+ class Element
5
+ class PermittedChildren
6
+ All = Class.new do
7
+ def permitted?(*)
8
+ true
9
+ end
10
+ end.new
11
+
12
+ None = Class.new do
13
+ def permitted?(*)
14
+ false
15
+ end
16
+ end.new
17
+
18
+ class Some
19
+ attr_reader :permitted_children
20
+
21
+ def initialize(children)
22
+ @permitted_children = children
23
+ end
24
+
25
+ def permitted?(child)
26
+ permitted_children.any? { |permitted_child|
27
+ permitted_child_class = Elements.const_get(Inflecto.camelize(permitted_child))
28
+ child.ancestors.include?(permitted_child_class)
29
+ }
30
+ end
31
+ end
32
+
33
+ def self.all
34
+ All
35
+ end
36
+
37
+ def self.none
38
+ None
39
+ end
40
+
41
+ def self.[](children)
42
+ Some.new(children)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,51 @@
1
+ require "formalist/element/attributes"
2
+ require "formalist/element/class_interface"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Element
7
+ extend ClassInterface
8
+
9
+ # @api private
10
+ attr_reader :attributes, :children, :input, :errors
11
+
12
+ # @api private
13
+ def initialize(*args, attributes, children, input, errors)
14
+ # Set supplied attributes or their defaults
15
+ full_attributes = self.class.attributes_schema.each_with_object({}) { |(name, defn), memo|
16
+ value = attributes[name] || defn[:default]
17
+ memo[name] = value unless value.nil?
18
+ }
19
+
20
+ # Then run them through the schema
21
+ @attributes = Types::Hash.schema(self.class.attributes_schema.map { |name, defn| [name, defn[:type]] }.to_h).(full_attributes)
22
+
23
+ @children = []
24
+ @input = input
25
+ @errors = errors
26
+ end
27
+
28
+ # Returns the element's type, which is a symbolized, camlized
29
+ # representation of the element's class name.
30
+ #
31
+ # This is a critical hook for customising form rendering when using custom
32
+ # form elements, since the type in this case will be based on the name of
33
+ # form element's sublass.
34
+ #
35
+ # @example Basic element
36
+ # field.type # => :field
37
+ #
38
+ # @example Custom element
39
+ # my_field.type # => :my_field
40
+ #
41
+ # @return [Symbol] the element type.
42
+ def type
43
+ self.class.type
44
+ end
45
+
46
+ # @abstract
47
+ def to_ast
48
+ raise NotImplementedError
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,74 @@
1
+ require "formalist/element"
2
+ require "formalist/types"
3
+
4
+ module Formalist
5
+ class Elements
6
+ class Attr < Element
7
+ permitted_children :all
8
+
9
+ # @api private
10
+ attr_reader :name
11
+
12
+ attribute :label, Types::String
13
+
14
+ # @api private
15
+ def initialize(*args, attributes, children, input, errors)
16
+ super
17
+
18
+ @name = Types::ElementName.(args.first)
19
+ @input = input.fetch(@name, {})
20
+ @errors = errors[@name]
21
+ @children = build_children(children)
22
+ end
23
+
24
+ # Converts the attribute into an abstract syntax tree.
25
+ #
26
+ # It takes the following format:
27
+ #
28
+ # ```
29
+ # [:attr, [params]]
30
+ # ```
31
+ #
32
+ # With the following parameters:
33
+ #
34
+ # 1. Attribute name
35
+ # 2. Custom element type (or `:attr` otherwise)
36
+ # 3. Error messages
37
+ # 4. Form element attributes
38
+ # 5. Child form elements
39
+ #
40
+ # @see Formalist::Element::Attributes#to_ast "Form element attributes" structure
41
+ #
42
+ # @example "metadata" attr
43
+ # attr.to_ast
44
+ # # => [:attr, [
45
+ # :metadata,
46
+ # :attr,
47
+ # ["metadata is missing"],
48
+ # [:object, []],
49
+ # [...child elements...]
50
+ # ]]
51
+ #
52
+ # @return [Array] the attribute as an abstract syntax tree.
53
+ def to_ast
54
+ local_errors = errors.is_a?(Array) ? errors : []
55
+
56
+ [:attr, [
57
+ name,
58
+ type,
59
+ local_errors,
60
+ Element::Attributes.new(attributes).to_ast,
61
+ children.map(&:to_ast),
62
+ ]]
63
+ end
64
+
65
+ private
66
+
67
+ def build_children(definitions)
68
+ child_errors = errors.is_a?(Hash) ? errors : {}
69
+
70
+ definitions.map { |definition| definition.(input, child_errors) }
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,49 @@
1
+ require "formalist/element"
2
+ require "formalist/types"
3
+
4
+ module Formalist
5
+ class Elements
6
+ class CompoundField < Element
7
+ permitted_children :field
8
+
9
+ def initialize(*args, attributes, children, input, errors)
10
+ super
11
+ @children = children.map { |definition| definition.(input, errors) }
12
+ end
13
+
14
+ # Converts the compound field into an abstract syntax tree.
15
+ #
16
+ # It takes the following format:
17
+ #
18
+ # ```
19
+ # [:compound_field, [params]]
20
+ # ```
21
+ #
22
+ # With the following parameters:
23
+ #
24
+ # 1. Custom element type (or `:compound_field` otherwise)
25
+ # 2. Form element attributes
26
+ # 3. Child form elements
27
+ #
28
+ # @see Formalist::Element::Attributes#to_ast "Form element attributes" structure
29
+ #
30
+ # @example
31
+ # compound_field.to_ast
32
+ # # => [:compound_field, [
33
+ # :content,
34
+ # :compound_field,
35
+ # [:object, []],
36
+ # [...child elements...],
37
+ # ]]
38
+ #
39
+ # @return [Array] the compound field as an abstract syntax tree.
40
+ def to_ast
41
+ [:compound_field, [
42
+ type,
43
+ Element::Attributes.new(attributes).to_ast,
44
+ children.map(&:to_ast),
45
+ ]]
46
+ end
47
+ end
48
+ end
49
+ end