representable 1.6.1 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,7 +12,7 @@ module Representable::Hash
12
12
 
13
13
  module ClassMethods
14
14
  def items(options)
15
- collection :_self, options
15
+ collection :_self, options.merge(:getter => lambda { |*| self })
16
16
  end
17
17
  end
18
18
 
@@ -0,0 +1,18 @@
1
+ require "representable/deserializer"
2
+
3
+ module Representable
4
+ class ObjectSerializer < ObjectDeserializer
5
+ def call
6
+ return @object if @object.nil?
7
+
8
+ representable = prepare(@object)
9
+
10
+ serialize(representable, @binding.user_options)
11
+ end
12
+
13
+ private
14
+ def serialize(object, user_options)
15
+ object.send(@binding.serialize_method, user_options.merge!({:wrap => false}))
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.6.1"
2
+ VERSION = "1.7.0"
3
3
  end
@@ -0,0 +1,104 @@
1
+ require 'test_helper'
2
+
3
+ class ConfigTest < MiniTest::Spec
4
+ subject { Representable::Config.new }
5
+ PunkRock = Class.new
6
+
7
+ let (:definition) { Representable::Definition.new(:title) }
8
+
9
+ describe "wrapping" do
10
+ it "returns false per default" do
11
+ assert_equal nil, subject.wrap_for("Punk")
12
+ end
13
+
14
+ it "infers a printable class name if set to true" do
15
+ subject.wrap = true
16
+ assert_equal "punk_rock", subject.wrap_for(PunkRock)
17
+ end
18
+
19
+ it "can be set explicitely" do
20
+ subject.wrap = "Descendents"
21
+ assert_equal "Descendents", subject.wrap_for(PunkRock)
22
+ end
23
+ end
24
+
25
+ describe "#cloned" do
26
+ it "clones all definitions" do
27
+ subject << obj = definition
28
+
29
+ subject.cloned.map(&:name).must_equal ["title"]
30
+ subject.cloned.first.object_id.wont_equal obj.object_id
31
+ end
32
+ end
33
+
34
+ describe "#<<" do
35
+ it "returns Definition" do
36
+ (subject << definition).must_equal definition
37
+ end
38
+ end
39
+
40
+ describe "#[]" do
41
+ before { subject << definition }
42
+
43
+ it { subject[:unknown].must_equal nil }
44
+ it { subject[:title].must_equal definition }
45
+ it { subject["title"].must_equal definition }
46
+ end
47
+
48
+ describe "Config inheritance" do
49
+ # TODO: this section will soon be moved to uber.
50
+ describe "inheritance when including" do
51
+ # TODO: test all the below issues AND if cloning works.
52
+ module TestMethods
53
+ def representer_for(modules=[Representable], &block)
54
+ Module.new do
55
+ extend TestMethods
56
+ include *modules
57
+ module_exec(&block)
58
+ end
59
+ end
60
+ end
61
+ include TestMethods
62
+
63
+ it "inherits to uninitialized child" do
64
+ representer_for do # child
65
+ include(representer_for do # parent
66
+ representable_attrs.inheritable_array(:links) << "bar"
67
+ end)
68
+ end.representable_attrs.inheritable_array(:links).must_equal(["bar"])
69
+ end
70
+
71
+ it "works with uninitialized parent" do
72
+ representer_for do # child
73
+ representable_attrs.inheritable_array(:links) << "bar"
74
+
75
+ include(representer_for do # parent
76
+ end)
77
+ end.representable_attrs.inheritable_array(:links).must_equal(["bar"])
78
+ end
79
+
80
+ it "inherits when both are initialized" do
81
+ representer_for do # child
82
+ representable_attrs.inheritable_array(:links) << "bar"
83
+
84
+ include(representer_for do # parent
85
+ representable_attrs.inheritable_array(:links) << "stadium"
86
+ end)
87
+ end.representable_attrs.inheritable_array(:links).must_equal(["bar", "stadium"])
88
+ end
89
+
90
+ it "clones parent inheritables" do # FIXME: actually we don't clone here!
91
+ representer_for do # child
92
+ representable_attrs.inheritable_array(:links) << "bar"
93
+
94
+ include(parent = representer_for do # parent
95
+ representable_attrs.inheritable_array(:links) << "stadium"
96
+ end)
97
+
98
+ parent.representable_attrs.inheritable_array(:links) << "park" # modify parent array.
99
+
100
+ end.representable_attrs.inheritable_array(:links).must_equal(["bar", "stadium"])
101
+ end
102
+ end
103
+ end
104
+ end
@@ -5,23 +5,23 @@ class DefinitionTest < MiniTest::Spec
5
5
  before do
