reform 1.2.6 → 2.0.0.beta1

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -1
  3. data/CHANGES.md +14 -0
  4. data/Gemfile +3 -2
  5. data/README.md +225 -283
  6. data/Rakefile +27 -0
  7. data/TODO.md +12 -0
  8. data/database.sqlite3 +0 -0
  9. data/gemfiles/Gemfile.rails-3.0 +1 -0
  10. data/gemfiles/Gemfile.rails-3.1 +1 -0
  11. data/gemfiles/Gemfile.rails-3.2 +1 -0
  12. data/gemfiles/Gemfile.rails-4.0 +1 -0
  13. data/lib/reform.rb +0 -1
  14. data/lib/reform/contract.rb +64 -170
  15. data/lib/reform/contract/validate.rb +10 -13
  16. data/lib/reform/form.rb +74 -19
  17. data/lib/reform/form/active_model.rb +19 -14
  18. data/lib/reform/form/coercion.rb +1 -13
  19. data/lib/reform/form/composition.rb +2 -24
  20. data/lib/reform/form/multi_parameter_attributes.rb +43 -62
  21. data/lib/reform/form/populator.rb +85 -0
  22. data/lib/reform/form/prepopulate.rb +13 -43
  23. data/lib/reform/form/validate.rb +29 -90
  24. data/lib/reform/form/validation/unique_validator.rb +13 -0
  25. data/lib/reform/version.rb +1 -1
  26. data/reform.gemspec +7 -7
  27. data/test/active_model_test.rb +43 -0
  28. data/test/changed_test.rb +23 -51
  29. data/test/coercion_test.rb +1 -7
  30. data/test/composition_test.rb +128 -34
  31. data/test/contract_test.rb +27 -86
  32. data/test/feature_test.rb +43 -6
  33. data/test/fields_test.rb +2 -12
  34. data/test/form_builder_test.rb +28 -25
  35. data/test/form_option_test.rb +19 -0
  36. data/test/from_test.rb +0 -75
  37. data/test/inherit_test.rb +178 -117
  38. data/test/model_reflections_test.rb +1 -1
  39. data/test/populate_test.rb +226 -0
  40. data/test/prepopulator_test.rb +112 -0
  41. data/test/readable_test.rb +2 -4
  42. data/test/save_test.rb +56 -112
  43. data/test/setup_test.rb +48 -0
  44. data/test/skip_if_test.rb +5 -2
  45. data/test/skip_setter_and_getter_test.rb +54 -0
  46. data/test/test_helper.rb +3 -1
  47. data/test/uniqueness_test.rb +41 -0
  48. data/test/validate_test.rb +325 -289
  49. data/test/virtual_test.rb +1 -3
  50. data/test/writeable_test.rb +3 -4
  51. metadata +35 -39
  52. data/lib/reform/composition.rb +0 -63
  53. data/lib/reform/contract/setup.rb +0 -50
  54. data/lib/reform/form/changed.rb +0 -9
  55. data/lib/reform/form/sync.rb +0 -116
  56. data/lib/reform/representer.rb +0 -84
  57. data/test/empty_test.rb +0 -58
  58. data/test/form_composition_test.rb +0 -145
  59. data/test/nested_form_test.rb +0 -197
  60. data/test/prepopulate_test.rb +0 -85
  61. data/test/sync_option_test.rb +0 -83
  62. data/test/sync_test.rb +0 -56
@@ -0,0 +1,13 @@
1
+ class Reform::Form::UniqueValidator < ActiveModel::EachValidator
2
+ def validate_each(form, attribute, value)
3
+ # search for models with attribute equals to form field value
4
+ query = form.model.class.where(attribute => value)
5
+
6
+ # if model persisted, excluded own model from query
7
+ query = query.merge(form.model.class.where("id <> ?", form.model.id)) if form.model.persisted?
8
+
9
+ # if any models found, add error on attribute
10
+ form.errors.add(attribute, "#{attribute} must be unique.") if query.any?
11
+ end
12
+ end
13
+
@@ -1,3 +1,3 @@
1
1
  module Reform
2
- VERSION = "1.2.6"
2
+ VERSION = "2.0.0.beta1"
3
3
  end
