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
@@ -0,0 +1,73 @@
1
+ require "formalist/element"
2
+ require "formalist/types"
3
+
4
+ module Formalist
5
+ class Elements
6
+ class Field < Element
7
+ permitted_children :none
8
+
9
+ # @api private
10
+ attr_reader :name
11
+
12
+ attribute :label, Types::String
13
+ attribute :hint, Types::String
14
+ attribute :placeholder, Types::String
15
+ attribute :inline, Types::Bool
16
+ attribute :validation, Types::Validation
17
+
18
+ # @api private
19
+ attr_reader :predicates
20
+
21
+ # @api private
22
+ def initialize(*args, attributes, children, input, errors)
23
+ super
24
+
25
+ @name = Types::ElementName.(args.first)
26
+ @input = input[@name] if input
27
+ @errors = errors[@name].to_a
28
+ end
29
+
30
+ # Converts the field into an abstract syntax tree.
31
+ #
32
+ # It takes the following format:
33
+ #
34
+ # ```
35
+ # [:field, [params]]
36
+ # ```
37
+ #
38
+ # With the following parameters:
39
+ #
40
+ # 1. Field name
41
+ # 2. Custom form element type (or `:field` otherwise)
42
+ # 3. Associated form input data
43
+ # 4. Error messages
44
+ # 5. Form element attributes
45
+ #
46
+ # @see Formalist::Element::Attributes#to_ast "Form element attributes" structure
47
+ #
48
+ # @example "email" field
49
+ # field.to_ast
50
+ # # => [:field, [
51
+ # :email,
52
+ # :field,
53
+ # "jane@doe.org",
54
+ # [],
55
+ # [:object, []],
56
+ # ]]
57
+ #
58
+ # @return [Array] the field as an abstract syntax tree.
59
+ def to_ast
60
+ # errors looks like this
61
+ # {:field_name => [["pages is missing", "another error message"], nil]}
62
+
63
+ [:field, [
64
+ name,
65
+ type,
66
+ input,
67
+ errors,
68
+ Element::Attributes.new(attributes).to_ast,
69
+ ]]
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,50 @@
1
+ require "formalist/element"
2
+ require "formalist/types"
3
+
4
+ module Formalist
5
+ class Elements
6
+ class Group < Element
7
+ permitted_children :attr, :compound_field, :field, :many
8
+
9
+ attribute :label, Types::String
10
+
11
+ def initialize(*args, attributes, children, input, errors)
12
+ super
13
+ @children = children.map { |definition| definition.(input, errors) }
14
+ end
15
+
16
+ # Converts the group into an abstract syntax tree.
17
+ #
18
+ # It takes the following format:
19
+ #
20
+ # ```
21
+ # [:group, [params]]
22
+ # ```
23
+ #
24
+ # With the following parameters:
25
+ #
26
+ # 1. Custom form element type (or `:group` otherwise)
27
+ # 2. Form element attributes
28
+ # 3. Child form elements
29
+ #
30
+ # @see Formalist::Element::Attributes#to_ast "Form element attributes" structure
31
+ #
32
+ # @example
33
+ # group.to_ast
34
+ # # => [:group, [
35
+ # :group,
36
+ # [:object, []],
37
+ # [...child elements...]
38
+ # ]]
39
+ #
40
+ # @return [Array] the group as an abstract syntax tree.
41
+ def to_ast
42
+ [:group, [
43
+ type,
44
+ Element::Attributes.new(attributes).to_ast,
45
+ children.map(&:to_ast),
46
+ ]]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,125 @@
1
+ require "formalist/element"
2
+ require "formalist/types"
3
+
4
+ module Formalist
5
+ class Elements
6
+ class Many < Element
7
+ permitted_children :attr, :compound_field, :group, :field
8
+
9
+ # @api private
10
+ attr_reader :name
11
+
12
+ attribute :label, Types::String
13
+ attribute :action_label, Types::String
14
+ attribute :placeholder, Types::String
15
+ attribute :validation, Types::Validation
16
+
17
+ # @api private
18
+ attr_reader :child_template
19
+
20
+ # @api private
21
+ def initialize(*args, attributes, children, input, errors)
22
+ super
23
+
24
+ @name = Types::ElementName.(args.first)
25
+ @input = input.fetch(name, [])
26
+ @errors = errors[@name]
27
+ @child_template = build_child_template(children)
28
+ @children = build_children(children)
29
+ end
30
+
31
+ # Until we can put defaults on `Types::Bool`, supply them here
32
+ # @api private
33
+ def attributes
34
+ {
35
+ allow_create: true,
36
+ allow_update: true,
37
+ allow_destroy: true,
38
+ allow_reorder: true,
39
+ }.merge(super)
40
+ end
41
+
42
+ # Converts a collection of "many" repeating elements into an abstract
43
+ # syntax tree.
44
+ #
45
+ # It takes the following format:
46
+ #
47
+ # ```
48
+ # [:many, [params]]
49
+ # ```
50
+ #
51
+ # With the following parameters:
52
+ #
53
+ # 1. Collection name
54
+ # 2. Custom form element type (or `:many` otherwise)
55
+ # 3. Collection-level error messages
56
+ # 4. Form element attributes
57
+ # 5. Child element "template" (i.e. the form elements comprising a
58
+ # single entry in the collection of "many" elements, without any user
59
+ # data associated)
60
+ # 6. Child elements, one for each of the entries in the input data (or
61
+ # none, if there is no or empty input data)
62
+ #
63
+ # @see Formalist::Element::Attributes#to_ast "Form element attributes" structure
64
+ #
65
+ # @example "locations" collection
66
+ # many.to_ast
67
+ # # => [:many, [
68
+ # :locations,
69
+ # :many,
70
+ # ["locations size cannot be less than 3"],
71
+ # [:object, [
72
+ # [:allow_create, [:value, [true]]],
73
+ # [:allow_update, [:value, [true]]],
74
+ # [:allow_destroy, [:value, [true]]],
75
+ # [:allow_reorder, [:value, [true]]]
76
+ # ]],
77
+ # [
78
+ # [:field, [:name, :field, nil, [], [], [:object, []]]],
79
+ # [:field, [:address, :field, nil, [], [], [:object, []]]]
80
+ # ],
81
+ # [
82
+ # [
83
+ # [:field, [:name, :field, "Icelab Canberra", [], [], [:object, []]]],
84
+ # [:field, [:address, :field, "Canberra, ACT, Australia", [], [], [:object, []]]]
85
+ # ],
86
+ # [
87
+ # [:field, [:name, :field, "Icelab Melbourne", [], [], [:object, []]]],
88
+ # [:field, [:address, :field, "Melbourne, VIC, Australia", [], [], [:object, []]]]
89
+ # ]
90
+ # ]
91
+ # ]]
92
+ #
93
+ # @return [Array] the collection as an abstract syntax tree.
94
+ def to_ast
95
+ local_errors = errors.is_a?(Array) ? errors : []
96
+
97
+ [:many, [
98
+ name,
99
+ type,
100
+ local_errors,
101
+ Element::Attributes.new(attributes).to_ast,
102
+ child_template.map(&:to_ast),
103
+ children.map { |el_list| el_list.map(&:to_ast) },
104
+ ]]
105
+ end
106
+
107
+ private
108
+
109
+ def build_child_template(definitions)
110
+ definitions.map { |el| el.({}, {})}
111
+ end
112
+
113
+ def build_children(definitions)
114
+ # Child errors look like this: {0=>{:summary=>["must be filled"]}
115
+ child_errors = errors.is_a?(Hash) ? errors : {}
116
+
117
+ input.each_with_index.map { |child_input, index|
118
+ errors = child_errors.fetch(index, {})
119
+
120
+ definitions.map { |el| el.(child_input, errors) }
121
+ }
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,58 @@
1
+ require "formalist/element"
2
+ require "formalist/types"
3
+
4
+ module Formalist
5
+ class Elements
6
+ class Section < Element
7
+ permitted_children :all
8
+
9
+ # @api private
10
+ attr_reader :name
11
+
12
+ attribute :label, Types::String
13
+
14
+ def initialize(*args, attributes, children, input, errors)
15
+ super
16
+
17
+ @name = Types::ElementName.(args.first)
18
+ @children = children.map { |definition| definition.(input, errors) }
19
+ end
20
+
21
+ # Converts the section into an abstract syntax tree.
22
+ #
23
+ # It takes the following format:
24
+ #
25
+ # ```
26
+ # [:section, [params]]
27
+ # ```
28
+ #
29
+ # With the following parameters:
30
+ #
31
+ # 1. Section name
32
+ # 2. Custom form element type (or `:section` otherwise)
33
+ # 3. Form element attributes
34
+ # 4. Child form elements
35
+ #
36
+ # @see Formalist::Element::Attributes#to_ast "Form element attributes" structure
37
+ #
38
+ # @example "content" section
39
+ # section.to_ast
40
+ # # => [:section, [
41
+ # :content,
42
+ # :section,
43
+ # [:object, []],
44
+ # [...child elements...]
45
+ # ]]
46
+ #
47
+ # @return [Array] the section as an abstract syntax tree.
48
+ def to_ast
49
+ [:section, [
50
+ name,
51
+ type,
52
+ Element::Attributes.new(attributes).to_ast,
53
+ children.map(&:to_ast),
54
+ ]]
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,20 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Elements
7
+ class CheckBox < Field
8
+ attribute :question_text, Types::String
9
+
10
+ def initialize(*)
11
+ super
12
+
13
+ # Ensure value is a boolean (also: default to false for nil values)
14
+ @input = !!@input
15
+ end
16
+ end
17
+
18
+ register :check_box, CheckBox
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Elements
7
+ class DateField < Field
8
+ end
9
+
10
+ register :date_field, DateField
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Elements
7
+ class DateTimeField < Field
8
+ end
9
+
10
+ register :date_time_field, DateTimeField
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+
4
+ module Formalist
5
+ class Elements
6
+ class HiddenField < Field
7
+ end
8
+
9
+ register :hidden_field, HiddenField
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Elements
7
+ class MultiSelectionField < Field
8
+ attribute :options, Types::SelectionsList
9
+ attribute :selector_label, Types::String
10
+ attribute :render_option_as, Types::String
11
+ attribute :render_selection_as, Types::String
12
+ end
13
+
14
+ register :multi_selection_field, MultiSelectionField
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Elements
7
+ class NumberField < Field
8
+ Number = Types::Int | Types::Float
9
+
10
+ attribute :step, Number
11
+ attribute :min, Number
12
+ attribute :max, Number
13
+ end
14
+
15
+ register :number_field, NumberField
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Elements
7
+ class RadioButtons < Field
8
+ attribute :options, Types::OptionsList
9
+ end
10
+
11
+ register :radio_buttons, RadioButtons
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Elements
7
+ class SelectBox < Field
8
+ attribute :options, Types::OptionsList
9
+ end
10
+
11
+ register :select_box, SelectBox
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Elements
7
+ class SelectionField < Field
8
+ attribute :options, Types::SelectionsList
9
+ attribute :selector_label, Types::String
10
+ attribute :render_option_as, Types::String
11
+ attribute :render_selection_as, Types::String
12
+ end
13
+
14
+ register :selection_field, SelectionField
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Elements
7
+ class TextArea < Field
8
+ attribute :text_size, Types::String.enum("xsmall", "small", "normal", "large", "xlarge"), default: "normal"
9
+ attribute :box_size, Types::String.enum("single", "small", "normal", "large", "xlarge"), default: "normal"
10
+ attribute :code, Types::Bool
11
+ end
12
+
13
+ register :text_area, TextArea
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ require "formalist/element"
2
+ require "formalist/elements"
3
+ require "formalist/types"
4
+
5
+ module Formalist
6
+ class Elements
7
+ class TextField < Field
8
+ attribute :password, Types::Bool
9
+ attribute :code, Types::Bool
10
+ end
11
+
12
+ register :text_field, TextField
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require "formalist/elements/standard/check_box"
2
+ require "formalist/elements/standard/date_field"
3
+ require "formalist/elements/standard/date_time_field"
4
+ require "formalist/elements/standard/hidden_field"
5
+ require "formalist/elements/standard/multi_selection_field"
6
+ require "formalist/elements/standard/number_field"
7
+ require "formalist/elements/standard/radio_buttons"
8
+ require "formalist/elements/standard/select_box"
9
+ require "formalist/elements/standard/selection_field"
10
+ require "formalist/elements/standard/text_area"
11
+ require "formalist/elements/standard/text_field"
@@ -0,0 +1,20 @@
1
+ require "dry-container"
2
+ require "formalist/elements/attr"
3
+ require "formalist/elements/compound_field"
4
+ require "formalist/elements/field"
5
+ require "formalist/elements/group"
6
+ require "formalist/elements/many"
7
+ require "formalist/elements/section"
8
+
9
+ module Formalist
10
+ class Elements
11
+ extend Dry::Container::Mixin
12
+
13
+ register :attr, Attr
14
+ register :compound_field, CompoundField
15
+ register :field, Field
16
+ register :group, Group
17
+ register :many, Many
18
+ register :section, Section
19
+ end
20
+ end
@@ -1,14 +1,68 @@
1
+ require "formalist/element/definition"
2
+
1
3
  module Formalist
