reform 2.2.4 → 2.3.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +13 -7
- data/CHANGES.md +26 -4
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +1 -12
- data/ISSUE_TEMPLATE.md +25 -0
- data/LICENSE.txt +1 -1
- data/README.md +3 -3
- data/lib/reform.rb +1 -0
- data/lib/reform/contract.rb +1 -11
- data/lib/reform/contract/validate.rb +49 -23
- data/lib/reform/errors.rb +49 -0
- data/lib/reform/form.rb +20 -5
- data/lib/reform/form/dry.rb +57 -29
- data/lib/reform/form/populator.rb +2 -16
- data/lib/reform/form/prepopulate.rb +1 -1
- data/lib/reform/form/validate.rb +10 -2
- data/lib/reform/result.rb +63 -0
- data/lib/reform/validation.rb +19 -13
- data/lib/reform/validation/groups.rb +11 -25
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +7 -6
- data/test/benchmarking.rb +39 -5
- data/test/call_test.rb +1 -1
- data/test/changed_test.rb +1 -1
- data/test/coercion_test.rb +2 -2
- data/test/composition_test.rb +47 -9
- data/test/contract_test.rb +5 -5
- data/test/default_test.rb +1 -1
- data/test/deserialize_test.rb +3 -3
- data/test/errors_test.rb +36 -21
- data/test/feature_test.rb +1 -1
- data/test/fixtures/dry_error_messages.yml +70 -23
- data/test/form_option_test.rb +3 -3
- data/test/form_test.rb +3 -3
- data/test/from_test.rb +2 -2
- data/test/inherit_test.rb +44 -51
- data/test/module_test.rb +12 -12
- data/test/parse_option_test.rb +40 -0
- data/test/parse_pipeline_test.rb +2 -2
- data/test/populate_test.rb +59 -19
- data/test/populator_skip_test.rb +9 -8
- data/test/prepopulator_test.rb +3 -3
- data/test/readable_test.rb +2 -2
- data/test/readonly_test.rb +1 -1
- data/test/reform_test.rb +16 -31
- data/test/save_test.rb +23 -8
- data/test/setup_test.rb +2 -2
- data/test/skip_if_test.rb +4 -4
- data/test/skip_setter_and_getter_test.rb +1 -1
- data/test/test_helper.rb +13 -10
- data/test/validate_test.rb +18 -18
- data/test/validation/dry_validation_test.rb +430 -117
- data/test/validation/result_test.rb +79 -0
- data/test/validation_library_provided_test.rb +16 -0
- data/test/virtual_test.rb +1 -1
- data/test/writeable_test.rb +31 -2
- metadata +42 -23
- 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/validation/dry_test.rb +0 -60
- data/test/validation/errors.yml +0 -4
@@ -7,7 +7,7 @@
|
|
7
7
|
class Reform::Form::Populator
|
8
8
|
def initialize(user_proc)
|
9
9
|
@user_proc = user_proc # the actual `populator: ->{}` block from the user, via ::property.
|
10
|
-
@value = Declarative::Option(user_proc, instance_exec: true) # we can now process Callable, procs, :symbol.
|
10
|
+
@value = Declarative::Option(user_proc, instance_exec: true, callable: Object) # we can now process Callable, procs, :symbol.
|
11
11
|
end
|
12
12
|
|
13
13
|
def call(input, options)
|
@@ -29,10 +29,7 @@ class Reform::Form::Populator
|
|
29
29
|
private
|
30
30
|
def call!(options)
|
31
31
|
form = options[:represented]
|
32
|
-
|
33
|
-
deprecate_positional_args(form, @user_proc, options) do
|
34
|
-
@value.(form, options)
|
35
|
-
end
|
32
|
+
@value.(form, options) # Declarative::Option call.
|
36
33
|
end
|
37
34
|
|
38
35
|
def handle_fail(twin, options)
|
@@ -43,17 +40,6 @@ private
|
|
43
40
|
Representable::GetValue.(nil, options)
|
44
41
|
end
|
45
42
|
|
46
|
-
def deprecate_positional_args(form, proc, options) # TODO: remove in 2.2.
|
47
|
-
arity = proc.is_a?(Symbol) ? form.method(proc).arity : proc.arity
|
48
|
-
return yield if arity == 1
|
49
|
-
warn "[Reform] Positional arguments for :populator and friends are deprecated. Please use ->(options) and enjoy the rest of your day. Learn more at http://trailblazerb.org/gems/reform/upgrading-guide.html#to-21"
|
50
|
-
args = []
|
51
|
-
args << options[:index] if options[:index]
|
52
|
-
args << options[:representable_options]
|
53
|
-
form.instance_exec(options[:fragment], options[:model], *args, &proc)
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
43
|
class IfEmpty < self # Populator
|
58
44
|
def call!(options)
|
59
45
|
binding, twin, index, fragment = options[:binding], options[:model], options[:index], options[:fragment] # TODO: remove once we drop 2.0.
|
data/lib/reform/form/validate.rb
CHANGED
@@ -18,16 +18,19 @@ module Reform::Form::Validate
|
|
18
18
|
|
19
19
|
def validate(params)
|
20
20
|
# allow an external deserializer.
|
21
|
+
@input_params = params # we want to store these for access via dry later
|
21
22
|
block_given? ? yield(params) : deserialize(params)
|
22
23
|
|
23
24
|
super() # run the actual validation on self.
|
24
25
|
end
|
26
|
+
attr_reader :input_params # make the raw input params public
|
25
27
|
|
26
28
|
def deserialize(params)
|
27
29
|
params = deserialize!(params)
|
28
30
|
deserializer.new(self).from_hash(params)
|
29
31
|
end
|
30
32
|
|
33
|
+
|
31
34
|
private
|
32
35
|
# Meant to return params processable by the representer. This is the hook for munching date fields, etc.
|
33
36
|
def deserialize!(params)
|
@@ -40,7 +43,7 @@ private
|
|
40
43
|
|
41
44
|
# Default deserializer for hash.
|
42
45
|
# This is input-specific, e.g. Hash, JSON, or XML.
|
43
|
-
def deserializer(source=self.class, options={}) # called on top-level, only, for now.
|
46
|
+
def deserializer!(source=self.class, options={}) # called on top-level, only, for now.
|
44
47
|
deserializer = Disposable::Rescheme.from(source,
|
45
48
|
{
|
46
49
|
include: [Representable::Hash::AllowSymbols, Representable::Hash],
|
@@ -54,7 +57,12 @@ private
|
|
54
57
|
deserializer
|
55
58
|
end
|
56
59
|
|
60
|
+
def deserializer(*args)
|
61
|
+
# DISCUSS: should we simply delegate to class and sort out memoizing there?
|
62
|
+
self.class.deserializer_class || self.class.deserializer_class = deserializer!(*args)
|
63
|
+
end
|
57
64
|
|
58
|
-
|
65
|
+
def self.included(includer)
|
66
|
+
includer.singleton_class.send :attr_accessor, :deserializer_class
|
59
67
|
end
|
60
68
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Reform
|
2
|
+
class Contract < Disposable::Twin
|
3
|
+
|
4
|
+
# Collects all native results of a form of all groups and provides
|
5
|
+
# a unified API: #success?, #errors, #messages, #hints.
|
6
|
+
# #success? returns validity of the branch.
|
7
|
+
class Result
|
8
|
+
def initialize(results, nested_results=[]) # DISCUSS: do we like this?
|
9
|
+
@results = results # native Result objects, e.g. `#<Dry::Validation::Result output={:title=>"Fallout", :composer=>nil} errors={}>`
|
10
|
+
@failure = (results + nested_results).find(&:failure?) # TODO: test nested.
|
11
|
+
end
|
12
|
+
|
13
|
+
def failure?; @failure end
|
14
|
+
def success?; !failure? end
|
15
|
+
|
16
|
+
def errors(*args); filter_for(:errors, *args) end
|
17
|
+
def messages(*args); filter_for(:messages, *args) end
|
18
|
+
def hints(*args); filter_for(:hints, *args) end
|
19
|
+
|
20
|
+
private
|
21
|
+
def filter_for(method, *args)
|
22
|
+
@results.collect { |r| r.public_send(method, *args) }
|
23
|
+
.inject({}) { |hsh, errs| hsh.merge(errs) }
|
24
|
+
.find_all { |k, v| v.is_a?(Array) } # filter :nested=>{:something=>["too nested!"]} #DISCUSS: do we want that here?
|
25
|
+
.to_h
|
26
|
+
end
|
27
|
+
|
28
|
+
# Note: this class will be redundant in Reform 3, where the public API
|
29
|
+
# allows/enforces to pass options to #errors (e.g. errors(locale: "br"))
|
30
|
+
# which means we don't have to "lazy-handle" that with "pointers".
|
31
|
+
# :private:
|
32
|
+
class Pointer
|
33
|
+
extend Forwardable
|
34
|
+
|
35
|
+
def initialize(result, path)
|
36
|
+
@result, @path = result, path
|
37
|
+
end
|
38
|
+
|
39
|
+
def_delegators :@result, :success?, :failure?
|
40
|
+
|
41
|
+
def errors(*args); traverse_for(:errors, *args) end
|
42
|
+
def messages(*args); traverse_for(:messages, *args) end
|
43
|
+
def hints(*args); traverse_for(:hints, *args) end
|
44
|
+
|
45
|
+
def advance(*path)
|
46
|
+
path = @path + path.compact # remove index if nil.
|
47
|
+
return if traverse(@result.errors, path) == {}
|
48
|
+
|
49
|
+
Pointer.new(@result, path)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def traverse(hash, path)
|
54
|
+
path.inject(hash) { |errs, segment| errs[segment] || {} } # FIXME. test if all segments present.
|
55
|
+
end
|
56
|
+
|
57
|
+
def traverse_for(method, *args)
|
58
|
+
traverse(@result.public_send(method, *args), @path) # TODO: return [] if nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/reform/validation.rb
CHANGED
@@ -3,28 +3,36 @@
|
|
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
|
+
if name.nil?
|
27
|
+
return { name: :default }.merge(options)
|
28
|
+
end
|
21
29
|
|
22
|
-
|
23
|
-
validation(:default, inherit: true) { validate *args, &block }
|
30
|
+
{ name: :default }.merge(name)
|
24
31
|
end
|
25
32
|
|
26
|
-
def
|
27
|
-
|
33
|
+
def validation_group_class
|
34
|
+
raise NoValidationLibraryError, 'no validation library loaded. Please include a ' +
|
35
|
+
'validation library such as Reform::Form::Dry'
|
28
36
|
end
|
29
37
|
end
|
30
38
|
|
@@ -32,9 +40,7 @@ module Reform::Validation
|
|
32
40
|
includer.extend(ClassMethods)
|
33
41
|
end
|
34
42
|
|
35
|
-
|
36
|
-
Groups::Result.new(self.class.validation_groups).(to_nested_hash, errors, self)
|
37
|
-
end
|
43
|
+
NoValidationLibraryError = Class.new(RuntimeError)
|
38
44
|
end
|
39
45
|
|
40
46
|
require "reform/validation/groups"
|
@@ -19,7 +19,7 @@ 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
|
|
@@ -31,43 +31,29 @@ 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
39
|
|
40
|
-
# Runs all validations groups according to their rules and returns
|
41
|
-
|
42
|
-
|
43
|
-
def initialize(groups)
|
44
|
-
@groups = groups
|
45
|
-
end
|
46
|
-
|
47
|
-
def call(fields, errors, form)
|
48
|
-
result = true
|
40
|
+
# Runs all validations groups according to their rules and returns all Result objects.
|
41
|
+
class Validate
|
42
|
+
def self.call(groups, form)
|
49
43
|
results = {}
|
50
44
|
|
51
|
-
|
52
|
-
|
53
|
-
depends_on = options[:if]
|
45
|
+
groups.collect do |(name, group, options)|
|
46
|
+
next unless evaluate?(options[:if], results, form)
|
54
47
|
|
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?
|
48
|
+
results[name] = group.(form) # run validation for group. store and collect <Result>.
|
61
49
|
end
|
62
|
-
|
63
|
-
result
|
64
50
|
end
|
65
51
|
|
66
|
-
def
|
52
|
+
def self.evaluate?(depends_on, results, form)
|
67
53
|
return true if depends_on.nil?
|
68
|
-
return results[depends_on] if depends_on.is_a?(Symbol)
|
54
|
+
return results[depends_on].success? if depends_on.is_a?(Symbol)
|
69
55
|
form.instance_exec(results, &depends_on)
|
70
56
|
end
|
71
57
|
end
|
72
58
|
end
|
73
|
-
end
|
59
|
+
end
|
data/lib/reform/version.rb
CHANGED
data/reform.gemspec
CHANGED
@@ -5,11 +5,11 @@ require 'reform/version'
|
|
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", "
|
8
|
+
spec.authors = ["Nick Sutterer", "Fran Worley"]
|
9
|
+
spec.email = ["apotonick@gmail.com", "frances@safetytoolbox.co.uk"]
|
10
10
|
spec.description = %q{Form object decoupled from models.}
|
11
11
|
spec.summary = %q{Form object decoupled from models with validation, population and presentation.}
|
12
|
-
spec.homepage = "https://github.com/
|
12
|
+
spec.homepage = "https://github.com/trailblazer/reform"
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
15
15
|
spec.files = `git ls-files`.split($/)
|
@@ -17,13 +17,14 @@ Gem::Specification.new do |spec|
|
|
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 "
|
20
|
+
spec.add_dependency "disposable", ">= 0.4.2", "< 0.5.0"
|
21
|
+
spec.add_dependency "uber", "< 0.2.0"
|
22
|
+
spec.add_dependency "representable", ">= 2.4.0", "< 3.1.0"
|
22
23
|
|
23
24
|
spec.add_development_dependency "bundler"
|
24
25
|
spec.add_development_dependency "rake"
|
25
26
|
spec.add_development_dependency "minitest"
|
26
27
|
spec.add_development_dependency "dry-types"
|
27
28
|
spec.add_development_dependency "multi_json"
|
28
|
-
spec.add_development_dependency "dry-validation", ">= 0.10.
|
29
|
+
spec.add_development_dependency "dry-validation", ">= 0.10.1"
|
29
30
|
end
|
data/test/benchmarking.rb
CHANGED
@@ -1,15 +1,49 @@
|
|
1
1
|
require 'reform'
|
2
|
-
require '
|
3
|
-
require
|
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
|
+
|
46
|
+
|
13
47
|
songs = 50.times.collect { OpenStruct.new(title: "Be Stag") }
|
14
48
|
band = OpenStruct.new(name: "Teenage Bottlerock", songs: songs)
|
15
49
|
|
@@ -23,4 +57,4 @@ time = Benchmark.measure do
|
|
23
57
|
end
|
24
58
|
end
|
25
59
|
|
26
|
-
puts time
|
60
|
+
puts time
|
data/test/call_test.rb
CHANGED
data/test/changed_test.rb
CHANGED
data/test/coercion_test.rb
CHANGED
@@ -8,7 +8,7 @@ class CoercionTest < BaseTest
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
class Form <
|
11
|
+
class Form < TestForm
|
12
12
|
feature Coercion
|
13
13
|
|
14
14
|
property :released_at, type: Types::Form::DateTime
|
@@ -58,7 +58,7 @@ class CoercionTest < BaseTest
|
|
58
58
|
|
59
59
|
it { subject.released_at.must_equal DateTime.parse("30/03/1981") }
|
60
60
|
it { subject.hit.length.must_equal 312 }
|
61
|
-
it { subject.hit.good
|
61
|
+
it { assert_nil subject.hit.good }
|
62
62
|
it { subject.band.label.value.must_equal "9999.999999.99" } # coercion happened once.
|
63
63
|
end
|
64
64
|
|
data/test/composition_test.rb
CHANGED
@@ -1,11 +1,49 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
+
class FormCompositionInheritanceTest < MiniTest::Spec
|
4
|
+
module SizePrice
|
5
|
+
include Reform::Form::Module
|
6
|
+
|
7
|
+
property :price
|
8
|
+
property :size
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
def price(for_size: size)
|
12
|
+
case for_size.to_sym
|
13
|
+
when :s then super() * 1
|
14
|
+
when :m then super() * 2
|
15
|
+
when :l then super() * 3
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class OutfitForm < TestForm
|
22
|
+
include Reform::Form::Composition
|
23
|
+
include SizePrice
|
24
|
+
|
25
|
+
property :price, inherit: true, on: :tshirt
|
26
|
+
property :size, inherit: true, on: :measurement
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
let (:measurement) { Measurement.new(:l) }
|
31
|
+
let (:tshirt) { Tshirt.new(2, :m) }
|
32
|
+
let (:form) { OutfitForm.new(tshirt: tshirt, measurement: measurement) }
|
33
|
+
|
34
|
+
Tshirt = Struct.new(:price, :size)
|
35
|
+
Measurement = Struct.new(:size)
|
36
|
+
|
37
|
+
it { form.price.must_equal 6 }
|
38
|
+
it { form.price(for_size: :s).must_equal 2 }
|
39
|
+
end
|
40
|
+
|
3
41
|
class FormCompositionTest < MiniTest::Spec
|
4
42
|
Song = Struct.new(:id, :title, :band)
|
5
43
|
Requester = Struct.new(:id, :name, :requester)
|
6
44
|
Band = Struct.new(:title)
|
7
45
|
|
8
|
-
class RequestForm <
|
46
|
+
class RequestForm < TestForm
|
9
47
|
include Composition
|
10
48
|
|
11
49
|
property :name, :on => :requester
|
@@ -17,9 +55,8 @@ class FormCompositionTest < MiniTest::Spec
|
|
17
55
|
property :captcha, :on => :song, :virtual => true
|
18
56
|
|
19
57
|
validation do
|
20
|
-
|
21
|
-
|
22
|
-
key(:title).required
|
58
|
+
required(:name).filled
|
59
|
+
required(:title).filled
|
23
60
|
end
|
24
61
|
|
25
62
|
property :band, :on => :song do
|
@@ -37,20 +74,21 @@ class FormCompositionTest < MiniTest::Spec
|
|
37
74
|
it { form.title.must_equal "Rio" }
|
38
75
|
it { form.name.must_equal "Duran Duran" }
|
39
76
|
it { form.requester_id.must_equal 2 }
|
40
|
-
it { form.channel
|
77
|
+
it { assert_nil form.channel }
|
41
78
|
it { form.requester.must_equal "MCP" } # same name as composed model.
|
42
|
-
it { form.captcha
|
79
|
+
it { assert_nil form.captcha }
|
43
80
|
|
44
81
|
# #model just returns <Composition>.
|
45
82
|
it { form.mapper.must_be_kind_of Disposable::Composition }
|
46
83
|
|
47
84
|
# #model[] -> composed models
|
48
85
|
it { form.model[:requester].must_equal requester }
|
49
|
-
it { form.model[:song].must_equal
|
86
|
+
it { form.model[:song].must_equal song }
|
50
87
|
|
51
88
|
|
52
89
|
it "creates Composition for you" do
|
53
|
-
form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal
|
90
|
+
form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal true
|
91
|
+
form.validate("title" => "", "name" => "Frenzal Rhomb").must_equal false
|
54
92
|
end
|
55
93
|
|
56
94
|
describe "#save" do
|
@@ -133,7 +171,7 @@ class FormCompositionCollectionTest < MiniTest::Spec
|
|
133
171
|
end
|
134
172
|
end
|
135
173
|
|
136
|
-
class LibraryForm <
|
174
|
+
class LibraryForm < TestForm
|
137
175
|
include Reform::Form::Composition
|
138
176
|
|
139
177
|
collection :books, on: :library do
|