data/reform.gemspec CHANGED
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  lib = File.expand_path('../lib', __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'reform/version'
@@ -8,8 +7,8 @@ Gem::Specification.new do |spec|
8
7
  spec.version = Reform::VERSION
9
8
  spec.authors = ["Nick Sutterer", "Garrett Heinlen"]
10
9
  spec.email = ["apotonick@gmail.com", "heinleng@gmail.com"]
11
- spec.description = %q{Freeing your AR models from form logic.}
12
- spec.summary = %q{Decouples your models from form by giving you form objects with validation, presentation, workflows and security.}
10
+ spec.description = %q{Form object decoupled from models.}
11
+ spec.summary = %q{Form object decoupled from models with validation, population and presentation.}
13
12
  spec.homepage = "https://github.com/apotonick/reform"
14
13
  spec.license = "MIT"
15
14
 
@@ -18,13 +17,14 @@ Gem::Specification.new do |spec|
18
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
18
  spec.require_paths = ["lib"]
20
19
 
21
- spec.add_dependency "representable", "~> 2.1.0"
22
- spec.add_dependency "disposable", "~> 0.0.5"
20
+ spec.add_dependency "representable", ">= 2.2.2"
21
+ spec.add_dependency "disposable", "~> 0.1.2"
23
22
  spec.add_dependency "uber", "~> 0.0.11"
24
23
  spec.add_dependency "activemodel"
25
- spec.add_development_dependency "bundler", "~> 1.3"
24
+
25
+ spec.add_development_dependency "bundler"
26
26
  spec.add_development_dependency "rake"
27
- spec.add_development_dependency "minitest", "5.4.1"
27
+ spec.add_development_dependency "minitest"
28
28
  spec.add_development_dependency "activerecord"
29
29
  spec.add_development_dependency "sqlite3"
30
30
  spec.add_development_dependency "virtus"
@@ -1,5 +1,20 @@
1
1
  require 'test_helper'
2
2
 
3
+ module IsolatedRailsEngine
4
+ def self.use_relative_model_naming?
5
+ true
6
+ end
7
+
8
+ class Lyric < ActiveRecord::Base
9
+ end
10
+ end
11
+
12
+ module NormalRailsEngine
13
+ class Lyric < ActiveRecord::Base
14
+ end
15
+ end
16
+
17
+
3
18
  class NewActiveModelTest < MiniTest::Spec # TODO: move to test/rails/
4
19
  class SongForm < Reform::Form
5
20
  include Reform::Form::ActiveModel
@@ -31,6 +46,27 @@ class NewActiveModelTest < MiniTest::Spec # TODO: move to test/rails/
31
46
  it { class_with_model.model_name.must_be_kind_of ActiveModel::Name }
32
47
  it { class_with_model.model_name.to_s.must_equal "Album" }
33
48
 
49
+ let (:class_with_isolated_model) {
50
+ Class.new(Reform::Form) do
51
+ include Reform::Form::ActiveModel
52
+
53
+ model "isolated_rails_engine/lyric", namespace: "isolated_rails_engine"
54
+ end
55
+ }
56
+
57
+ it { class_with_isolated_model.model_name.must_be_kind_of ActiveModel::Name }
58
+ it { class_with_isolated_model.model_name.to_s.must_equal "IsolatedRailsEngine::Lyric" }
59
+
60
+ let (:class_with_namespace_model) {
61
+ Class.new(Reform::Form) do
62
+ include Reform::Form::ActiveModel
63
+
64
+ model "normal_rails_engine/lyric"
65
+ end
66
+ }
67
+
68
+ it { class_with_namespace_model.model_name.must_be_kind_of ActiveModel::Name }
69
+ it { class_with_namespace_model.model_name.to_s.must_equal "NormalRailsEngine::Lyric" }
34
70
 
35
71
  let (:subclass_of_class_with_model) {
36
72
  Class.new(class_with_model)
@@ -39,6 +75,13 @@ class NewActiveModelTest < MiniTest::Spec # TODO: move to test/rails/
39
75
  it { subclass_of_class_with_model.model_name.must_be_kind_of ActiveModel::Name }
40
76
  it { subclass_of_class_with_model.model_name.to_s.must_equal 'Album' }
41
77
 
78
+ unless Reform.rails3_0?
79
+ it { form.class.model_name.route_key.must_equal "new_active_model_test_songs" }
80
+ it { class_with_model.model_name.route_key.must_equal "albums" }
81
+ it { class_with_isolated_model.model_name.route_key.must_equal "lyrics" }
82
+ it { class_with_namespace_model.model_name.route_key.must_equal "normal_rails_engine_lyrics" }
83
+ it { subclass_of_class_with_model.model_name.route_key.must_equal 'albums' }
84
+ end
42
85
 
43
86
  describe "class named Song::Form" do
44
87
  it do
data/test/changed_test.rb CHANGED
@@ -1,69 +1,41 @@
1
1
  require 'test_helper'
2
2
  require 'reform/form/coercion'
3
3
 
4
- class ChangedTest < BaseTest
5
- class AlbumForm < Reform::Form
6
- include Coercion
7
-
8
- property :title
4
+ class ChangedTest < MiniTest::Spec
5
+ Song = Struct.new(:title, :album, :composer)
6
+ Album = Struct.new(:name, :songs, :artist)
7
+ Artist = Struct.new(:name)
9
8
 
10
- property :hit do
11
- property :title
12
- property :length, type: Integer
13
- validates :title, :presence => true
14
- end
9
+ class AlbumForm < Reform::Form
10
+ property :name
15
11
 
16
12
  collection :songs do
17
13
  property :title
18
- validates :title, :presence => true
19
- end
20
14
 
21
- property :band do # yepp, people do crazy stuff like that.
22
- property :label do
15
+ property :composer do
23
16
  property :name
24
- property :location
25
- validates :name, :presence => true
26
17
  end
27
- # TODO: make band a required object.
28
18
  end
29
-
30
- validates :title, :presence => true
31
19
  end
32
20
 
33
- Label = Struct.new(:name, :location)
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]) }
34
24
 
