representable 2.4.0.rc3 → 2.4.0.rc4
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/CHANGES.md +7 -2
- data/Rakefile +6 -0
- data/lib/representable.rb +21 -17
- data/lib/representable/binding.rb +3 -2
- data/lib/representable/definition.rb +1 -1
- data/lib/representable/deprecations.rb +31 -5
- data/lib/representable/deserializer.rb +24 -34
- data/lib/representable/hash/collection.rb +9 -2
- data/lib/representable/hash_methods.rb +2 -2
- data/lib/representable/parse_strategies.rb +6 -7
- data/lib/representable/pipeline.rb +12 -1
- data/lib/representable/pipeline_factories.rb +9 -2
- data/lib/representable/serializer.rb +3 -3
- data/lib/representable/version.rb +1 -1
- data/test-with-deprecations/as_test.rb +65 -0
- data/test-with-deprecations/benchmarking.rb +83 -0
- data/test-with-deprecations/binding_test.rb +46 -0
- data/test-with-deprecations/blaaaaaaaa_test.rb +69 -0
- data/test-with-deprecations/cached_test.rb +147 -0
- data/test-with-deprecations/class_test.rb +119 -0
- data/test-with-deprecations/coercion_test.rb +52 -0
- data/test-with-deprecations/config/inherit_test.rb +135 -0
- data/test-with-deprecations/config_test.rb +122 -0
- data/test-with-deprecations/decorator_scope_test.rb +28 -0
- data/test-with-deprecations/decorator_test.rb +96 -0
- data/test-with-deprecations/default_test.rb +34 -0
- data/test-with-deprecations/defaults_options_test.rb +93 -0
- data/test-with-deprecations/definition_test.rb +264 -0
- data/test-with-deprecations/example.rb +310 -0
- data/test-with-deprecations/examples/object.rb +31 -0
- data/test-with-deprecations/exec_context_test.rb +93 -0
- data/test-with-deprecations/features_test.rb +70 -0
- data/test-with-deprecations/filter_test.rb +57 -0
- data/test-with-deprecations/for_collection_test.rb +74 -0
- data/test-with-deprecations/generic_test.rb +116 -0
- data/test-with-deprecations/getter_setter_test.rb +21 -0
- data/test-with-deprecations/hash_bindings_test.rb +87 -0
- data/test-with-deprecations/hash_test.rb +160 -0
- data/test-with-deprecations/heritage_test.rb +62 -0
- data/test-with-deprecations/if_test.rb +79 -0
- data/test-with-deprecations/include_exclude_test.rb +88 -0
- data/test-with-deprecations/inherit_test.rb +159 -0
- data/test-with-deprecations/inline_test.rb +272 -0
- data/test-with-deprecations/instance_test.rb +266 -0
- data/test-with-deprecations/is_representable_test.rb +77 -0
- data/test-with-deprecations/json_test.rb +355 -0
- data/test-with-deprecations/lonely_test.rb +239 -0
- data/test-with-deprecations/mongoid_test.rb +31 -0
- data/test-with-deprecations/nested_test.rb +115 -0
- data/test-with-deprecations/object_test.rb +60 -0
- data/{test/---deserialize-pipeline_test.rb → test-with-deprecations/parse_pipeline_test.rb} +29 -2
- data/test-with-deprecations/parse_strategy_test.rb +279 -0
- data/{test → test-with-deprecations}/pass_options_test.rb +0 -0
- data/test-with-deprecations/pipeline_test.rb +277 -0
- data/test-with-deprecations/populator_test.rb +105 -0
- data/test-with-deprecations/prepare_test.rb +67 -0
- data/test-with-deprecations/private_options_test.rb +18 -0
- data/test-with-deprecations/reader_writer_test.rb +19 -0
- data/test-with-deprecations/realistic_benchmark.rb +115 -0
- data/test-with-deprecations/render_nil_test.rb +21 -0
- data/test-with-deprecations/represent_test.rb +88 -0
- data/test-with-deprecations/representable_test.rb +511 -0
- data/test-with-deprecations/schema_test.rb +148 -0
- data/test-with-deprecations/serialize_deserialize_test.rb +33 -0
- data/test-with-deprecations/skip_test.rb +81 -0
- data/test-with-deprecations/stringify_hash_test.rb +41 -0
- data/test-with-deprecations/test_helper.rb +135 -0
- data/test-with-deprecations/test_helper_test.rb +25 -0
- data/test-with-deprecations/uncategorized_test.rb +67 -0
- data/test-with-deprecations/user_options_test.rb +15 -0
- data/test-with-deprecations/wrap_test.rb +152 -0
- data/test-with-deprecations/xml_bindings_test.rb +62 -0
- data/test-with-deprecations/xml_test.rb +503 -0
- data/test-with-deprecations/yaml_test.rb +162 -0
- data/test/as_test.rb +3 -3
- data/test/cached_test.rb +2 -2
- data/test/class_test.rb +5 -5
- data/test/exec_context_test.rb +2 -2
- data/test/filter_test.rb +1 -1
- data/test/getter_setter_test.rb +4 -4
- data/test/if_test.rb +2 -2
- data/test/include_exclude_test.rb +88 -0
- data/test/instance_test.rb +15 -15
- data/test/lonely_test.rb +18 -2
- data/test/object_test.rb +4 -4
- data/test/parse_pipeline_test.rb +64 -0
- data/test/parse_strategy_test.rb +3 -3
- data/test/pipeline_test.rb +8 -12
- data/test/prepare_test.rb +2 -3
- data/test/reader_writer_test.rb +3 -3
- data/test/representable_test.rb +12 -48
- data/test/serialize_deserialize_test.rb +9 -9
- data/test/skip_test.rb +11 -11
- data/test/test_helper.rb +2 -0
- data/test/uncategorized_test.rb +10 -10
- data/test/user_options_test.rb +15 -0
- data/test/wrap_test.rb +1 -1
- metadata +65 -4
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'mongoid'
|
3
|
+
require 'mongoid/document'
|
4
|
+
|
5
|
+
class MongoidTest < MiniTest::Spec
|
6
|
+
describe "Mongoid compatibility" do
|
7
|
+
it "allows #to_json" do
|
8
|
+
class Profile
|
9
|
+
include Mongoid::Document
|
10
|
+
field :name
|
11
|
+
end
|
12
|
+
|
13
|
+
class Dude
|
14
|
+
include Mongoid::Document
|
15
|
+
embeds_one :profile, :class_name => "MongoidTest::Profile"
|
16
|
+
end
|
17
|
+
|
18
|
+
module ProfileRepresenter
|
19
|
+
include Representable::JSON
|
20
|
+
|
21
|
+
property :name
|
22
|
+
end
|
23
|
+
|
24
|
+
dude = Dude.new
|
25
|
+
dude.profile = Profile.new
|
26
|
+
dude.profile.name = "Kofi"
|
27
|
+
|
28
|
+
assert_equal "{\"name\":\"Kofi\"}", dude.profile.extend(ProfileRepresenter).to_json
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class NestedTest < MiniTest::Spec
|
4
|
+
Album = Struct.new(:label, :owner, :amount)
|
5
|
+
|
6
|
+
for_formats(
|
7
|
+
:hash => [Representable::Hash, {"label" => {"label"=>"Epitaph", "owner"=>"Brett Gurewitz", "releases"=>{"amount"=>19}}}],
|
8
|
+
# :xml => [Representable::XML, "<open_struct></open_struct>"],
|
9
|
+
:yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n releases:\n amount: 19\n"]
|
10
|
+
) do |format, mod, output, input|
|
11
|
+
|
12
|
+
[false, true].each do |is_decorator|
|
13
|
+
describe "::nested with (inline representer|decorator): #{is_decorator}" do
|
14
|
+
let (:format) { format }
|
15
|
+
|
16
|
+
representer!(:module => mod, :decorator => is_decorator) do
|
17
|
+
nested :label do
|
18
|
+
property :label
|
19
|
+
property :owner
|
20
|
+
|
21
|
+
# self.representation_wrap = nil if format == :xml
|
22
|
+
nested :releases do
|
23
|
+
property :amount
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# self.representation_wrap = :album if format == :xml
|
28
|
+
end
|
29
|
+
|
30
|
+
let (:album) { Album.new("Epitaph", "Brett Gurewitz", 19) }
|
31
|
+
let (:decorator) { representer.prepare(album) }
|
32
|
+
|
33
|
+
it "renders nested Album-properties in separate section" do
|
34
|
+
render(decorator).must_equal_document output
|
35
|
+
|
36
|
+
# do not use extend on the nested object. # FIXME: make this a proper test with two describes instead of this pseudo-meta stuff.
|
37
|
+
if is_decorator==true
|
38
|
+
album.wont_be_kind_of(Representable::Hash)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "parses nested properties to Album instance" do
|
43
|
+
album = parse(representer.prepare(Album.new), output)
|
44
|
+
album.label.must_equal "Epitaph"
|
45
|
+
album.owner.must_equal "Brett Gurewitz"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
describe "Decorator ::nested with extend:" do
|
52
|
+
let (:format) { format }
|
53
|
+
|
54
|
+
representer!(:name => :label_rpr) do
|
55
|
+
include mod
|
56
|
+
property :label
|
57
|
+
property :owner
|
58
|
+
|
59
|
+
nested :releases do # DISCUSS: do we need to test this?
|
60
|
+
property :amount
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
representer!(:module => mod, :decorator => true, :inject => :label_rpr) do
|
65
|
+
nested :label, :extend => label_rpr
|
66
|
+
|
67
|
+
self.representation_wrap = :album if format == :xml
|
68
|
+
end
|
69
|
+
|
70
|
+
let (:album) { representer.prepare(Album.new("Epitaph", "Brett Gurewitz", 19)) }
|
71
|
+
|
72
|
+
# TODO: shared example with above.
|
73
|
+
it "renders nested Album-properties in separate section" do
|
74
|
+
render(album).must_equal_document output
|
75
|
+
end
|
76
|
+
|
77
|
+
it "parses nested properties to Album instance" do
|
78
|
+
album = parse(representer.prepare(Album.new), output)
|
79
|
+
album.label.must_equal "Epitaph"
|
80
|
+
album.owner.must_equal "Brett Gurewitz"
|
81
|
+
album.amount.must_equal 19
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
describe "::nested without block but with inherit:" do
|
88
|
+
|
89
|
+
representer!(:name => :parent) do
|
90
|
+
include Representable::Hash
|
91
|
+
|
92
|
+
nested :label do
|
93
|
+
property :owner
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
representer!(:module => Representable::Hash, :inject => :parent) do
|
98
|
+
include parent
|
99
|
+
nested :label, :inherit => true, :as => "Label"
|
100
|
+
end
|
101
|
+
|
102
|
+
let (:album) { representer.prepare(Album.new("Epitaph", "Brett Gurewitz", 19)) }
|
103
|
+
|
104
|
+
it "renders nested Album-properties in separate section" do
|
105
|
+
representer.prepare(album).to_hash.must_equal({"Label"=>{"owner"=>"Brett Gurewitz"}})
|
106
|
+
end
|
107
|
+
|
108
|
+
# it "parses nested properties to Album instance" do
|
109
|
+
# album = parse(representer.prepare(Album.new), output)
|
110
|
+
# album.label.must_equal "Epitaph"
|
111
|
+
# album.owner.must_equal "Brett Gurewitz"
|
112
|
+
# album.amount.must_equal 19
|
113
|
+
# end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "representable/object"
|
3
|
+
|
4
|
+
class ObjectTest < MiniTest::Spec
|
5
|
+
Song = Struct.new(:title, :album)
|
6
|
+
Album = Struct.new(:name, :songs)
|
7
|
+
|
8
|
+
representer!(module: Representable::Object) do
|
9
|
+
property :title
|
10
|
+
|
11
|
+
property :album, instance: lambda { |fragment, *| fragment.name.upcase!; fragment } do
|
12
|
+
property :name
|
13
|
+
|
14
|
+
collection :songs, instance: lambda { |fragment, *| fragment.title.upcase!; fragment } do
|
15
|
+
property :title
|
16
|
+
end
|
17
|
+
end
|
18
|
+
# TODO: collection
|
19
|
+
end
|
20
|
+
|
21
|
+
let (:source) { Song.new("The King Is Dead", Album.new("Ruiner", [Song.new("In Vino Veritas II")])) }
|
22
|
+
let (:target) { Song.new }
|
23
|
+
|
24
|
+
it do
|
25
|
+
representer.prepare(target).from_object(source)
|
26
|
+
|
27
|
+
target.title.must_equal "The King Is Dead"
|
28
|
+
target.album.name.must_equal "RUINER"
|
29
|
+
target.album.songs[0].title.must_equal "IN VINO VERITAS II"
|
30
|
+
end
|
31
|
+
|
32
|
+
# ignore nested object when nil
|
33
|
+
it do
|
34
|
+
representer.prepare(Song.new("The King Is Dead")).from_object(Song.new)
|
35
|
+
|
36
|
+
target.title.must_equal nil # scalar property gets overridden when nil.
|
37
|
+
target.album.must_equal nil # nested property stays nil.
|
38
|
+
end
|
39
|
+
|
40
|
+
# to_object
|
41
|
+
describe "#to_object" do
|
42
|
+
representer!(module: Representable::Object) do
|
43
|
+
property :title
|
44
|
+
|
45
|
+
property :album, render_filter: lambda { |object, *| object.name = "Live"; object } do
|
46
|
+
property :name
|
47
|
+
|
48
|
+
collection :songs, render_filter: lambda { |object, *| object[0].title = 1; object } do
|
49
|
+
property :title
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it do
|
55
|
+
representer.prepare(source).to_object
|
56
|
+
source.album.name.must_equal "Live"
|
57
|
+
source.album.songs[0].title.must_equal 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -1,10 +1,37 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
|
-
class
|
4
|
-
Album = Struct.new(:artist, :songs)
|
3
|
+
class ParsePipelineTest < MiniTest::Spec
|
4
|
+
Album = Struct.new(:id, :artist, :songs)
|
5
5
|
Artist = Struct.new(:email)
|
6
6
|
Song = Struct.new(:title)
|
7
7
|
|
8
|
+
describe "transforming nil to [] when parsing" do
|
9
|
+
representer!(decorator: true) do
|
10
|
+
collection :songs,
|
11
|
+
parse_pipeline: ->(*) {
|
12
|
+
Representable::Pipeline.insert(
|
13
|
+
parse_functions, # original function list from Binding#parse_functions.
|
14
|
+
->(input, options) { input.nil? ? [] : input }, # your new function (can be any callable object)..
|
15
|
+
replace: Representable::OverwriteOnNil # ..that replaces the original function.
|
16
|
+
)
|
17
|
+
},
|
18
|
+
class: Song do
|
19
|
+
property :title
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it do
|
24
|
+
representer.new(album = Album.new).from_hash("songs"=>nil)
|
25
|
+
album.songs.must_equal []
|
26
|
+
end
|
27
|
+
|
28
|
+
it do
|
29
|
+
representer.new(album = Album.new).from_hash("songs"=>[{"title" => "Business Conduct"}])
|
30
|
+
album.songs.must_equal [Song.new("Business Conduct")]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
8
35
|
# tests [Collect[Instance, Prepare, Deserialize], Setter]
|
9
36
|
class Representer < Representable::Decorator
|
10
37
|
include Representable::Hash
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
# parse_strategy: :sync
|
4
|
+
# parse_strategy: :replace
|
5
|
+
# parse_strategy: :find_or_instantiate ("expand" since we don't delete existing unmatched in target)
|
6
|
+
|
7
|
+
|
8
|
+
class ParseStrategySyncTest < BaseTest
|
9
|
+
for_formats(
|
10
|
+
:hash => [Representable::Hash, {"song"=>{"title"=>"Resist Stance"}}, {"song"=>{"title"=>"Suffer"}}],
|
11
|
+
:xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><song><title>Suffer</title></song></open_struct>",],
|
12
|
+
:yaml => [Representable::YAML, "---\nsong:\n title: Resist Stance\n", "---\nsong:\n title: Suffer\n"],
|
13
|
+
) do |format, mod, output, input|
|
14
|
+
|
15
|
+
describe "[#{format}] property with parse_strategy: :sync" do # TODO: introduce :representable option?
|
16
|
+
let (:format) { format }
|
17
|
+
|
18
|
+
representer!(:module => mod, :name => :song_representer) do
|
19
|
+
property :title
|
20
|
+
self.representation_wrap = :song if format == :xml
|
21
|
+
end
|
22
|
+
|
23
|
+
representer!(:inject => :song_representer, :module => mod) do
|
24
|
+
property :song, :parse_strategy => :sync, :extend => song_representer
|
25
|
+
end
|
26
|
+
|
27
|
+
let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
|
28
|
+
|
29
|
+
it "calls #to_hash on song instance, nothing else" do
|
30
|
+
render(hit).must_equal_document(output)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
it "calls #from_hash on the existing song instance, nothing else" do
|
35
|
+
song_id = hit.song.object_id
|
36
|
+
|
37
|
+
parse(hit, input)
|
38
|
+
|
39
|
+
hit.song.title.must_equal "Suffer"
|
40
|
+
hit.song.object_id.must_equal song_id
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# FIXME: there's a bug with XML and the collection name!
|
46
|
+
for_formats(
|
47
|
+
:hash => [Representable::Hash, {"songs"=>[{"title"=>"Resist Stance"}]}, {"songs"=>[{"title"=>"Suffer"}]}],
|
48
|
+
#:json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"],
|
49
|
+
:xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><songs><title>Suffer</title></songs></open_struct>"],
|
50
|
+
:yaml => [Representable::YAML, "---\nsongs:\n- title: Resist Stance\n", "---\nsongs:\n- title: Suffer\n"],
|
51
|
+
) do |format, mod, output, input|
|
52
|
+
|
53
|
+
describe "[#{format}] collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
|
54
|
+
let (:format) { format }
|
55
|
+
representer!(:module => mod, :name => :song_representer) do
|
56
|
+
property :title
|
57
|
+
self.representation_wrap = :song if format == :xml
|
58
|
+
end
|
59
|
+
|
60
|
+
representer!(:inject => :song_representer, :module => mod) do
|
61
|
+
collection :songs, :parse_strategy => :sync, :extend => song_representer
|
62
|
+
end
|
63
|
+
|
64
|
+
let (:album) { OpenStruct.new(:songs => [song]).extend(representer) }
|
65
|
+
|
66
|
+
it "calls #to_hash on song instances, nothing else" do
|
67
|
+
render(album).must_equal_document(output)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "calls #from_hash on the existing song instance, nothing else" do
|
71
|
+
collection_id = album.songs.object_id
|
72
|
+
song = album.songs.first
|
73
|
+
song_id = song.object_id
|
74
|
+
|
75
|
+
parse(album, input)
|
76
|
+
|
77
|
+
album.songs.first.title.must_equal "Suffer"
|
78
|
+
song.title.must_equal "Suffer"
|
79
|
+
#album.songs.object_id.must_equal collection_id # TODO: don't replace!
|
80
|
+
song.object_id.must_equal song_id
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Sync errors, when model and incoming are not in sync.
|
87
|
+
describe ":sync with error" do
|
88
|
+
representer! do
|
89
|
+
property :song, :parse_strategy => :sync do
|
90
|
+
property :title
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# object.song is nil whereas the document contains one.
|
95
|
+
it do
|
96
|
+
assert_raises Representable::DeserializeError do
|
97
|
+
OpenStruct.new.extend(representer).from_hash({"song" => {"title" => "Perpetual"}})
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
# Lonely Collection
|
105
|
+
for_formats(
|
106
|
+
:hash => [Representable::Hash::Collection, [{"title"=>"Resist Stance"}], [{"title"=>"Suffer"}]],
|
107
|
+
# :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><songs><title>Suffer</title></songs></open_struct>"],
|
108
|
+
) do |format, mod, output, input|
|
109
|
+
|
110
|
+
describe "[#{format}] lonely collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
|
111
|
+
let (:format) { format }
|
112
|
+
representer!(:module => Representable::Hash, :name => :song_representer) do
|
113
|
+
property :title
|
114
|
+
self.representation_wrap = :song if format == :xml
|
115
|
+
end
|
116
|
+
|
117
|
+
representer!(:inject => :song_representer, :module => mod) do
|
118
|
+
items :parse_strategy => :sync, :extend => song_representer
|
119
|
+
end
|
120
|
+
|
121
|
+
let (:album) { [song].extend(representer) }
|
122
|
+
|
123
|
+
it "calls #to_hash on song instances, nothing else" do
|
124
|
+
render(album).must_equal_document(output)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "calls #from_hash on the existing song instance, nothing else" do
|
128
|
+
#collection_id = album.object_id
|
129
|
+
song = album.first
|
130
|
+
song_id = song.object_id
|
131
|
+
|
132
|
+
parse(album, input)
|
133
|
+
|
134
|
+
album.first.title.must_equal "Suffer"
|
135
|
+
song.title.must_equal "Suffer"
|
136
|
+
song.object_id.must_equal song_id
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
class ParseStrategyFindOrInstantiateTest < BaseTest
|
144
|
+
# parse_strategy: :find_or_instantiate
|
145
|
+
|
146
|
+
Song = Struct.new(:id, :title)
|
147
|
+
Song.class_eval do
|
148
|
+
def self.find_by(attributes={})
|
149
|
+
return new(1, "Resist Stan") if attributes[:id]==1# we should return the same object here
|
150
|
+
new
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
representer!(:name => :song_representer) do
|
155
|
+
property :title
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
describe "collection" do
|
160
|
+
representer!(:inject => :song_representer) do
|
161
|
+
collection :songs, :parse_strategy => :find_or_instantiate, :extend => song_representer, :class => Song
|
162
|
+
end
|
163
|
+
|
164
|
+
let (:album) { Struct.new(:songs).new([]).extend(representer) }
|
165
|
+
|
166
|
+
|
167
|
+
it "replaces the existing collection with a new consisting of existing items or new items" do
|
168
|
+
songs_id = album.songs.object_id
|
169
|
+
|
170
|
+
album.from_hash({"songs"=>[{"id" => 1, "title"=>"Resist Stance"}, {"title"=>"Suffer"}]})
|
171
|
+
|
172
|
+
album.songs[0].title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
|
173
|
+
album.songs[0].id.must_equal 1
|
174
|
+
album.songs[1].title.must_equal "Suffer"
|
175
|
+
album.songs[1].id.must_equal nil
|
176
|
+
|
177
|
+
album.songs.object_id.wont_equal songs_id
|
178
|
+
end
|
179
|
+
|
180
|
+
# TODO: test with existing collection
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
describe "property" do
|
185
|
+
representer!(:inject => :song_representer) do
|
186
|
+
property :song, :parse_strategy => :find_or_instantiate, :extend => song_representer, :class => Song
|
187
|
+
end
|
188
|
+
|
189
|
+
let (:album) { Struct.new(:song).new.extend(representer) }
|
190
|
+
|
191
|
+
|
192
|
+
it "finds song by id" do
|
193
|
+
album.from_hash({"song"=>{"id" => 1, "title"=>"Resist Stance"}})
|
194
|
+
|
195
|
+
album.song.title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
|
196
|
+
album.song.id.must_equal 1
|
197
|
+
end
|
198
|
+
|
199
|
+
it "creates song" do
|
200
|
+
album.from_hash({"song"=>{"title"=>"Off The Track"}})
|
201
|
+
|
202
|
+
album.song.title.must_equal "Off The Track"
|
203
|
+
album.song.id.must_equal nil
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
describe "property with dynamic :class" do
|
209
|
+
representer!(:inject => :song_representer) do
|
210
|
+
property :song, :parse_strategy => :find_or_instantiate, :extend => song_representer,
|
211
|
+
:class => lambda { |fragment, *args| fragment["class"] }
|
212
|
+
end
|
213
|
+
|
214
|
+
let (:album) { Struct.new(:song).new.extend(representer) }
|
215
|
+
|
216
|
+
|
217
|
+
it "finds song by id" do
|
218
|
+
album.from_hash({"song"=>{"id" => 1, "title"=>"Resist Stance", "class"=>Song}})
|
219
|
+
|
220
|
+
album.song.title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
|
221
|
+
album.song.id.must_equal 1
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
class ParseStrategyLambdaTest < MiniTest::Spec
|
228
|
+
Song = Struct.new(:id, :title)
|
229
|
+
Song.class_eval do
|
230
|
+
def self.find_by(attributes={})
|
231
|
+
return new(1, "Resist Stan") if attributes[:id]==1# we should return the same object here
|
232
|
+
new
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
representer!(:name => :song_representer) do
|
237
|
+
property :title
|
238
|
+
end
|
239
|
+
|
240
|
+
# property with instance: lambda, using representable's setter. # TODO: that should be handled better via my api.
|
241
|
+
describe "property parse_strategy: lambda, representable: false" do
|
242
|
+
representer! do
|
243
|
+
property :title,
|
244
|
+
:instance => lambda { |fragment, options| fragment.to_s }, # this will still call song.title= "8675309".
|
245
|
+
:representable => false # don't call object.from_hash
|
246
|
+
end
|
247
|
+
|
248
|
+
let (:song) { Song.new(nil, nil) }
|
249
|
+
it { song.extend(representer).from_hash("title" => 8675309).title.must_equal "8675309" }
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
describe "collection" do
|
254
|
+
representer!(:inject => :song_representer) do
|
255
|
+
collection :songs, :parse_strategy => lambda { |fragment, i, options|
|
256
|
+
songs << song = Song.new
|
257
|
+
song
|
258
|
+
}, :extend => song_representer
|
259
|
+
end
|
260
|
+
|
261
|
+
let (:album) { Struct.new(:songs).new([Song.new(1, "A Walk")]).extend(representer) }
|
262
|
+
|
263
|
+
|
264
|
+
it "adds to existing collection" do
|
265
|
+
songs_id = album.songs.object_id
|
266
|
+
|
267
|
+
album.from_hash({"songs"=>[{"title"=>"Resist Stance"}]})
|
268
|
+
|
269
|
+
album.songs[0].title.must_equal "A Walk" # note how title is updated from "Resist Stan"
|
270
|
+
album.songs[0].id.must_equal 1
|
271
|
+
album.songs[1].title.must_equal "Resist Stance"
|
272
|
+
album.songs[1].id.must_equal nil
|
273
|
+
|
274
|
+
album.songs.object_id.must_equal songs_id
|
275
|
+
end
|
276
|
+
|
277
|
+
# TODO: test with existing collection
|
278
|
+
end
|
279
|
+
end
|