6
6
  @def = Representable::Definition.new(:songs)
7
7
  end
8
-
8
+
9
9
  describe "DCI" do
10
10
  it "responds to #representer_module" do
11
11
  assert_equal nil, Representable::Definition.new(:song).representer_module
12
12
  assert_equal Hash, Representable::Definition.new(:song, :extend => Hash).representer_module
13
13
  end
14
14
  end
15
-
15
+
16
16
  describe "#typed?" do
17
17
  it "is false per default" do
18
18
  assert ! @def.typed?
19
19
  end
20
-
20
+
21
21
  it "is true when :class is present" do
22
22
  assert Representable::Definition.new(:songs, :class => Hash).typed?
23
23
  end
24
-
24
+
25
25
  it "is true when :extend is present, only" do
26
26
  assert Representable::Definition.new(:songs, :extend => Hash).typed?
27
27
  end
@@ -30,109 +30,109 @@ class DefinitionTest < MiniTest::Spec
30
30
  assert Representable::Definition.new(:songs, :instance => Object.new).typed?
31
31
  end
32
32
  end
33
-
33
+
34
34
  it "responds to #getter and returns string" do
35
35
  assert_equal "songs", @def.getter
36
36
  end
37
-
37
+
38
38
  it "responds to #name" do
39
- assert_equal "songs", @def.name
39
+ assert_equal "songs", @def.name
40
40
  end
41
-
41
+
42
42
  it "responds to #setter" do
43
43
  assert_equal :"songs=", @def.setter
44
44
  end
45
-
45
+
46
46
  it "responds to #sought_type" do
47
47
  assert_equal nil, @def.sought_type
48
48
  end
49
-
49
+
50
50
  describe "#clone" do
51
51
  it "clones @options" do
52
52
  @def.options[:volume] = 9
53
53
  cloned = @def.clone
54
54
  cloned.options[:volume] = 8
55
-
55
+
56
56
  assert_equal @def.options[:volume], 9
57
57
  assert_equal cloned.options[:volume], 8
58
58
  end
59
59
  end
60
60
  end
61
-
61
+
62
62
  describe "#has_default?" do
63
63
  it "returns false if no :default set" do
64
64
  assert_equal false, Representable::Definition.new(:song).has_default?
65
65
  end
66
-
66
+
67
67
  it "returns true if :default set" do
68
68
  assert_equal true, Representable::Definition.new(:song, :default => nil).has_default?
69
69
  end
70
-
70
+
71
71
  it "returns true if :collection" do
72
72
  assert_equal true, Representable::Definition.new(:songs, :collection => true).has_default?
73
73
  end
74
-
74
+
75
75
  end
76
-
77
-
76
+
77
+
78
78
  describe "#skipable_nil_value?" do
79
79
  # default if skipable_nil_value?
80
80
  before do
81
81
  @def = Representable::Definition.new(:song, :render_nil => true)
82
82
  end
83
-
83
+
84
84
  it "returns false when not nil" do
85
85
  assert_equal false, @def.skipable_nil_value?("Disconnect, Disconnect")
86
86
  end
87
-
87
+
88
88
  it "returns false when nil and :render_nil => true" do
89
89
  assert_equal false, @def.skipable_nil_value?(nil)
90
90
  end
91
-
91
+
92
92
  it "returns true when nil and :render_nil => false" do
93
93
  assert_equal true, Representable::Definition.new(:song).skipable_nil_value?(nil)
94
94
  end
95
-
95
+
96
96
  it "returns false when not nil and :render_nil => false" do
97
97
  assert_equal false, Representable::Definition.new(:song).skipable_nil_value?("Fatal Flu")
98
98
  end
