formalist 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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