reform 2.2.4 → 2.3.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 +5 -5
- data/.gitignore +5 -1
- data/.travis.yml +11 -6
- data/Appraisals +8 -0
- data/CHANGES.md +57 -4
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +2 -16
- data/ISSUE_TEMPLATE.md +25 -0
- data/LICENSE.txt +1 -1
- data/README.md +5 -7
- data/Rakefile +16 -9
- data/gemfiles/0.13.0.gemfile +8 -0
- data/gemfiles/1.5.0.gemfile +9 -0
- data/lib/reform.rb +1 -0
- data/lib/reform/contract.rb +7 -17
- data/lib/reform/contract/custom_error.rb +41 -0
- data/lib/reform/contract/validate.rb +53 -23
- data/lib/reform/errors.rb +61 -0
- data/lib/reform/form.rb +36 -10
- data/lib/reform/form/call.rb +1 -1
- data/lib/reform/form/composition.rb +2 -2
- data/lib/reform/form/dry.rb +10 -58
- data/lib/reform/form/dry/input_hash.rb +37 -0
- data/lib/reform/form/dry/new_api.rb +45 -0
- data/lib/reform/form/dry/old_api.rb +61 -0
- data/lib/reform/form/populator.rb +11 -27
- data/lib/reform/form/prepopulate.rb +4 -3
- data/lib/reform/form/validate.rb +28 -13
- data/lib/reform/result.rb +90 -0
- data/lib/reform/validation.rb +19 -11
- data/lib/reform/validation/groups.rb +12 -27
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +14 -13
- data/test/benchmarking.rb +39 -6
- data/test/call_new_api.rb +23 -0
- data/test/call_old_api.rb +23 -0
- data/test/changed_test.rb +14 -14
- data/test/coercion_test.rb +57 -25
- data/test/composition_new_api.rb +186 -0
- data/test/composition_old_api.rb +184 -0
- data/test/contract/custom_error_test.rb +55 -0
- data/test/contract_new_api.rb +77 -0
- data/test/contract_old_api.rb +77 -0
- data/test/default_test.rb +4 -4
- data/test/deserialize_test.rb +17 -20
- data/test/errors_new_api.rb +225 -0
- data/test/errors_old_api.rb +230 -0
- data/test/feature_test.rb +10 -12
- data/test/fixtures/dry_error_messages.yml +73 -23
- data/test/fixtures/dry_new_api_error_messages.yml +104 -0
- data/test/form_new_api.rb +57 -0
- data/test/{form_test.rb → form_old_api.rb} +8 -8
- data/test/form_option_new_api.rb +24 -0
- data/test/{form_option_test.rb → form_option_old_api.rb} +5 -5
- data/test/from_test.rb +18 -22
- data/test/inherit_new_api.rb +105 -0
- data/test/inherit_old_api.rb +105 -0
- data/test/{module_test.rb → module_new_api.rb} +26 -31
- data/test/module_old_api.rb +146 -0
- data/test/parse_option_test.rb +40 -0
- data/test/parse_pipeline_test.rb +4 -4
- data/test/populate_new_api.rb +304 -0
- data/test/populate_old_api.rb +304 -0
- data/test/populator_skip_test.rb +11 -11
- data/test/prepopulator_test.rb +23 -24
- data/test/read_only_test.rb +12 -1
- data/test/readable_test.rb +9 -9
- data/test/reform_new_api.rb +204 -0
- data/test/{reform_test.rb → reform_old_api.rb} +44 -65
- data/test/save_new_api.rb +101 -0
- data/test/save_old_api.rb +101 -0
- data/test/setup_test.rb +17 -17
- data/test/skip_if_new_api.rb +85 -0
- data/test/skip_if_old_api.rb +92 -0
- data/test/skip_setter_and_getter_test.rb +9 -10
- data/test/test_helper.rb +25 -14
- data/test/validate_new_api.rb +453 -0
- data/test/{validate_test.rb → validate_old_api.rb} +121 -131
- data/test/validation/dry_validation_new_api.rb +835 -0
- data/test/validation/dry_validation_old_api.rb +772 -0
- data/test/validation/result_test.rb +77 -0
- data/test/validation_library_provided_test.rb +16 -0
- data/test/virtual_test.rb +47 -7
- data/test/writeable_test.rb +38 -9
- metadata +111 -56
- data/gemfiles/Gemfile.disposable-0.3 +0 -6
- data/lib/reform/contract/errors.rb +0 -43
- data/lib/reform/form/mongoid.rb +0 -37
- data/lib/reform/form/orm.rb +0 -26
- data/lib/reform/mongoid.rb +0 -4
- data/test/call_test.rb +0 -23
- data/test/composition_test.rb +0 -149
- data/test/contract_test.rb +0 -77
- data/test/deprecation_test.rb +0 -27
- data/test/errors_test.rb +0 -165
- data/test/inherit_test.rb +0 -119
- data/test/populate_test.rb +0 -270
- data/test/readonly_test.rb +0 -14
- data/test/save_test.rb +0 -89
- data/test/skip_if_test.rb +0 -74
- data/test/validation/dry_test.rb +0 -60
- data/test/validation/dry_validation_test.rb +0 -352
- data/test/validation/errors.yml +0 -4
@@ -1,14 +1,15 @@
|
|
1
1
|
# prepopulate!(options)
|
2
2
|
# prepopulator: ->(model, user_options)
|
3
3
|
module Reform::Form::Prepopulate
|
4
|
-
def prepopulate!(options={})
|
4
|
+
def prepopulate!(options = {})
|
5
5
|
prepopulate_local!(options) # call #prepopulate! on local properties.
|
6
6
|
prepopulate_nested!(options) # THEN call #prepopulate! on nested forms.
|
7
7
|
|
8
8
|
self
|
9
9
|
end
|
10
10
|
|
11
|
-
private
|
11
|
+
private
|
12
|
+
|
12
13
|
def prepopulate_local!(options)
|
13
14
|
schema.each do |dfn|
|
14
15
|
next unless block = dfn[:prepopulator]
|
@@ -21,4 +22,4 @@ private
|
|
21
22
|
Disposable::Twin::PropertyProcessor.new(dfn, self).() { |form| form.prepopulate!(options) }
|
22
23
|
end
|
23
24
|
end
|
24
|
-
end
|
25
|
+
end
|
data/lib/reform/form/validate.rb
CHANGED
@@ -5,56 +5,71 @@ module Reform::Form::Validate
|
|
5
5
|
include Uber::Callable
|
6
6
|
|
7
7
|
def call(form, options)
|
8
|
-
params = options[:input]
|
9
8
|
# TODO: Schema should provide property names as plain list.
|
10
|
-
|
9
|
+
# ensure param keys are strings.
|
10
|
+
params = options[:input].each_with_object({}) { |(k, v), hash|
|
11
|
+
hash[k.to_s] = v
|
12
|
+
}
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
+
# return false if any property inputs are populated.
|
15
|
+
options[:binding][:nested].definitions.each do |definition|
|
16
|
+
value = params[definition.name.to_s]
|
17
|
+
return false if (!value.nil? && value != '')
|
18
|
+
end
|
19
|
+
|
20
|
+
true # skip this property
|
14
21
|
end
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
18
|
-
|
19
25
|
def validate(params)
|
20
26
|
# allow an external deserializer.
|
27
|
+
@input_params = params # we want to store these for access via dry later
|
21
28
|
block_given? ? yield(params) : deserialize(params)
|
22
29
|
|
23
30
|
super() # run the actual validation on self.
|
24
31
|
end
|
32
|
+
attr_reader :input_params # make the raw input params public
|
25
33
|
|
26
34
|
def deserialize(params)
|
27
35
|
params = deserialize!(params)
|
28
36
|
deserializer.new(self).from_hash(params)
|
29
37
|
end
|
30
38
|
|
31
|
-
private
|
39
|
+
private
|
40
|
+
|
32
41
|
# Meant to return params processable by the representer. This is the hook for munching date fields, etc.
|
33
42
|
def deserialize!(params)
|
34
43
|
# NOTE: it is completely up to the form user how they want to deserialize (e.g. using an external JSON-API representer).
|
35
|
-
|
36
|
-
|
44
|
+
# use the deserializer as an external instance to operate on the Twin API,
|
45
|
+
# e.g. adding new items in collections using #<< etc.
|
37
46
|
# DISCUSS: using self here will call the form's setters like title= which might be overridden.
|
38
47
|
params
|
39
48
|
end
|
40
49
|
|
41
50
|
# Default deserializer for hash.
|
42
51
|
# This is input-specific, e.g. Hash, JSON, or XML.
|
43
|
-
def deserializer(source=self.class, options={}) # called on top-level, only, for now.
|
44
|
-
deserializer = Disposable::Rescheme.from(
|
52
|
+
def deserializer!(source = self.class, options = {}) # called on top-level, only, for now.
|
53
|
+
deserializer = Disposable::Rescheme.from(
|
54
|
+
source,
|
45
55
|
{
|
46
56
|
include: [Representable::Hash::AllowSymbols, Representable::Hash],
|
47
57
|
superclass: Representable::Decorator,
|
48
|
-
definitions_from:
|
58
|
+
definitions_from: ->(inline) { inline.definitions },
|
49
59
|
options_from: :deserializer,
|
50
|
-
exclude_options: [
|
60
|
+
exclude_options: %i[default populator] # Reform must not copy Disposable/Reform-only options that might confuse representable.
|
51
61
|
}.merge(options)
|
52
62
|
)
|
53
63
|
|
54
64
|
deserializer
|
55
65
|
end
|
56
66
|
|
67
|
+
def deserializer(*args)
|
68
|
+
# DISCUSS: should we simply delegate to class and sort out memoizing there?
|
69
|
+
self.class.deserializer_class || self.class.deserializer_class = deserializer!(*args)
|
70
|
+
end
|
57
71
|
|
58
|
-
|
72
|
+
def self.included(includer)
|
73
|
+
includer.singleton_class.send :attr_accessor, :deserializer_class
|
59
74
|
end
|
60
75
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Reform
|
2
|
+
class Contract < Disposable::Twin
|
3
|
+
# Collects all native results of a form of all groups and provides
|
4
|
+
# a unified API: #success?, #errors, #messages, #hints.
|
5
|
+
# #success? returns validity of the branch.
|
6
|
+
class Result
|
7
|
+
def initialize(results, nested_results = []) # DISCUSS: do we like this?
|
8
|
+
@results = results # native Result objects, e.g. `#<Dry::Validation::Result output={:title=>"Fallout", :composer=>nil} errors={}>`
|
9
|
+
@failure = (results + nested_results).find(&:failure?) # TODO: test nested.
|
10
|
+
end
|
11
|
+
|
12
|
+
def failure?; @failure end
|
13
|
+
|
14
|
+
def success?; !failure? end
|
15
|
+
|
16
|
+
def errors(*args); filter_for(:errors, *args) end
|
17
|
+
|
18
|
+
def messages(*args); filter_for(:messages, *args) end
|
19
|
+
|
20
|
+
def hints(*args); filter_for(:hints, *args) end
|
21
|
+
|
22
|
+
def add_error(key, error_text)
|
23
|
+
CustomError.new(key, error_text, @results)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_results
|
27
|
+
@results
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# this doesn't do nested errors (e.g. )
|
33
|
+
def filter_for(method, *args)
|
34
|
+
@results.collect { |r| r.public_send(method, *args).to_h }
|
35
|
+
.inject({}) { |hah, err| hah.merge(err) { |key, old_v, new_v| (new_v.is_a?(Array) ? (old_v |= new_v) : old_v.merge(new_v)) } }
|
36
|
+
.find_all { |k, v| # filter :nested=>{:something=>["too nested!"]} #DISCUSS: do we want that here?
|
37
|
+
if v.is_a?(Hash)
|
38
|
+
nested_errors = v.select { |attr_key, val| attr_key.is_a?(Integer) && val.is_a?(Array) && val.any? }
|
39
|
+
v = nested_errors.to_a if nested_errors.any?
|
40
|
+
end
|
41
|
+
v.is_a?(Array)
|
42
|
+
}.to_h
|
43
|
+
end
|
44
|
+
|
45
|
+
# Note: this class will be redundant in Reform 3, where the public API
|
46
|
+
# allows/enforces to pass options to #errors (e.g. errors(locale: "br"))
|
47
|
+
# which means we don't have to "lazy-handle" that with "pointers".
|
48
|
+
# :private:
|
49
|
+
class Pointer
|
50
|
+
extend Forwardable
|
51
|
+
|
52
|
+
def initialize(result, path)
|
53
|
+
@result, @path = result, path
|
54
|
+
end
|
55
|
+
|
56
|
+
def_delegators :@result, :success?, :failure?
|
57
|
+
|
58
|
+
def errors(*args); traverse_for(:errors, *args) end
|
59
|
+
|
60
|
+
def messages(*args); traverse_for(:messages, *args) end
|
61
|
+
|
62
|
+
def hints(*args); traverse_for(:hints, *args) end
|
63
|
+
|
64
|
+
def advance(*path)
|
65
|
+
path = @path + path.compact # remove index if nil.
|
66
|
+
traverse = traverse(@result.errors, path)
|
67
|
+
# when returns {} is because no errors are found
|
68
|
+
# when returns a String is because an error has been found on the main key not in the nested one.
|
69
|
+
# Collection with custom rule will return a String here and does not need to be considered
|
70
|
+
# as a nested error.
|
71
|
+
# when return an Array without an index is same as String but it's a property with a custom rule.
|
72
|
+
# Check test/validation/dry_validation_test.rb:685
|
73
|
+
return if traverse == {} || traverse.is_a?(String) || (traverse.is_a?(Array) && path.compact.size == 1)
|
74
|
+
|
75
|
+
Pointer.new(@result, path)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def traverse(hash, path)
|
81
|
+
path.inject(hash) { |errs, segment| errs[segment] || {} } # FIXME. test if all segments present.
|
82
|
+
end
|
83
|
+
|
84
|
+
def traverse_for(method, *args)
|
85
|
+
traverse(@result.public_send(method, *args), @path) # TODO: return [] if nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/reform/validation.rb
CHANGED
@@ -3,28 +3,34 @@
|
|
3
3
|
module Reform::Validation
|
4
4
|
module ClassMethods
|
5
5
|
def validation_groups
|
6
|
-
@groups ||= Groups.new(validation_group_class)
|
6
|
+
@groups ||= Groups.new(validation_group_class)
|
7
7
|
end
|
8
8
|
|
9
9
|
# DSL.
|
10
|
-
def validation(name
|
11
|
-
|
10
|
+
def validation(name = nil, options = {}, &block)
|
11
|
+
options = deprecate_validation_positional_args(name, options)
|
12
|
+
name = options[:name] # TODO: remove in favor of kw args in 3.0.
|
12
13
|
|
14
|
+
heritage.record(:validation, options, &block)
|
13
15
|
group = validation_groups.add(name, options)
|
14
16
|
|
15
17
|
group.instance_exec(&block)
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
20
|
+
def deprecate_validation_positional_args(name, options)
|
21
|
+
if name.is_a?(Symbol)
|
22
|
+
warn "[Reform] Form::validation API is now: validation(name: :default, if:nil, schema:Schema). Please use keyword arguments instead of positional arguments."
|
23
|
+
return {name: name}.merge(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
return {name: :default}.merge(options) if name.nil?
|
21
27
|
|
22
|
-
|
23
|
-
validation(:default, inherit: true) { validate *args, &block }
|
28
|
+
{name: :default}.merge(name)
|
24
29
|
end
|
25
30
|
|
26
|
-
def
|
27
|
-
|
31
|
+
def validation_group_class
|
32
|
+
raise NoValidationLibraryError, "no validation library loaded. Please include a " +
|
33
|
+
"validation library such as Reform::Form::Dry"
|
28
34
|
end
|
29
35
|
end
|
30
36
|
|
@@ -33,8 +39,10 @@ module Reform::Validation
|
|
33
39
|
end
|
34
40
|
|
35
41
|
def valid?
|
36
|
-
|
42
|
+
validate({})
|
37
43
|
end
|
44
|
+
|
45
|
+
NoValidationLibraryError = Class.new(RuntimeError)
|
38
46
|
end
|
39
47
|
|
40
48
|
require "reform/validation/groups"
|
@@ -19,11 +19,11 @@ module Reform::Validation
|
|
19
19
|
|
20
20
|
i = index_for(options)
|
21
21
|
|
22
|
-
self.insert(i, [name, group = @group_class.new, options]) # Group.new
|
22
|
+
self.insert(i, [name, group = @group_class.new(options), options]) # Group.new
|
23
23
|
group
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
private
|
27
27
|
|
28
28
|
def index_for(options)
|
29
29
|
return find_index { |el| el.first == options[:after] } + 1 if options[:after]
|
@@ -31,43 +31,28 @@ module Reform::Validation
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def [](name)
|
34
|
-
cfg = find { |
|
34
|
+
cfg = find { |c| c.first == name }
|
35
35
|
return unless cfg
|
36
36
|
cfg[1]
|
37
37
|
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
class Result # DISCUSS: could be in Groups.
|
43
|
-
def initialize(groups)
|
44
|
-
@groups = groups
|
45
|
-
end
|
46
|
-
|
47
|
-
def call(fields, errors, form)
|
48
|
-
result = true
|
39
|
+
# Runs all validations groups according to their rules and returns all Result objects.
|
40
|
+
class Validate
|
41
|
+
def self.call(groups, form)
|
49
42
|
results = {}
|
50
43
|
|
51
|
-
|
52
|
-
|
53
|
-
depends_on = options[:if]
|
44
|
+
groups.collect do |(name, group, options)|
|
45
|
+
next unless evaluate?(options[:if], results, form)
|
54
46
|
|
55
|
-
|
56
|
-
# puts "evaluating #{group.instance_variable_get(:@validator).instance_variable_get(:@checker).inspect}"
|
57
|
-
results[name] = group.(fields, errors, form).empty? # validate.
|
58
|
-
end
|
59
|
-
|
60
|
-
result &= errors.empty?
|
47
|
+
results[name] = group.(form) # run validation for group. store and collect <Result>.
|
61
48
|
end
|
62
|
-
|
63
|
-
result
|
64
49
|
end
|
65
50
|
|
66
|
-
def
|
51
|
+
def self.evaluate?(depends_on, results, form)
|
67
52
|
return true if depends_on.nil?
|
68
|
-
return results[depends_on] if depends_on.is_a?(Symbol)
|
53
|
+
return !results[depends_on].nil? && results[depends_on].success? if depends_on.is_a?(Symbol)
|
69
54
|
form.instance_exec(results, &depends_on)
|
70
55
|
end
|
71
56
|
end
|
72
57
|
end
|
73
|
-
end
|
58
|
+
end
|
data/lib/reform/version.rb
CHANGED
data/reform.gemspec
CHANGED
@@ -1,29 +1,30 @@
|
|
1
|
-
lib = File.expand_path(
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require
|
3
|
+
require "reform/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "reform"
|
7
7
|
spec.version = Reform::VERSION
|
8
|
-
spec.authors = ["Nick Sutterer", "
|
9
|
-
spec.email = ["apotonick@gmail.com", "
|
10
|
-
spec.description =
|
11
|
-
spec.summary =
|
12
|
-
spec.homepage = "https://github.com/
|
8
|
+
spec.authors = ["Nick Sutterer", "Fran Worley"]
|
9
|
+
spec.email = ["apotonick@gmail.com", "frances@safetytoolbox.co.uk"]
|
10
|
+
spec.description = "Form object decoupled from models."
|
11
|
+
spec.summary = "Form object decoupled from models with validation, population and presentation."
|
12
|
+
spec.homepage = "https://github.com/trailblazer/reform"
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
15
15
|
spec.files = `git ls-files`.split($/)
|
16
|
-
spec.executables = spec.files.grep(%r
|
16
|
+
spec.executables = spec.files.grep(%r(^bin/)) { |f| File.basename(f) }
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.add_dependency "disposable",
|
21
|
-
spec.add_dependency "representable",
|
20
|
+
spec.add_dependency "disposable", ">= 0.4.2", "< 0.5.0"
|
21
|
+
spec.add_dependency "representable", ">= 2.4.0", "< 3.1.0"
|
22
|
+
spec.add_dependency "uber", "< 0.2.0"
|
22
23
|
|
23
24
|
spec.add_development_dependency "bundler"
|
24
|
-
spec.add_development_dependency "rake"
|
25
25
|
spec.add_development_dependency "minitest"
|
26
|
-
spec.add_development_dependency "
|
26
|
+
spec.add_development_dependency "minitest-line"
|
27
|
+
spec.add_development_dependency "pry-byebug"
|
27
28
|
spec.add_development_dependency "multi_json"
|
28
|
-
spec.add_development_dependency "
|
29
|
+
spec.add_development_dependency "rake"
|
29
30
|
end
|
data/test/benchmarking.rb
CHANGED
@@ -1,15 +1,48 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "reform"
|
2
|
+
require "benchmark/ips"
|
3
|
+
require "reform/form/dry"
|
4
4
|
|
5
5
|
class BandForm < Reform::Form
|
6
|
-
|
6
|
+
feature Reform::Form::Dry
|
7
|
+
property :name #, validates: {presence: true}
|
8
|
+
collection :songs do
|
9
|
+
property :title #, validates: {presence: true}
|
10
|
+
end
|
11
|
+
end
|
7
12
|
|
13
|
+
class OptimizedBandForm < Reform::Form
|
14
|
+
feature Reform::Form::Dry
|
15
|
+
property :name #, validates: {presence: true}
|
8
16
|
collection :songs do
|
9
|
-
property :title
|
17
|
+
property :title #, validates: {presence: true}
|
18
|
+
|
19
|
+
def deserializer(*args)
|
20
|
+
# DISCUSS: should we simply delegate to class and sort out memoizing there?
|
21
|
+
self.class.deserializer_class || self.class.deserializer_class = deserializer!(*args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def deserializer(*args)
|
26
|
+
# DISCUSS: should we simply delegate to class and sort out memoizing there?
|
27
|
+
self.class.deserializer_class || self.class.deserializer_class = deserializer!(*args)
|
10
28
|
end
|
11
29
|
end
|
12
30
|
|
31
|
+
songs = 10.times.collect { OpenStruct.new(title: "Be Stag") }
|
32
|
+
band = OpenStruct.new(name: "Teenage Bottlerock", songs: songs)
|
33
|
+
|
34
|
+
unoptimized_form = BandForm.new(band)
|
35
|
+
optimized_form = OptimizedBandForm.new(band)
|
36
|
+
|
37
|
+
songs_params = songs_params = 10.times.collect { {title: "Commando"} }
|
38
|
+
|
39
|
+
Benchmark.ips do |x|
|
40
|
+
x.report("2.2") { BandForm.new(band).validate("name" => "Ramones", "songs" => songs_params) }
|
41
|
+
x.report("2.3") { OptimizedBandForm.new(band).validate("name" => "Ramones", "songs" => songs_params) }
|
42
|
+
end
|
43
|
+
|
44
|
+
exit
|
45
|
+
|
13
46
|
songs = 50.times.collect { OpenStruct.new(title: "Be Stag") }
|
14
47
|
band = OpenStruct.new(name: "Teenage Bottlerock", songs: songs)
|
15
48
|
|
@@ -23,4 +56,4 @@ time = Benchmark.measure do
|
|
23
56
|
end
|
24
57
|
end
|
25
58
|
|
26
|
-
puts time
|
59
|
+
puts time
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class CallTest < Minitest::Spec
|
4
|
+
Song = Struct.new(:title)
|
5
|
+
|
6
|
+
class SongForm < TestForm
|
7
|
+
property :title
|
8
|
+
|
9
|
+
validation do
|
10
|
+
params { required(:title).filled }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:form) { SongForm.new(Song.new) }
|
15
|
+
|
16
|
+
it { _(form.(title: "True North").success?).must_equal true }
|
17
|
+
it { _(form.(title: "True North").failure?).must_equal false }
|
18
|
+
it { _(form.(title: "").success?).must_equal false }
|
19
|
+
it { _(form.(title: "").failure?).must_equal true }
|
20
|
+
|
21
|
+
it { _(form.(title: "True North").errors.messages).must_equal({}) }
|
22
|
+
it { _(form.(title: "").errors.messages).must_equal(title: ["must be filled"]) }
|
23
|
+
end
|