99
99
  end
100
-
101
-
100
+
101
+
102
102
  describe "#default_for" do
103
103
  before do
104
104
  @def = Representable::Definition.new(:song, :default => "Insider")
105
105
  end
106
-
106
+
107
107
  it "always returns value when value not nil" do
108
108
  assert_equal "Black And Blue", @def.default_for("Black And Blue")
109
109
  end
110
-
110
+
111
111
  it "returns false when value false" do
112
112
  assert_equal false, @def.default_for(false)
113
113
  end
114
-
114
+
115
115
  it "returns default when value nil" do
116
116
  assert_equal "Insider", @def.default_for(nil)
117
117
  end
118
-
118
+
119
119
  it "returns nil when value nil and :render_nil true" do
120
120
  @def = Representable::Definition.new(:song, :render_nil => true)
121
121
  assert_equal nil, @def.default_for(nil)
122
122
  end
123
-
123
+
124
124
  it "returns nil when value nil and :render_nil true even when :default is set" do
125
125
  @def = Representable::Definition.new(:song, :render_nil => true, :default => "The Quest")
126
126
  assert_equal nil, @def.default_for(nil)
127
127
  end
128
-
128
+
129
129
  it "returns nil if no :default" do
130
130
  @def = Representable::Definition.new(:song)
131
131
  assert_equal nil, @def.default_for(nil)
132
132
  end
133
133
  end
134
-
135
-
134
+
135
+
136
136
  describe "#writeable?" do
137
137
 
138
138
  it "returns true when :writeable is not given" do
@@ -154,7 +154,7 @@ class DefinitionTest < MiniTest::Spec
154
154
  @def = Representable::Definition.new(:song, :writeable => nil)
155
155
  assert_equal nil, @def.writeable?
156
156
  end
157
-
157
+
158
158
  end
159
159
 
160
160
  describe "#readable?" do
@@ -203,47 +203,47 @@ class DefinitionTest < MiniTest::Spec
203
203
  before do
204
204
  @def = Representable::Definition.new(:songs, :collection => true, :tag => :song)
205
205
  end
206
-
206
+
207
207
  it "responds to #array?" do
208
208
  assert @def.array?
209
209
  end
210
-
210
+
211
211
  it "responds to #sought_type" do
212
212
  assert_equal nil, @def.sought_type
213
213
  end
214
-
214
+
215
215
  it "responds to #default" do
216
216
  assert_equal [], @def.send(:default)
217
217
  end
218
218
  end
219
-
219
+
220
220
  describe ":class => Item" do
221
221
  before do
222
222
  @def = Representable::Definition.new(:songs, :class => Hash)
223
223
  end
224
-
224
+
225
225
  it "responds to #sought_type" do
226
226
  assert_equal Hash, @def.sought_type
227
227
  end
228
228
  end
229
-
229
+
230
230
  describe ":default => value" do
231
231
  it "responds to #default" do
232
232
  @def = Representable::Definition.new(:song)
233
233
  assert_equal nil, @def.send(:default)
234
234
  end
235
-
235
+
236
236
  it "accepts a default value" do
237
237
  @def = Representable::Definition.new(:song, :default => "Atheist Peace")
238
238
  assert_equal "Atheist Peace", @def.send(:default)
239
239
  end
240
240
  end
241
-
241
+
242
242
  describe ":hash => true" do
243
243
  before do
244
244
  @def = Representable::Definition.new(:songs, :hash => true)
245
245
  end
246
-
246
+
247
247
  it "responds to #hash?" do
248
248
  assert @def.hash?
249
249
  assert ! Representable::Definition.new(:songs).hash?
@@ -259,4 +259,9 @@ class DefinitionTest < MiniTest::Spec
259
259
  assert_equal subject.binding, Object
260
260
  end
261
261
  end
262
+
263
+ describe "#sync?" do
264
+ it { Representable::Definition.new(:song).sync?.must_equal false }
265
+ it { Representable::Definition.new(:song, :parse_strategy => :sync).sync?.must_equal true }
266
+ end
262
267
  end
@@ -4,7 +4,7 @@ class GenericTest < MiniTest::Spec
4
4
  # one day, this file will contain all engine-independent test cases. one day...