2
4
  class Form
3
- # @api private
4
5
  class DefinitionContext
5
- include Definition
6
+ DuplicateDefinitionError = Class.new(StandardError)
6
7
 
7
8
  attr_reader :elements
9
+ attr_reader :container
10
+ attr_reader :permissions
8
11
 
9
- def initialize(&block)
12
+ def initialize(options = {})
10
13
  @elements = []
11
- yield(self)
14
+ @container = options.fetch(:container)
15
+ @permissions = options.fetch(:permissions)
16
+ end
17
+
18
+ def with(options = {})
19
+ %i[container permissions].each do |attr|
20
+ options[attr] ||= send(attr)
21
+ end
22
+
23
+ self.class.new(options)
24
+ end
25
+
26
+ def call(&block)
27
+ instance_eval(&block) if block
28
+ self
29
+ end
30
+
31
+ def dep(name)
32
+ Element::Definition::Deferred.new(name)
33
+ end
34
+
35
+ def method_missing(name, *args, &block)
36
+ return add_element(name, *args, &block) if element_type_exists?(name)
37
+ super
38
+ end
39
+
40
+ def respond_to_missing?(name)
41
+ element_type_exists?(name)
42
+ end
43
+
44
+ private
45
+
46
+ def element_type_exists?(type)
47
+ container.key?(type)
48
+ end
49
+
50
+ def add_element(element_type, *args, &block)
51
+ type = container[element_type]
52
+ raise ArgumentError, "element +#{element_type}+ is not permitted in this context" unless permissions.permitted?(type)
53
+
54
+ # Work with top-level args and a trailing attributes hash
55
+ args = args.dup
56
+ attributes = args.last.is_a?(Hash) ? args.pop : {}
57
+
58
+ children = with(permissions: type.permitted_children).call(&block).elements
59
+ definition = Element::Definition.new(type, *args, attributes, children)
60
+
61
+ if elements.any? { |el| el == definition }
62
+ raise DuplicateDefinitionError, "element +#{element_type} #{args.map(&:inspect).join(', ')}+ is already defined in this context"
63
+ end
64
+
65
+ elements << definition
12
66
  end