35
- # setup: changed? is always false
36
- let (:form) { AlbumForm.new(Album.new("Drawn Down The Moon", Song.new("The Ripper", 9), [Song.new("Black Candles"), Song.new("The Ripper")], Band.new(Label.new("Cleopatra Records")))) }
25
+ let (:form) { AlbumForm.new(album) }
37
26
 
38
- it { form.changed?(:title).must_equal false }
39
- it { form.changed?("title").must_equal false }
40
- it { form.hit.changed?(:title).must_equal false }
41
- it { form.hit.changed?.must_equal false }
42
-
43
-
44
- describe "#validate" do
45
- before { form.validate(
46
- "title" => "Five", # changed.
47
- "hit" => {"title" => "The Ripper", # same, but overridden.
48
- "length" => "9"}, # gets coerced, then compared, so not changed.
49
- "band" => {"label" => {"name" => "Shrapnel Records"}} # only label.name changes.
50
- ) }
51
-
52
- it { form.changed?(:title).must_equal true }
53
-
54
- # it { form.changed?(:hit).must_equal false }
55
-
56
- # overridden with same value is no change.
57
- it { form.hit.changed?(:title).must_equal false }
58
- # coerced value is identical to form's => not changed.
59
- it { form.hit.changed?(:length).must_equal false }
60
-
61
- # it { form.changed?(:band).must_equal true }
62
- # it { form.band.changed?(:label).must_equal true }
63
- it { form.band.label.changed?(:name).must_equal true }
27
+ # nothing changed after setup.
28
+ it do
29
+ form.changed?(:name).must_equal false
30
+ form.songs[0].changed?(:title).must_equal false
31
+ form.songs[0].composer.changed?(:name).must_equal false
32
+ end
64
33
 
65
- # not present key/value in #validate is no change.
66
- it { form.band.label.changed?(:location).must_equal false }
67
- # TODO: parent form changed when child has changed!
34
+ # after validate, things might have changed.
35
+ it do
36
+ form.validate("name" => "Out Of Bounds", "songs" => [{"composer" => {"name" => "Ingemar Jansson & Mikael Danielsson"}}])
37
+ form.changed?(:name).must_equal true
38
+ form.songs[0].changed?(:title).must_equal false
39
+ form.songs[0].composer.changed?(:name).must_equal true
68
40
  end
