reform 2.3.0.rc1 → 2.3.0.rc2
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 +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +30 -0
- data/.rubocop_todo.yml +460 -0
- data/.travis.yml +26 -11
- data/CHANGES.md +25 -2
- data/Gemfile +6 -3
- data/ISSUE_TEMPLATE.md +1 -1
- data/README.md +2 -4
- data/Rakefile +18 -9
- data/lib/reform/contract.rb +7 -7
- data/lib/reform/contract/custom_error.rb +41 -0
- data/lib/reform/contract/validate.rb +9 -5
- data/lib/reform/errors.rb +27 -15
- data/lib/reform/form.rb +22 -11
- data/lib/reform/form/call.rb +1 -1
- data/lib/reform/form/composition.rb +2 -2
- data/lib/reform/form/dry.rb +10 -86
- data/lib/reform/form/dry/input_hash.rb +37 -0
- data/lib/reform/form/dry/new_api.rb +58 -0
- data/lib/reform/form/dry/old_api.rb +61 -0
- data/lib/reform/form/populator.rb +9 -11
- data/lib/reform/form/prepopulate.rb +3 -2
- data/lib/reform/form/validate.rb +19 -12
- data/lib/reform/result.rb +36 -9
- data/lib/reform/validation.rb +10 -8
- data/lib/reform/validation/groups.rb +2 -3
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +10 -9
- data/test/benchmarking.rb +10 -11
- data/test/call_new_api.rb +23 -0
- data/test/{call_test.rb → call_old_api.rb} +3 -3
- data/test/changed_test.rb +7 -7
- data/test/coercion_test.rb +50 -18
- data/test/composition_new_api.rb +186 -0
- data/test/{composition_test.rb → composition_old_api.rb} +23 -26
- 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} +8 -8
- data/test/default_test.rb +1 -1
- data/test/deserialize_test.rb +8 -11
- data/test/errors_new_api.rb +225 -0
- data/test/errors_old_api.rb +230 -0
- data/test/feature_test.rb +7 -9
- data/test/fixtures/dry_error_messages.yml +5 -2
- 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} +2 -2
- data/test/form_option_new_api.rb +24 -0
- data/test/{form_option_test.rb → form_option_old_api.rb} +1 -1
- data/test/from_test.rb +8 -12
- data/test/inherit_new_api.rb +105 -0
- data/test/{inherit_test.rb → inherit_old_api.rb} +10 -17
- data/test/module_new_api.rb +137 -0
- data/test/{module_test.rb → module_old_api.rb} +19 -15
- data/test/parse_option_test.rb +5 -5
- data/test/parse_pipeline_test.rb +2 -2
- data/test/populate_new_api.rb +304 -0
- data/test/{populate_test.rb → populate_old_api.rb} +28 -34
- data/test/populator_skip_test.rb +1 -2
- data/test/prepopulator_test.rb +5 -6
- data/test/read_only_test.rb +12 -1
- data/test/readable_test.rb +5 -5
- data/test/reform_new_api.rb +204 -0
- data/test/{reform_test.rb → reform_old_api.rb} +17 -23
- data/test/save_new_api.rb +101 -0
- data/test/{save_test.rb → save_old_api.rb} +10 -13
- data/test/setup_test.rb +6 -6
- data/test/{skip_if_test.rb → skip_if_new_api.rb} +20 -9
- data/test/skip_if_old_api.rb +92 -0
- data/test/skip_setter_and_getter_test.rb +2 -3
- data/test/test_helper.rb +13 -5
- data/test/validate_new_api.rb +408 -0
- data/test/{validate_test.rb → validate_old_api.rb} +43 -53
- data/test/validation/dry_validation_new_api.rb +826 -0
- data/test/validation/{dry_validation_test.rb → dry_validation_old_api.rb} +223 -116
- data/test/validation/result_test.rb +20 -22
- data/test/validation_library_provided_test.rb +3 -3
- data/test/virtual_test.rb +46 -6
- data/test/writeable_test.rb +7 -7
- metadata +101 -51
- data/test/errors_test.rb +0 -180
- data/test/readonly_test.rb +0 -14
data/lib/reform/validation.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Reform::Validation
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
# DSL.
|
|
10
|
-
def validation(name=nil, options={}, &block)
|
|
10
|
+
def validation(name = nil, options = {}, &block)
|
|
11
11
|
options = deprecate_validation_positional_args(name, options)
|
|
12
12
|
name = options[:name] # TODO: remove in favor of kw args in 3.0.
|
|
13
13
|
|
|
@@ -20,19 +20,17 @@ module Reform::Validation
|
|
|
20
20
|
def deprecate_validation_positional_args(name, options)
|
|
21
21
|
if name.is_a?(Symbol)
|
|
22
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 {
|
|
23
|
+
return {name: name}.merge(options)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
if name.nil?
|
|
27
|
-
return { name: :default }.merge(options)
|
|
28
|
-
end
|
|
26
|
+
return {name: :default}.merge(options) if name.nil?
|
|
29
27
|
|
|
30
|
-
{
|
|
28
|
+
{name: :default}.merge(name)
|
|
31
29
|
end
|
|
32
30
|
|
|
33
31
|
def validation_group_class
|
|
34
|
-
raise NoValidationLibraryError,
|
|
35
|
-
|
|
32
|
+
raise NoValidationLibraryError, "no validation library loaded. Please include a " +
|
|
33
|
+
"validation library such as Reform::Form::Dry"
|
|
36
34
|
end
|
|
37
35
|
end
|
|
38
36
|
|
|
@@ -40,6 +38,10 @@ module Reform::Validation
|
|
|
40
38
|
includer.extend(ClassMethods)
|
|
41
39
|
end
|
|
42
40
|
|
|
41
|
+
def valid?
|
|
42
|
+
validate({})
|
|
43
|
+
end
|
|
44
|
+
|
|
43
45
|
NoValidationLibraryError = Class.new(RuntimeError)
|
|
44
46
|
end
|
|
45
47
|
|
|
@@ -23,7 +23,7 @@ module Reform::Validation
|
|
|
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]
|
|
@@ -36,7 +36,6 @@ module Reform::Validation
|
|
|
36
36
|
cfg[1]
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
|
|
40
39
|
# Runs all validations groups according to their rules and returns all Result objects.
|
|
41
40
|
class Validate
|
|
42
41
|
def self.call(groups, form)
|
|
@@ -51,7 +50,7 @@ module Reform::Validation
|
|
|
51
50
|
|
|
52
51
|
def self.evaluate?(depends_on, results, form)
|
|
53
52
|
return true if depends_on.nil?
|
|
54
|
-
return results[depends_on].success? if depends_on.is_a?(Symbol)
|
|
53
|
+
return !results[depends_on].nil? && results[depends_on].success? if depends_on.is_a?(Symbol)
|
|
55
54
|
form.instance_exec(results, &depends_on)
|
|
56
55
|
end
|
|
57
56
|
end
|
data/lib/reform/version.rb
CHANGED
data/reform.gemspec
CHANGED
|
@@ -1,30 +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
8
|
spec.authors = ["Nick Sutterer", "Fran Worley"]
|
|
9
9
|
spec.email = ["apotonick@gmail.com", "frances@safetytoolbox.co.uk"]
|
|
10
|
-
spec.description =
|
|
11
|
-
spec.summary =
|
|
10
|
+
spec.description = "Form object decoupled from models."
|
|
11
|
+
spec.summary = "Form object decoupled from models with validation, population and presentation."
|
|
12
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
20
|
spec.add_dependency "disposable", ">= 0.4.2", "< 0.5.0"
|
|
21
|
-
spec.add_dependency "uber", "< 0.2.0"
|
|
22
21
|
spec.add_dependency "representable", ">= 2.4.0", "< 3.1.0"
|
|
22
|
+
spec.add_dependency "uber", "< 0.2.0"
|
|
23
23
|
|
|
24
24
|
spec.add_development_dependency "bundler"
|
|
25
|
-
spec.add_development_dependency "rake"
|
|
26
25
|
spec.add_development_dependency "minitest"
|
|
27
|
-
spec.add_development_dependency "
|
|
26
|
+
spec.add_development_dependency "minitest-line"
|
|
27
|
+
spec.add_development_dependency "byebug"
|
|
28
28
|
spec.add_development_dependency "multi_json"
|
|
29
|
-
spec.add_development_dependency "
|
|
29
|
+
spec.add_development_dependency "rake"
|
|
30
|
+
spec.add_development_dependency "rubocop"
|
|
30
31
|
end
|
data/test/benchmarking.rb
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require "reform"
|
|
2
|
+
require "benchmark/ips"
|
|
3
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}
|
|
7
|
+
property :name #, validates: {presence: true}
|
|
8
8
|
collection :songs do
|
|
9
|
-
property :title#, validates: {presence: true}
|
|
9
|
+
property :title #, validates: {presence: true}
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
class OptimizedBandForm < Reform::Form
|
|
14
14
|
feature Reform::Form::Dry
|
|
15
|
-
property :name#, validates: {presence: true}
|
|
15
|
+
property :name #, validates: {presence: true}
|
|
16
16
|
collection :songs do
|
|
17
|
-
property :title#, validates: {presence: true}
|
|
18
|
-
|
|
17
|
+
property :title #, validates: {presence: true}
|
|
18
|
+
|
|
19
19
|
def deserializer(*args)
|
|
20
|
-
# DISCUSS: should we simply delegate to class and sort out memoizing there?
|
|
20
|
+
# DISCUSS: should we simply delegate to class and sort out memoizing there?
|
|
21
21
|
self.class.deserializer_class || self.class.deserializer_class = deserializer!(*args)
|
|
22
|
-
|
|
22
|
+
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def deserializer(*args)
|
|
26
|
-
# DISCUSS: should we simply delegate to class and sort out memoizing there?
|
|
26
|
+
# DISCUSS: should we simply delegate to class and sort out memoizing there?
|
|
27
27
|
self.class.deserializer_class || self.class.deserializer_class = deserializer!(*args)
|
|
28
28
|
end
|
|
29
29
|
end
|
|
@@ -43,7 +43,6 @@ end
|
|
|
43
43
|
|
|
44
44
|
exit
|
|
45
45
|
|
|
46
|
-
|
|
47
46
|
songs = 50.times.collect { OpenStruct.new(title: "Be Stag") }
|
|
48
47
|
band = OpenStruct.new(name: "Teenage Bottlerock", songs: songs)
|
|
49
48
|
|
|
@@ -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
|
|
@@ -7,11 +7,11 @@ class CallTest < Minitest::Spec
|
|
|
7
7
|
property :title
|
|
8
8
|
|
|
9
9
|
validation do
|
|
10
|
-
|
|
10
|
+
required(:title).filled
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
let
|
|
14
|
+
let(:form) { SongForm.new(Song.new) }
|
|
15
15
|
|
|
16
16
|
it { form.(title: "True North").success?.must_equal true }
|
|
17
17
|
it { form.(title: "True North").failure?.must_equal false }
|
|
@@ -19,5 +19,5 @@ class CallTest < Minitest::Spec
|
|
|
19
19
|
it { form.(title: "").failure?.must_equal true }
|
|
20
20
|
|
|
21
21
|
it { form.(title: "True North").errors.messages.must_equal({}) }
|
|
22
|
-
it { form.(title: "").errors.messages.must_equal(
|
|
22
|
+
it { form.(title: "").errors.messages.must_equal(title: ["must be filled"]) }
|
|
23
23
|
end
|
data/test/changed_test.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
|
+
require "reform/form/coercion"
|
|
3
3
|
|
|
4
4
|
class ChangedTest < MiniTest::Spec
|
|
5
5
|
Song = Struct.new(:title, :album, :composer)
|
|
@@ -18,11 +18,11 @@ class ChangedTest < MiniTest::Spec
|
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
let
|
|
22
|
-
let
|
|
23
|
-
let
|
|
21
|
+
let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
|
22
|
+
let(:composer) { Artist.new("Greg Graffin") }
|
|
23
|
+
let(:album) { Album.new("The Dissent Of Man", [song_with_composer]) }
|
|
24
24
|
|
|
25
|
-
let
|
|
25
|
+
let(:form) { AlbumForm.new(album) }
|
|
26
26
|
|
|
27
27
|
# nothing changed after setup.
|
|
28
28
|
it do
|
|
@@ -38,4 +38,4 @@ class ChangedTest < MiniTest::Spec
|
|
|
38
38
|
form.songs[0].changed?(:title).must_equal false
|
|
39
39
|
form.songs[0].composer.changed?(:name).must_equal true
|
|
40
40
|
end
|
|
41
|
-
end
|
|
41
|
+
end
|
data/test/coercion_test.rb
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
require "test_helper"
|
|
2
2
|
require "reform/form/coercion"
|
|
3
|
+
require "disposable/twin/property/hash"
|
|
3
4
|
|
|
4
5
|
class CoercionTest < BaseTest
|
|
5
6
|
class Irreversible
|
|
6
7
|
def self.call(value)
|
|
7
|
-
value*2
|
|
8
|
+
value * 2
|
|
8
9
|
end
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
class Form < TestForm
|
|
12
13
|
feature Coercion
|
|
14
|
+
include Disposable::Twin::Property::Hash
|
|
13
15
|
|
|
14
|
-
property :released_at, type:
|
|
16
|
+
property :released_at, type: DRY_TYPES_CONSTANT::DateTime
|
|
15
17
|
|
|
16
18
|
property :hit do
|
|
17
|
-
property :length, type:
|
|
18
|
-
property :good, type:
|
|
19
|
+
property :length, type: DRY_TYPES_INT_CONSTANT
|
|
20
|
+
property :good, type: DRY_TYPES_CONSTANT::Bool
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
property :band do
|
|
@@ -23,34 +25,51 @@ class CoercionTest < BaseTest
|
|
|
23
25
|
property :value, type: Irreversible
|
|
24
26
|
end
|
|
25
27
|
end
|
|
28
|
+
|
|
29
|
+
property :metadata, field: :hash do
|
|
30
|
+
property :publication_settings do
|
|
31
|
+
property :featured, type: DRY_TYPES_CONSTANT::Bool
|
|
32
|
+
end
|
|
33
|
+
end
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
subject do
|
|
29
37
|
Form.new(album)
|
|
30
38
|
end
|
|
31
39
|
|
|
32
|
-
let
|
|
40
|
+
let(:album) do
|
|
33
41
|
OpenStruct.new(
|
|
34
|
-
:
|
|
35
|
-
:
|
|
36
|
-
:
|
|
42
|
+
released_at: "31/03/1981",
|
|
43
|
+
hit: OpenStruct.new(length: "312"),
|
|
44
|
+
band: Band.new(OpenStruct.new(value: "9999.99")),
|
|
45
|
+
metadata: {}
|
|
37
46
|
)
|
|
38
|
-
|
|
47
|
+
end
|
|
39
48
|
|
|
40
49
|
# it { subject.released_at.must_be_kind_of DateTime }
|
|
41
50
|
it { subject.released_at.must_equal "31/03/1981" } # NO coercion in setup.
|
|
42
51
|
it { subject.hit.length.must_equal "312" }
|
|
43
52
|
it { subject.band.label.value.must_equal "9999.99" }
|
|
44
53
|
|
|
45
|
-
|
|
46
|
-
let (:params) {
|
|
54
|
+
let(:params) do
|
|
47
55
|
{
|
|
48
|
-
:
|
|
49
|
-
:
|
|
50
|
-
|
|
56
|
+
released_at: "30/03/1981",
|
|
57
|
+
hit: {
|
|
58
|
+
length: "312",
|
|
59
|
+
good: "0",
|
|
60
|
+
},
|
|
61
|
+
band: {
|
|
62
|
+
label: {
|
|
63
|
+
value: "9999.99"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
metadata: {
|
|
67
|
+
publication_settings: {
|
|
68
|
+
featured: "0"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
51
71
|
}
|
|
52
|
-
|
|
53
|
-
|
|
72
|
+
end
|
|
54
73
|
|
|
55
74
|
# validate
|
|
56
75
|
describe "#validate" do
|
|
@@ -58,9 +77,22 @@ class CoercionTest < BaseTest
|
|
|
58
77
|
|
|
59
78
|
it { subject.released_at.must_equal DateTime.parse("30/03/1981") }
|
|
60
79
|
it { subject.hit.length.must_equal 312 }
|
|
61
|
-
it {
|
|
80
|
+
it { subject.hit.good.must_equal false }
|
|
62
81
|
it { subject.band.label.value.must_equal "9999.999999.99" } # coercion happened once.
|
|
82
|
+
it { subject.metadata.publication_settings.featured.must_equal false }
|
|
63
83
|
end
|
|
64
84
|
|
|
65
|
-
#
|
|
85
|
+
# sync
|
|
86
|
+
describe "#sync" do
|
|
87
|
+
before do
|
|
88
|
+
subject.validate(params).must_equal true
|
|
89
|
+
subject.sync
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it { album.released_at.must_equal DateTime.parse("30/03/1981") }
|
|
93
|
+
it { album.hit.length.must_equal 312 }
|
|
94
|
+
it { album.hit.good.must_equal false }
|
|
95
|
+
it { assert_nil album.metadata[:publication_settings] }
|
|
96
|
+
it { album.metadata["publication_settings"]["featured"].must_equal false }
|
|
97
|
+
end
|
|
66
98
|
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
require "test_helper"
|
|
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
|
+
let(:measurement) { Measurement.new(:l) }
|
|
30
|
+
let(:tshirt) { Tshirt.new(2, :m) }
|
|
31
|
+
let(:form) { OutfitForm.new(tshirt: tshirt, measurement: measurement) }
|
|
32
|
+
|
|
33
|
+
Tshirt = Struct.new(:price, :size)
|
|
34
|
+
Measurement = Struct.new(:size)
|
|
35
|
+
|
|
36
|
+
it { form.price.must_equal 6 }
|
|
37
|
+
it { form.price(for_size: :s).must_equal 2 }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class FormCompositionTest < MiniTest::Spec
|
|
41
|
+
Song = Struct.new(:id, :title, :band)
|
|
42
|
+
Requester = Struct.new(:id, :name, :requester)
|
|
43
|
+
Band = Struct.new(:title)
|
|
44
|
+
|
|
45
|
+
class RequestForm < TestForm
|
|
46
|
+
include Composition
|
|
47
|
+
|
|
48
|
+
property :name, on: :requester
|
|
49
|
+
property :requester_id, on: :requester, from: :id
|
|
50
|
+
properties :title, :id, on: :song
|
|
51
|
+
# property :channel # FIXME: what about the "main model"?
|
|
52
|
+
property :channel, virtual: true, on: :song
|
|
53
|
+
property :requester, on: :requester
|
|
54
|
+
property :captcha, on: :song, virtual: true
|
|
55
|
+
|
|
56
|
+
validation do
|
|
57
|
+
params do
|
|
58
|
+
required(:name).filled
|
|
59
|
+
required(:title).filled
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
property :band, on: :song do
|
|
64
|
+
property :title
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
let(:form) { RequestForm.new(song: song, requester: requester) }
|
|
69
|
+
let(:song) { Song.new(1, "Rio", band) }
|
|
70
|
+
let(:requester) { Requester.new(2, "Duran Duran", "MCP") }
|
|
71
|
+
let(:band) { Band.new("Duran^2") }
|
|
72
|
+
|
|
73
|
+
# delegation form -> composition works
|
|
74
|
+
it { form.id.must_equal 1 }
|
|
75
|
+
it { form.title.must_equal "Rio" }
|
|
76
|
+
it { form.name.must_equal "Duran Duran" }
|
|
77
|
+
it { form.requester_id.must_equal 2 }
|
|
78
|
+
it { assert_nil form.channel }
|
|
79
|
+
it { form.requester.must_equal "MCP" } # same name as composed model.
|
|
80
|
+
it { assert_nil form.captcha }
|
|
81
|
+
|
|
82
|
+
# #model just returns <Composition>.
|
|
83
|
+
it { form.mapper.must_be_kind_of Disposable::Composition }
|
|
84
|
+
|
|
85
|
+
# #model[] -> composed models
|
|
86
|
+
it { form.model[:requester].must_equal requester }
|
|
87
|
+
it { form.model[:song].must_equal song }
|
|
88
|
+
|
|
89
|
+
it "creates Composition for you" do
|
|
90
|
+
form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal true
|
|
91
|
+
form.validate("title" => "", "name" => "Frenzal Rhomb").must_equal false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe "#save" do
|
|
95
|
+
# #save with {}
|
|
96
|
+
it do
|
|
97
|
+
hash = {}
|
|
98
|
+
|
|
99
|
+
form.save do |map|
|
|
100
|
+
hash[:name] = form.name
|
|
101
|
+
hash[:title] = form.title
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
hash.must_equal({name: "Duran Duran", title: "Rio"})
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "provides nested symbolized hash as second block argument" do
|
|
108
|
+
form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "channel" => "JJJ", "captcha" => "wonderful")
|
|
109
|
+
|
|
110
|
+
hash = nil
|
|
111
|
+
|
|
112
|
+
form.save do |map|
|
|
113
|
+
hash = map
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
hash.must_equal({
|
|
117
|
+
song: {"title" => "Greyhound", "id" => 1, "channel" => "JJJ", "captcha" => "wonderful", "band" => {"title" => "Duran^2"}},
|
|
118
|
+
requester: {"name" => "Frenzal Rhomb", "id" => 2, "requester" => "MCP"}
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "xxx pushes data to models and calls #save when no block passed" do
|
|
124
|
+
song.extend(Saveable)
|
|
125
|
+
requester.extend(Saveable)
|
|
126
|
+
band.extend(Saveable)
|
|
127
|
+
|
|
128
|
+
form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "captcha" => "1337")
|
|
129
|
+
form.captcha.must_equal "1337" # TODO: move to separate test.
|
|
130
|
+
|
|
131
|
+
form.save
|
|
132
|
+
|
|
133
|
+
requester.name.must_equal "Frenzal Rhomb"
|
|
134
|
+
requester.saved?.must_equal true
|
|
135
|
+
song.title.must_equal "Greyhound"
|
|
136
|
+
song.saved?.must_equal true
|
|
137
|
+
song.band.title.must_equal "Duran^2"
|
|
138
|
+
song.band.saved?.must_equal true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "returns true when models all save successfully" do
|
|
142
|
+
song.extend(Saveable)
|
|
143
|
+
requester.extend(Saveable)
|
|
144
|
+
band.extend(Saveable)
|
|
145
|
+
|
|
146
|
+
form.save.must_equal true
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "returns false when one or more models don't save successfully" do
|
|
150
|
+
module Unsaveable
|
|
151
|
+
def save
|
|
152
|
+
false
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
song.extend(Unsaveable)
|
|
157
|
+
requester.extend(Saveable)
|
|
158
|
+
band.extend(Saveable)
|
|
159
|
+
|
|
160
|
+
form.save.must_equal false
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class FormCompositionCollectionTest < MiniTest::Spec
|
|
166
|
+
Book = Struct.new(:id, :name)
|
|
167
|
+
Library = Struct.new(:id) do
|
|
168
|
+
def books
|
|
169
|
+
[Book.new(1, "My book")]
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
class LibraryForm < TestForm
|
|
174
|
+
include Reform::Form::Composition
|
|
175
|
+
|
|
176
|
+
collection :books, on: :library do
|
|
177
|
+
property :id
|
|
178
|
+
property :name
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
let(:form) { LibraryForm.new(library: library) }
|
|
183
|
+
let(:library) { Library.new(2) }
|
|
184
|
+
|
|
185
|
+
it { form.save { |hash| hash.must_equal({library: {"books" => [{"id" => 1, "name" => "My book"}]}}) } }
|
|
186
|
+
end
|