representable 1.6.1 → 1.7.0

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.
@@ -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