69
41
  end
@@ -9,20 +9,17 @@ class CoercionTest < BaseTest
9
9
  end
10
10
 
11
11
  class Form < Reform::Form
12
- include Coercion
12
+ # include Coercion
13
13
 
14
14
  property :released_at, :type => DateTime
15
15
 
16
16
  property :hit do
17
- # include Coercion
18
17
  property :length, :type => Integer
19
18
  property :good, :type => Virtus::Attribute::Boolean
20
19
  end
21
20
 
22
21
  property :band do
23
- include Coercion
24
22
  property :label do
25
- include Coercion
26
23
  property :value, :type => Irreversible
27
24
  end
28
25
  end
@@ -66,7 +63,4 @@ class CoercionTest < BaseTest
66
63
  end
67
64
 
68
65
  # save
69
-
70
-
71
-
72
66
  end
@@ -1,51 +1,145 @@
1
- class CompositionTest < ReformSpec
2
- class SongAndArtist < Reform::Composition
3
- map({:artist => [[:name]], :song => [[:title]]}) #SongAndArtistMap.representable_attrs
1
+ require 'test_helper'
2
+
3
+ class FormCompositionTest < MiniTest::Spec
4
+ Song = Struct.new(:id, :title, :band)
5
+ Requester = Struct.new(:id, :name, :requester)
6
+ Band = Struct.new(:title)
7
+
8
+ class RequestForm < Reform::Form
9
+ include Composition
10
+
11
+ property :name, :on => :requester
12
+ property :requester_id, :on => :requester, :from => :id
13
+ properties :title, :id, :on => :song
14
+ # property :channel # FIXME: what about the "main model"?
15
+ property :channel, :virtual => true, :on => :song
16
+ property :requester, :on => :requester
17
+ property :captcha, :on => :song, :virtual => true
18
+
19
+ validates :name, :title, :channel, :presence => true
20
+
21
+ property :band, :on => :song do
22
+ property :title
23
+ end
4
24
  end
5
25
 
6
- let (:comp) { SongAndArtist.new(:artist => @artist=OpenStruct.new, :song => rio) }
26
+ let (:form) { RequestForm.new(:song => song, :requester => requester) }
27
+ let (:song) { Song.new(1, "Rio", band) }
28
+ let (:requester) { Requester.new(2, "Duran Duran", "MCP") }
29
+ let (:band) { Band.new("Duran^2") }
30
+
31
+ # delegation form -> composition works
32
+ it { form.id.must_equal 1 }
33
+ it { form.title.must_equal "Rio" }
34
+ it { form.name.must_equal "Duran Duran" }
35
+ it { form.requester_id.must_equal 2 }
36
+ it { form.channel.must_equal nil }
37
+ it { form.requester.must_equal "MCP" } # same name as composed model.
38
+ it { form.captcha.must_equal nil }
39
+
40
+ # #model just returns <Composition>.
41
+ it { form.mapper.must_be_kind_of Disposable::Composition }
7
42
 
8
- it "delegates to models as defined" do
9
- comp.name.must_equal nil
10
- comp.title.must_equal "Rio"
43
+ # #model[] -> composed models
44
+ it { form.model[:requester].must_equal requester }
45
+ it { form.model[:song].must_equal song }
46
+
47
+
48
+ it "creates Composition for you" do
49
+ form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal false
11
50
  end
12
51
 
13
- it "raises when non-mapped property" do
14
- assert_raises NoMethodError do
15
- comp.raise_an_exception
52
+ describe "#save" do
53
+ # #save with {}
54
+ it do
55
+ hash = {}
56
+
57
+ form.save do |map|
58
+ hash[:name] = form.name
59
+ hash[:title] = form.title
60
+ end
61
+
62
+ hash.must_equal({:name=>"Duran Duran", :title=>"Rio"})
16
63
  end
17
- end
18
64
 
19
- describe "::from" do
20
- it "creates the same mapping" do
21
- comp =
22
- Reform::Composition.from(
23
- Class.new(Reform::Representer) do
24
- property :name, :on => :artist
25
- property :title, :on => :song
26
- end
27
- ).
28
- new(:artist => duran, :song => rio)
65
+ it "provides nested symbolized hash as second block argument" do
66
+ form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "channel" => "JJJ", "captcha" => "wonderful")
67
+
68
+ hash = nil
29
69
 