13
67
  end
14
68
  end
@@ -1,5 +1,3 @@
1
- require "formalist/form/validated_result"
2
-
3
1
  module Formalist
4
2
  class Form
5
3
  class Result
@@ -7,39 +5,19 @@ module Formalist
7
5
  attr_reader :input
8
6
 
9
7
  # @api private
10
- attr_reader :schema
8
+ attr_reader :messages
11
9
 
12
10
  # @api private
13
11
  attr_reader :elements
14
12
 
15
- # @api public
16
- attr_reader :validation
17
-
18
- def initialize(schema, elements, input)
13
+ def initialize(input, messages, elements)
19
14
  @input = input
20
- @schema = schema
21
- @elements = elements
22
- @validation = schema.(input)
23
- end
24
-
25
- def output
26
- validation.output
27
- end
28
-
29
- def success?
30
- true
31
- end
32
-
33
- def messages
34
- {}
15
+ @messages = messages
16
+ @elements = elements.map { |el| el.(input, messages) }
35
17
  end
36
18
 
37
19
  def to_ast
38
- elements.map { |el| el.(output, schema.rules.map(&:to_ary), messages).to_ast }
39
- end
40
-
41
- def validate
42
- ValidatedResult.new(self)
20
+ elements.map(&:to_ast)
43
21
  end
44
22
  end
45
23
  end