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.
- checksums.yaml +4 -4
- data/Gemfile +7 -3
- data/Gemfile.lock +36 -38
- data/README.md +8 -4
- data/Rakefile +0 -7
- data/lib/formalist/element/attributes.rb +54 -0
- data/lib/formalist/element/class_interface.rb +133 -0
- data/lib/formalist/element/definition.rb +55 -0
- data/lib/formalist/element/permitted_children.rb +46 -0
- data/lib/formalist/element.rb +51 -0
- data/lib/formalist/elements/attr.rb +74 -0
- data/lib/formalist/elements/compound_field.rb +49 -0
- data/lib/formalist/elements/field.rb +73 -0
- data/lib/formalist/elements/group.rb +50 -0
- data/lib/formalist/elements/many.rb +125 -0
- data/lib/formalist/elements/section.rb +58 -0
- data/lib/formalist/elements/standard/check_box.rb +20 -0
- data/lib/formalist/elements/standard/date_field.rb +12 -0
- data/lib/formalist/elements/standard/date_time_field.rb +12 -0
- data/lib/formalist/elements/standard/hidden_field.rb +11 -0
- data/lib/formalist/elements/standard/multi_selection_field.rb +16 -0
- data/lib/formalist/elements/standard/number_field.rb +17 -0
- data/lib/formalist/elements/standard/radio_buttons.rb +13 -0
- data/lib/formalist/elements/standard/select_box.rb +13 -0
- data/lib/formalist/elements/standard/selection_field.rb +16 -0
- data/lib/formalist/elements/standard/text_area.rb +15 -0
- data/lib/formalist/elements/standard/text_field.rb +14 -0
- data/lib/formalist/elements/standard.rb +11 -0
- data/lib/formalist/elements.rb +20 -0
- data/lib/formalist/form/definition_context.rb +58 -4
- data/lib/formalist/form/result.rb +5 -27
- data/lib/formalist/form.rb +15 -35
- data/lib/formalist/types.rb +30 -0
- data/lib/formalist/version.rb +1 -1
- data/lib/formalist.rb +0 -20
- data/spec/examples.txt +8 -7
- data/spec/integration/dependency_injection_spec.rb +54 -0
- data/spec/integration/form_spec.rb +86 -13
- data/spec/spec_helper.rb +12 -5
- data/spec/support/constants.rb +11 -0
- data/spec/unit/elements/standard/check_box_spec.rb +33 -0
- metadata +36 -63
- data/lib/formalist/definition_compiler.rb +0 -61
- data/lib/formalist/display_adapters/default.rb +0 -9
- data/lib/formalist/display_adapters/radio.rb +0 -19
- data/lib/formalist/display_adapters/select.rb +0 -19
- data/lib/formalist/display_adapters/textarea.rb +0 -14
- data/lib/formalist/display_adapters.rb +0 -16
- data/lib/formalist/form/definition/attr.rb +0 -20
- data/lib/formalist/form/definition/component.rb +0 -31
- data/lib/formalist/form/definition/field.rb +0 -29
- data/lib/formalist/form/definition/group.rb +0 -31
- data/lib/formalist/form/definition/many.rb +0 -41
- data/lib/formalist/form/definition/section.rb +0 -23
- data/lib/formalist/form/definition.rb +0 -37
- data/lib/formalist/form/result/attr.rb +0 -82
- data/lib/formalist/form/result/component.rb +0 -51
- data/lib/formalist/form/result/field.rb +0 -77
- data/lib/formalist/form/result/group.rb +0 -51
- data/lib/formalist/form/result/many.rb +0 -123
- data/lib/formalist/form/result/section.rb +0 -54
- data/lib/formalist/form/validated_result.rb +0 -35
- data/lib/formalist/output_compiler.rb +0 -43
- data/lib/formalist/validation/collection_rules_compiler.rb +0 -77
- data/lib/formalist/validation/predicate_list_compiler.rb +0 -73
- data/lib/formalist/validation/value_rules_compiler.rb +0 -96
- data/spec/integration/display_adapters_spec.rb +0 -55
- data/spec/integration/validation_spec.rb +0 -86
- data/spec/unit/output_compiler_spec.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96b43ff385384015c3c6c1cac64a8e9cccaa753c
|
4
|
+
data.tar.gz: 42acab631e93a3c52c73de8a5822c96622e7b2af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
7
|
-
gem "
|
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:
|
4
|
-
|
17
|
+
revision: 6167d6f778faefe13916d3d8eee8bdb4197398d3
|
18
|
+
branch: master
|
5
19
|
specs:
|
6
|
-
dry-validation (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.
|
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.
|
31
|
+
formalist (0.2.3)
|
17
32
|
dry-configurable
|
18
33
|
dry-container
|
19
|
-
dry-
|
34
|
+
dry-types
|
20
35
|
inflecto
|
21
36
|
|
22
37
|
GEM
|
23
38
|
remote: https://rubygems.org/
|
24
39
|
specs:
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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-
|
33
|
-
|
34
|
-
|
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.
|
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]
|
5
9
|
[][travis]
|
10
|
+
[][code_climate]
|
11
|
+
[][code_climate]
|
12
|
+
[][inch]
|
6
13
|
|
7
14
|
## Installation
|
8
15
|
|
9
|
-
Add
|
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
@@ -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
|