30
- comp.name.must_equal "Duran Duran"
31
- comp.title.must_equal "Rio"
70
+ form.save do |map|
71
+ hash = map
72
+ end
73
+
74
+ hash.must_equal({
75
+ :song=>{"title"=>"Greyhound", "id"=>1, "channel" => "JJJ", "captcha"=>"wonderful", "band"=>{"title"=>"Duran^2"}},
76
+ :requester=>{"name"=>"Frenzal Rhomb", "id"=>2, "requester" => "MCP"}
77
+ }
78
+ )
32
79
  end
33
- end
34
80
 
81
+ it "xxx pushes data to models and calls #save when no block passed" do
82
+ song.extend(Saveable)
83
+ requester.extend(Saveable)
84
+ band.extend(Saveable)
35
85
 
36
- describe "#nested_hash_for" do
37
- it "returns nested hash" do
38
- comp.nested_hash_for(:name => "Jimi Hendrix", :title => "Fire").must_equal({:artist=>{:name=>"Jimi Hendrix"}, :song=>{:title=>"Fire"}})
86
+ form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "captcha" => "1337")
87
+ form.captcha.must_equal "1337" # TODO: move to separate test.
88
+
89
+ form.save
90
+
91
+ requester.name.must_equal "Frenzal Rhomb"
92
+ requester.saved?.must_equal true
93
+ song.title.must_equal "Greyhound"
94
+ song.saved?.must_equal true
95
+ song.band.title.must_equal "Duran^2"
96
+ song.band.saved?.must_equal true
39
97
  end
40
98
 
41
- it "works with strings" do
42
- comp.nested_hash_for("name" => "Jimi Hendrix", "title" => "Fire").must_equal({:artist=>{:name=>"Jimi Hendrix"}, :song=>{:title=>"Fire"}})
99
+ it "returns true when models all save successfully" do
100
+ song.extend(Saveable)
101
+ requester.extend(Saveable)
102
+ band.extend(Saveable)
103
+
104
+ form.save.must_equal true
43
105
  end
44
106
 
45
- it "works with strings in map" do
46
- Class.new(Reform::Composition) do
47
- map(:artist => [["name"]])
48
- end.new({}).nested_hash_for(:name => "Jimi Hendrix").must_equal({:artist=>{:name=>"Jimi Hendrix"}})
107
+ it "returns false when one or more models don't save successfully" do
108
+ module Unsaveable
109
+ def save
110
+ false
111
+ end
112
+ end
113
+
114
+ song.extend(Unsaveable)
115
+ requester.extend(Saveable)
116
+ band.extend(Saveable)
117
+
118
+ form.save.must_equal false
49
119
  end
50
120
  end
51
- end
121
+ end
122
+
123
+
124
+ class FormCompositionCollectionTest < MiniTest::Spec
125
+ Book = Struct.new(:id, :name)
126
+ Library = Struct.new(:id) do
127
+ def books
128
+ [Book.new(1,"My book")]
129
+ end
130
+ end
131
+
132
+ class LibraryForm < Reform::Form
133
+ include Reform::Form::Composition
134
+
135
+ collection :books, on: :library do
136
+ property :id
137
+ property :name
138
+ end
139
+ end
140
+
141
+ let (:form) { LibraryForm.new(library: library) }
142
+ let (:library) { Library.new(2) }
143
+
144
+ it { form.save do |hash| hash.must_equal({:library=>{"books"=>[{"id"=>1, "name"=>"My book"}]}}) end }
145
+ end
@@ -1,105 +1,46 @@
1
1
  require 'test_helper'
2
2
 
3
- class ContractTest < BaseTest
4
- class AlbumContract < Reform::Contract
5
- property :title
6
- validates :title, :presence => true, :length => {:minimum => 3}
3
+ class ContractTest < MiniTest::Spec
4
+ Song = Struct.new(:title, :album, :composer)
5
+ Album = Struct.new(:name, :songs, :artist)
6
+ Artist = Struct.new(:name)
7
7
 