5
5
  let (:new_album) { OpenStruct.new.extend(representer) }
6
6
  let (:album) { OpenStruct.new(:songs => ["Fuck Armageddon"]).extend(representer) }
7
- let (:song) { OpenStruct.new(:title => "Resist Stance").extend(song_representer) }
7
+ let (:song) { OpenStruct.new(:title => "Resist Stance") }
8
8
  let (:song_representer) { Module.new do include Representable::Hash; property :title end }
9
9
 
10
10
 
@@ -25,12 +25,12 @@ class GenericTest < MiniTest::Spec
25
25
  end
26
26
 
27
27
 
28
- describe ":representable with property" do # TODO: introduce :representable option?
29
- representer! do
30
- property :song, :instance => lambda { |*| nil }
28
+ describe "property with instance: { nil }" do # TODO: introduce :representable option?
29
+ representer!(:inject => :song_representer) do
30
+ property :song, :instance => lambda { |*| nil }, :extend => song_representer
31
31
  end
32
32
 
33
- let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) } # note that song is already representable.
33
+ let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
34
34
 
35
35
  it "calls #to_hash on song instance, nothing else" do
36
36
  hit.to_hash.must_equal("song"=>{"title"=>"Resist Stance"})
@@ -44,30 +44,169 @@ class GenericTest < MiniTest::Spec
44
44
  end
45
45
  end
46
46
 
47
+ def self.for_formats(formats)
48
+ formats.each do |format, cfg|
49
+ mod, output, input = cfg
50
+ yield format, mod, output, input
51
+ end
52
+ end
47
53
 
48
- describe ":representable with collection" do # TODO: introduce :representable option?
49
- representer! do
50
- collection :songs, :instance => lambda { |*| nil }
54
+
55
+ for_formats(
56
+ :hash => [Representable::Hash, {"song"=>{"title"=>"Resist Stance"}}, {"song"=>{"title"=>"Suffer"}}],
57
+ :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><song><title>Suffer</title></song></open_struct>",],
58
+ :yaml => [Representable::YAML, "---\nsong:\n title: Resist Stance\n", "---\nsong:\n title: Suffer\n"],
59
+ ) do |format, mod, output, input|
60
+
61
+ describe "[#{format}] property with parse_strategy: :sync" do # TODO: introduce :representable option?
62
+ let (:format) { format }
63
+
64
+ representer!(:module => mod, :name => :song_representer) do
65
+ property :title
66
+ self.representation_wrap = :song if format == :xml
67
+ end
68
+
69
+ representer!(:inject => :song_representer, :module => mod) do
70
+ property :song, :parse_strategy => :sync, :extend => song_representer
71
+ end
72
+
73
+ let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
74
+
75
+ it "calls #to_hash on song instance, nothing else" do
76
+ render(hit).must_equal_document(output)
77
+ end
78
+
79
+
80
+ it "calls #from_hash on the existing song instance, nothing else" do
81
+ song_id = hit.song.object_id
82
+
83
+ parse(hit, input)
84
+
85
+ hit.song.title.must_equal "Suffer"
86
+ hit.song.object_id.must_equal song_id
87
+ end
51
88
  end
52
- let (:album) { OpenStruct.new(:songs => [song]).extend(representer) }
89
+ end
90
+
91
+ # FIXME: there's a bug with XML and the collection name!
92
+ for_formats(
93
+ :hash => [Representable::Hash, {"songs"=>[{"title"=>"Resist Stance"}]}, {"songs"=>[{"title"=>"Suffer"}]}],
94
+ #:json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"],
95
+ :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><songs><title>Suffer</title></songs></open_struct>"],
96
+ :yaml => [Representable::YAML, "---\nsongs:\n- title: Resist Stance\n", "---\nsongs:\n- title: Suffer\n"],
97
+ ) do |format, mod, output, input|
98
+
99
+ describe "[#{format}] collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
100
+ let (:format) { format }
101
+ representer!(:module => mod, :name => :song_representer) do
102
+ property :title
103
+ self.representation_wrap = :song if format == :xml
104
+ end
105
+
106
+ representer!(:inject => :song_representer, :module => mod) do
107
+ collection :songs, :parse_strategy => :sync, :extend => song_representer
108
+ end
53
109
 
