reform 2.2.4 → 2.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +5 -1
- data/.rubocop.yml +30 -0
- data/.rubocop_todo.yml +460 -0
- data/.travis.yml +11 -6
- data/Appraisals +8 -0
- data/CHANGES.md +54 -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 +18 -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 +46 -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 +15 -13
- data/test/benchmarking.rb +39 -6
- data/test/call_new_api.rb +23 -0
- data/test/{call_test.rb → call_old_api.rb} +4 -4
- data/test/changed_test.rb +8 -8
- data/test/coercion_test.rb +51 -19
- data/test/composition_new_api.rb +186 -0
- data/test/{composition_test.rb → composition_old_api.rb} +66 -31
- data/test/contract/custom_error_test.rb +55 -0
- data/test/contract_new_api.rb +77 -0
- data/test/{contract_test.rb → contract_old_api.rb} +13 -13
- data/test/default_test.rb +2 -2
- data/test/deserialize_test.rb +11 -14
- data/test/errors_new_api.rb +225 -0
- data/test/errors_old_api.rb +230 -0
- data/test/feature_test.rb +8 -10
- 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} +5 -5
- data/test/form_option_new_api.rb +24 -0
- data/test/{form_option_test.rb → form_option_old_api.rb} +4 -4
- data/test/from_test.rb +9 -13
- 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} +20 -25
- data/test/module_old_api.rb +146 -0
- data/test/parse_option_test.rb +40 -0
- data/test/parse_pipeline_test.rb +3 -3
- data/test/populate_new_api.rb +304 -0
- data/test/{populate_test.rb → populate_old_api.rb} +83 -49
- data/test/populator_skip_test.rb +9 -9
- data/test/prepopulator_test.rb +8 -9
- data/test/read_only_test.rb +12 -1
- data/test/readable_test.rb +7 -7
- data/test/reform_new_api.rb +204 -0
- data/test/{reform_test.rb → reform_old_api.rb} +30 -51
- data/test/save_new_api.rb +101 -0
- data/test/{save_test.rb → save_old_api.rb} +32 -20
- data/test/setup_test.rb +8 -8
- data/test/{skip_if_test.rb → skip_if_new_api.rb} +23 -12
- data/test/skip_if_old_api.rb +92 -0
- data/test/skip_setter_and_getter_test.rb +3 -4
- data/test/test_helper.rb +25 -14
- data/test/validate_new_api.rb +408 -0
- data/test/{validate_test.rb → validate_old_api.rb} +59 -69
- data/test/validation/dry_validation_new_api.rb +836 -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 +35 -6
- metadata +127 -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/deprecation_test.rb +0 -27
- data/test/errors_test.rb +0 -165
- data/test/inherit_test.rb +0 -119
- data/test/readonly_test.rb +0 -14
- 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,31 @@
|
|
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"
|
30
|
+
spec.add_development_dependency "rubocop"
|
29
31
|
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
|