8
- property :hit do
9
- property :title
10
- validates :title, :presence => true
11
- end
8
+ class ArtistForm < Reform::Form
9
+ property :name
10
+ end
11
+
12
+ class AlbumForm < Reform::Contract
13
+ property :name
14
+ validates :name, presence: true
12
15
 
13
16
  collection :songs do
14
17
  property :title
15
- validates :title, :presence => true
16
- end
17
-
18
- validates :songs, :length => {:minimum => 4}
19
-
20
- property :band do # yepp, people do crazy stuff like that.
21
- validates :label, :presence => true
18
+ validates :title, presence: true
22
19
 
23
- property :label do
20
+ property :composer do
21
+ validates :name, presence: true
24
22
  property :name
25
- validates :name, :presence => true
26
23
  end
27
- # TODO: make band a required object.
28
24
  end
29
- end
30
-
31
- let (:album) { Album.new(nil, Song.new, [Song.new, Song.new], Band.new() ) }
32
- subject { AlbumContract.new(album) }
33
-
34
-
35
- describe "invalid" do
36
- before {
37
- res = subject.validate
38
- res.must_equal false
39
- }
40
-
41
- it { subject.errors.messages.must_equal({:"hit.title"=>["can't be blank"], :"songs.title"=>["can't be blank"], :"band.label"=>["can't be blank"], :songs=>["is too short (minimum is 4 characters)"], :title=>["can't be blank", "is too short (minimum is 3 characters)"]}) }
42
- end
43
25
 
44
-
45
- describe "valid" do
46
- let (:album) { Album.new(
47
- "Keeper Of The Seven Keys",
48
- nil,
49
- [Song.new("Initiation"), Song.new("I'm Alive"), Song.new("A Little Time"), Song.new("Future World"),],
50
- Band.new(Label.new("Noise"))
51
- ) }
52
-
53
- before { subject.validate.must_equal true }
54
-
55
- it { subject.errors.messages.must_equal({}) }
26
+ property :artist, form: ArtistForm
56
27
  end
57
28
 
29
+ let (:song) { Song.new("Broken") }
30
+ let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
31
+ let (:composer) { Artist.new("Greg Graffin") }
32
+ let (:artist) { Artist.new("Bad Religion") }
33
+ let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
58
34
 
59
- describe "::representer" do
60
- # without name will always iterate.
61
- it do
62
- names = []
63
- AlbumContract.representer { |dfn| names << dfn.name }
64
- names.must_equal ["hit", "songs", "band"]
65
-
66
- # this doesn't cache.
67
- names = []
68
- AlbumContract.representer { |dfn| names << dfn.name }
69
- names.must_equal ["hit", "songs", "band"]
70
- end
71
-
72
- # with name caches representer per class and runs once.
73
- it do
74
- names = []
75
- AlbumContract.representer(:sync) { |dfn| names << dfn.name }
76
- names.must_equal ["hit", "songs", "band"]
77
-
78
- # this does cache.
79
- names = []
80
- AlbumContract.representer(:sync) { |dfn| names << dfn.name }
81
- names.must_equal []
82
- end
83
-
84
- # it allows iterating all properties, not only nested.
85
- it do
86
- names = []
87
- AlbumContract.representer(:save, all: true) { |dfn| names << dfn.name }
88
- names.must_equal ["title", "hit", "songs", "band"]
89
-
90
- names = []
91
- AlbumContract.representer(:save, all: true) { |dfn| names << dfn.name }
92
- names.must_equal []
93
- end
94
-
95
- # test :superclass?
96
- end
35
+ let (:form) { AlbumForm.new(album) }
97
36
 
98
- class SongContract < Reform::Contract
99
- property :title, readable: true, type: String
37
+ # accept `property form: SongForm`.
38
+ it do
39
+ form.artist.must_be_instance_of ArtistForm
100
40
  end
101
41
 
102
42
  describe "#options_for" do
103
- it { SongContract.new(OpenStruct.new).options_for(:title)[:coercion_type].must_equal String }
43
+ it { AlbumForm.options_for(:name).inspect.must_match "#<Representable::Definition ==>name @options" }
44
+ it { AlbumForm.new(album).options_for(:name).inspect.must_match "#<Representable::Definition ==>name @options" }
104
45
  end
105
- end
46
+ end