54
- it "calls #to_hash on song instances, nothing else" do
55
- album.to_hash.must_equal("songs"=>[{"title"=>"Resist Stance"}])
110
+ let (:album) { OpenStruct.new(:songs => [song]).extend(representer) }
111
+
112
+ it "calls #to_hash on song instances, nothing else" do
113
+ render(album).must_equal_document(output)
114
+ end
115
+
116
+ it "calls #from_hash on the existing song instance, nothing else" do
117
+ collection_id = album.songs.object_id
118
+ song = album.songs.first
119
+ song_id = song.object_id
120
+
121
+ parse(album, input)
122
+
123
+ album.songs.first.title.must_equal "Suffer"
124
+ song.title.must_equal "Suffer"
125
+ #album.songs.object_id.must_equal collection_id # TODO: don't replace!
126
+ song.object_id.must_equal song_id
127
+ end
56
128
  end
129
+ end
57
130
 
58
- it "calls #from_hash on the existing song instance, nothing else" do
59
- album.songs.instance_eval do
60
- def from_hash(items, *args)
61
- #puts items #=> {"title"=>"Suffer"}
62
- first.from_hash(items) # example how you can use this.
63
- end
131
+ def render(object)
132
+ AssertableDocument.new(object.send("to_#{format}"), format)
133
+ end
134
+
135
+ def parse(object, input)
136
+ object.send("from_#{format}", input)
137
+ end
138
+
139
+ class AssertableDocument
140
+ attr_reader :document
141
+
142
+ def initialize(document, format)
143
+ @document, @format = document, format
144
+ end
145
+
146
+ def must_equal_document(*args)
147
+ return document.must_equal_xml(*args) if @format == :xml
148
+ document.must_equal(*args)
149
+ end
150
+ end
151
+
152
+
153
+ # Lonely Collection
154
+ require "representable/hash/collection"
155
+
156
+ for_formats(
157
+ :hash => [Representable::Hash::Collection, [{"title"=>"Resist Stance"}], [{"title"=>"Suffer"}]],
158
+ # :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><songs><title>Suffer</title></songs></open_struct>"],
159
+ ) do |format, mod, output, input|
160
+
161
+ describe "[#{format}] lonely collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
162
+ let (:format) { format }
163
+ representer!(:module => Representable::Hash, :name => :song_representer) do
164
+ property :title
165
+ self.representation_wrap = :song if format == :xml
166
+ end
167
+
168
+ representer!(:inject => :song_representer, :module => mod) do
169
+ items :parse_strategy => :sync, :extend => song_representer
64
170
  end
65
171
 
66
- song = album.songs.first
67
- song_id = song.object_id
68
- album.from_hash("songs"=>[{"title"=>"Suffer"}])
69
- song.title.must_equal "Suffer"
70
- song.object_id.must_equal song_id
172
+ let (:album) { [song].extend(representer) }
173
+
174
+ it "calls #to_hash on song instances, nothing else" do
175
+ render(album).must_equal_document(output)
176
+ end
177
+
178
+ it "calls #from_hash on the existing song instance, nothing else" do
179
+ #collection_id = album.object_id
180
+ song = album.first
181
+ song_id = song.object_id
182
+
183
+ parse(album, input)
184
+
185
+ album.first.title.must_equal "Suffer"
186
+ song.title.must_equal "Suffer"
187
+ song.object_id.must_equal song_id
188
+ end
189
+ end
190
+ end
191
+
192
+ def render(object)
193
+ AssertableDocument.new(object.send("to_#{format}"), format)
194
+ end
195
+
196
+ def parse(object, input)
197
+ object.send("from_#{format}", input)
198
+ end
199
+
200
+ class AssertableDocument
201
+ attr_reader :document
202
+
203
+ def initialize(document, format)
204
+ @document, @format = document, format
205
+ end
206
+
207
+ def must_equal_document(*args)
208
+ return document.must_equal_xml(*args) if @format == :xml
209
+ document.must_equal(*args)
71
210
  end
72
211
  end
73
212