representable 1.8.5 → 2.0.0.rc1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGES.md +36 -0
  4. data/Gemfile +3 -3
  5. data/README.md +62 -2
  6. data/Rakefile +1 -1
  7. data/lib/representable.rb +32 -109
  8. data/lib/representable/TODO.getting_serious +7 -1
  9. data/lib/representable/autoload.rb +10 -0
  10. data/lib/representable/binding.rb +20 -16
  11. data/lib/representable/bindings/xml_bindings.rb +0 -1
  12. data/lib/representable/coercion.rb +23 -31
  13. data/lib/representable/config.rb +45 -46
  14. data/lib/representable/declarative.rb +78 -0
  15. data/lib/representable/decorator.rb +39 -10
  16. data/lib/representable/definition.rb +40 -33
  17. data/lib/representable/deserializer.rb +2 -0
  18. data/lib/representable/for_collection.rb +25 -0
  19. data/lib/representable/hash.rb +4 -8
  20. data/lib/representable/hash/collection.rb +2 -9
  21. data/lib/representable/hash_methods.rb +0 -7
  22. data/lib/representable/inheritable.rb +50 -0
  23. data/lib/representable/json.rb +3 -9
  24. data/lib/representable/json/collection.rb +1 -3
  25. data/lib/representable/json/hash.rb +4 -9
  26. data/lib/representable/mapper.rb +8 -5
  27. data/lib/representable/parse_strategies.rb +1 -0
  28. data/lib/representable/pipeline.rb +14 -0
  29. data/lib/representable/represent.rb +6 -0
  30. data/lib/representable/version.rb +1 -1
  31. data/lib/representable/xml.rb +3 -18
  32. data/lib/representable/xml/collection.rb +2 -4
  33. data/lib/representable/xml/hash.rb +2 -10
  34. data/lib/representable/yaml.rb +1 -20
  35. data/representable.gemspec +2 -2
  36. data/test/class_test.rb +5 -10
  37. data/test/coercion_test.rb +31 -92
  38. data/test/config/inherit_test.rb +128 -0
  39. data/test/config_test.rb +114 -80
  40. data/test/definition_test.rb +107 -64
  41. data/test/features_test.rb +41 -0
  42. data/test/filter_test.rb +59 -0
  43. data/test/for_collection_test.rb +74 -0
  44. data/test/inherit_test.rb +44 -3
  45. data/test/inheritable_test.rb +97 -0
  46. data/test/inline_test.rb +0 -18
  47. data/test/instance_test.rb +0 -19
  48. data/test/json_test.rb +9 -44
  49. data/test/lonely_test.rb +1 -0
  50. data/test/parse_strategy_test.rb +30 -0
  51. data/test/represent_test.rb +88 -0
  52. data/test/representable_test.rb +3 -50
  53. data/test/schema_test.rb +123 -0
  54. data/test/test_helper.rb +1 -1
  55. data/test/xml_test.rb +34 -38
  56. metadata +25 -15
  57. data/lib/representable/decorator/coercion.rb +0 -4
  58. data/lib/representable/readable_writeable.rb +0 -29
  59. data/test/inheritance_test.rb +0 -22
@@ -0,0 +1,97 @@
1
+ require 'test_helper'
2
+
3
+ # tests Inheritable:: classes (the #inherit! method). This can be moved to uber if necessary.
4
+
5
+ class ConfigInheritableTest < MiniTest::Spec
6
+ class CloneableObject
7
+ include Representable::Cloneable
8
+
9
+ # same instance returns same clone.
10
+ def clone
11
+ @clone ||= super
12
+ end
13
+ end
14
+
15
+
16
+ # Inheritable::Array
17
+ it do
18
+ parent = Representable::Inheritable::Array.new([1,2,3])
19
+ child = Representable::Inheritable::Array.new([4])
20
+
21
+ child.inherit!(parent).must_equal([4,1,2,3])
22
+ end
23
+
24
+ # Inheritable::Hash
25
+ Inheritable = Representable::Inheritable
26
+ describe "Inheritable::Hash" do
27
+ it do
28
+ parent = Inheritable::Hash[
29
+ :volume => volume = Uber::Options::Value.new(9),
30
+ :genre => "Powermetal",
31
+ :only_parent => only_parent = Representable::Inheritable::Array["Pumpkin Box"],
32
+ :in_both => in_both = Representable::Inheritable::Array["Roxanne"],
33
+ :hash => {:type => :parent},
34
+ :clone => parent_clone = CloneableObject.new # cloneable is in both hashes.
35
+ ]
36
+ child = Inheritable::Hash[
37
+ :genre => "Metal",
38
+ :pitch => 99,
39
+ :in_both => Representable::Inheritable::Array["Generator"],
40
+ :hash => {:type => :child},
41
+ :clone => child_clone = CloneableObject.new
42
+ ]
43
+
44
+ child.inherit!(parent)
45
+
46
+ # order:
47
+ child.to_a.must_equal [
48
+ [:genre, "Powermetal"], # parent overrides child
49
+ [:pitch, 99], # parent doesn't define pitch
50
+ [:in_both, ["Generator", "Roxanne"]], # Inheritable array gets "merged".
51
+ [:hash, {:type => :parent}], # normal hash merge: parent overwrites child value.
52
+ [:clone, parent_clone.clone],
53
+ [:volume, volume],
54
+ [:only_parent, ["Pumpkin Box"]],
55
+ ]
56
+
57
+ # clone
58
+ child[:only_parent].object_id.wont_equal parent[:only_parent].object_id
59
+ child[:clone].object_id.wont_equal parent[:clone].object_id
60
+
61
+ # still a hash:
62
+ child.must_equal(
63
+ :genre => "Powermetal",
64
+ :pitch => 99,
65
+ :in_both => ["Generator", "Roxanne"],
66
+ :hash => {:type => :parent},
67
+ :clone => parent_clone.clone,
68
+ :volume => volume,
69
+ :only_parent => ["Pumpkin Box"]
70
+ )
71
+ end
72
+
73
+ # nested:
74
+ it do
75
+ parent = Inheritable::Hash[
76
+ :details => Inheritable::Hash[
77
+ :title => title = "Man Of Steel",
78
+ :length => length = Representable::Definition.new(:length) # Cloneable.
79
+ ]]
80
+
81
+ child = Inheritable::Hash[].inherit!(parent)
82
+ child[:details][:track] = 1
83
+
84
+ parent.must_equal({:details => {:title => "Man Of Steel", :length => length}})
85
+
86
+ child.keys.must_equal [:details]
87
+ child[:details].keys.must_equal [:title, :length, :track]
88
+ child[:details][:title].must_equal "Man Of Steel"
89
+ child[:details][:track].must_equal 1
90
+ child[:details][:length].name.must_equal "length"
91
+
92
+ # clone
93
+ child[:details][:title].object_id.must_equal title.object_id
94
+ child[:details][:length].object_id.wont_equal length.object_id
95
+ end
96
+ end
97
+ end
@@ -208,24 +208,6 @@ class InlineTest < MiniTest::Spec
208
208
  end
209
209
 
210
210
 
211
- describe "deprecate mixing :extend and inline representers" do # TODO: remove in 2.0.
212
- representer! do
213
- rpr_module = Module.new do
214
- include Representable::Hash
215
- property :title
216
- end
217
- property :song, :extend => rpr_module do
218
- property :artist
219
- end
220
- end
221
-
222
- it do OpenStruct.new(:song => OpenStruct.new(:title => "The Fever And The Sound", :artist => "Strung Out")).extend(representer).
223
- to_hash.
224
- must_equal({"song"=>{"artist"=>"Strung Out", "title"=>"The Fever And The Sound"}})
225
- end
226
- end
227
-
228
-
229
211
  describe "include module in inline representers" do
230
212
  representer! do
231
213
  extension = Module.new do
@@ -247,25 +247,6 @@ class InstanceTest < BaseTest
247
247
  end
248
248
 
249
249
 
250
- describe "instance: true" do
251
- representer!(:inject => :song_representer) do
252
- property :song,
253
- :extend => song_representer, :instance => true
254
- end
255
-
256
- it "uses Binding#get instead of creating an instance, but deprecates" do
257
- album= Struct.new(:song).new(song = Song.new(1, "The Answer Is Still No"))
258
-
259
- album.
260
- extend(representer).
261
- from_hash("song" => {"title" => "Invincible"}).
262
- song.must_equal Song.new(1, "Invincible")
263
-
264
- album.song.object_id.must_equal song.object_id
265
- end
266
- end
267
-
268
-
269
250
  describe "new syntax for instance: true" do
270
251
  representer!(:inject => :song_representer) do
271
252
  property :song, :pass_options => true,
@@ -22,41 +22,6 @@ module JsonTest
22
22
  end
23
23
 
24
24
 
25
- describe ".from_json" do
26
- it "is delegated to #from_json" do
27
- block = lambda {|*args|}
28
- @Band.any_instance.expects(:from_json).with("{document}", "options") # FIXME: how to NOT expect block?
29
- @Band.from_json("{document}", "options", &block)
30
- end
31
-
32
- it "yields new object and options to block" do
33
- @Band.class_eval { attr_accessor :new_name }
34
- @band = @Band.from_json("{}", :new_name => "Diesel Boy") do |band, options|
35
- band.new_name= options[:new_name]
36
- end
37
- assert_equal "Diesel Boy", @band.new_name
38
- end
39
- end
40
-
41
-
42
- describe ".from_hash" do
43
- it "is delegated to #from_hash not passing the block" do
44
- block = lambda {|*args|}
45
- @Band.any_instance.expects(:from_hash).with("{document}", "options") # FIXME: how to NOT expect block?
46
- @Band.from_hash("{document}", "options", &block)
47
- end
48
-
49
- it "yields new object and options to block" do
50
- @Band.class_eval { attr_accessor :new_name }
51
- @band = @Band.from_hash({}, :new_name => "Diesel Boy") do |band, options|
52
- band.new_name= options[:new_name]
53
- end
54
-
55
- assert_equal "Diesel Boy", @band.new_name
56
- end
57
- end
58
-
59
-
60
25
  describe "#from_json" do
61
26
  before do
62
27
  @band = @Band.new
@@ -204,7 +169,7 @@ module JsonTest
204
169
  end
205
170
 
206
171
  it "#from_json creates correct accessors" do
207
- band = Band.from_json({:name => "Bombshell Rocks"}.to_json)
172
+ band = Band.new.from_json({:name => "Bombshell Rocks"}.to_json)
208
173
  assert_equal "Bombshell Rocks", band.name
209
174
  end
210
175
 
@@ -230,7 +195,7 @@ module JsonTest
230
195
  end
231
196
 
232
197
  it "#from_json creates one Item instance" do
233
- album = Album.from_json('{"label":{"name":"Fat Wreck"}}')
198
+ album = Album.new.from_json('{"label":{"name":"Fat Wreck"}}')
234
199
  assert_equal "Fat Wreck", album.label.name
235
200
  end
236
201
 
@@ -267,7 +232,7 @@ module JsonTest
267
232
  end
268
233
 
269
234
  it "respects :as in #from_json" do
270
- song = Song.from_json({:songName => "Run To The Hills"}.to_json)
235
+ song = Song.new.from_json({:songName => "Run To The Hills"}.to_json)
271
236
  assert_equal "Run To The Hills", song.name
272
237
  end
273
238
 
@@ -288,17 +253,17 @@ module JsonTest
288
253
 
289
254
  describe "#from_json" do
290
255
  it "uses default when property nil in doc" do
291
- album = @Album.from_json({}.to_json)
256
+ album = @Album.new.from_json({}.to_json)
292
257
  assert_equal "30 Years Live", album.name
293
258
  end
294
259
 
295
260
  it "uses value from doc when present" do
296
- album = @Album.from_json({:name => "Live At The Wireless"}.to_json)
261
+ album = @Album.new.from_json({:name => "Live At The Wireless"}.to_json)
297
262
  assert_equal "Live At The Wireless", album.name
298
263
  end
299
264
 
300
265
  it "uses value from doc when empty string" do
301
- album = @Album.from_json({:name => ""}.to_json)
266
+ album = @Album.new.from_json({:name => ""}.to_json)
302
267
  assert_equal "", album.name
303
268
  end
304
269
  end
@@ -333,7 +298,7 @@ end
333
298
  end
334
299
 
335
300
  it "#from_json creates correct accessors" do
336
- cd = CD.from_json({:songs => ["Out in the cold", "Microphone"]}.to_json)
301
+ cd = CD.new.from_json({:songs => ["Out in the cold", "Microphone"]}.to_json)
337
302
  assert_equal ["Out in the cold", "Microphone"], cd.songs
338
303
  end
339
304
 
@@ -364,7 +329,7 @@ end
364
329
 
365
330
  describe "#from_json" do
366
331
  it "pushes collection items to array" do
367
- cd = Compilation.from_json({:bands => [
332
+ cd = Compilation.new.from_json({:bands => [
368
333
  {:name => "Cobra Skulls"},
369
334
  {:name => "Diesel Boy"}]}.to_json)
370
335
  assert_equal ["Cobra Skulls", "Diesel Boy"], cd.bands.map(&:name).sort
@@ -388,7 +353,7 @@ end
388
353
  end
389
354
 
390
355
  it "respects :as in #from_json" do
391
- songs = Songs.from_json({:songList => ["Out in the cold", "Microphone"]}.to_json)
356
+ songs = Songs.new.from_json({:songList => ["Out in the cold", "Microphone"]}.to_json)
392
357
  assert_equal ["Out in the cold", "Microphone"], songs.tracks
393
358
  end
394
359
 
@@ -6,6 +6,7 @@ require 'representable/json/hash'
6
6
  class LonelyRepresenterTest < MiniTest::Spec
7
7
  module SongRepresenter
8
8
  include Representable::JSON
9
+
9
10
  property :name
10
11
  end
11
12
 
@@ -83,6 +83,24 @@ class ParseStrategySyncTest < BaseTest
83
83
  end
84
84
 
85
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
+
86
104
  # Lonely Collection
87
105
  for_formats(
88
106
  :hash => [Representable::Hash::Collection, [{"title"=>"Resist Stance"}], [{"title"=>"Suffer"}]],
@@ -219,6 +237,18 @@ class ParseStrategyLambdaTest < MiniTest::Spec
219
237
  property :title
220
238
  end
221
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
+
222
252
 
223
253
  describe "collection" do
224
254
  representer!(:inject => :song_representer) do
@@ -0,0 +1,88 @@
1
+ require 'test_helper'
2
+
3
+ class RepresentTest < MiniTest::Spec
4
+ let (:songs) { [song, Song.new("Can't Take Them All")] }
5
+ let (:song) { Song.new("Days Go By") }
6
+
7
+ for_formats(
8
+ :hash => [Representable::Hash, out=[{"name" => "Days Go By"}, {"name"=>"Can't Take Them All"}], out],
9
+ :json => [Representable::JSON, out="[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]", out],
10
+ # :xml => [Representable::XML, out="<a><song></song><song></song></a>", out]
11
+ ) do |format, mod, output, input|
12
+
13
+ # Representer.represents detects collection.
14
+ describe "Module#to_/from_#{format}" do
15
+ let (:format) { format }
16
+
17
+ let (:representer) {
18
+ Module.new do
19
+ include mod
20
+ property :name
21
+
22
+ collection_representer :class => Song # TODOOOOOOOOOOOO: test without Song and fix THIS FUCKINGNoMethodError: undefined method `name=' for {"name"=>"Days Go By"}:Hash ERROR!!!!!!!!!!!!!!!
23
+ end
24
+ }
25
+
26
+ it { render(representer.represent(songs)).must_equal_document output }
27
+ it { parse(representer.represent([]), input).must_equal songs }
28
+ end
29
+
30
+ # Decorator.represents detects collection.
31
+ describe "Decorator#to_/from_#{format}" do
32
+ let (:format) { format }
33
+ let (:representer) {
34
+ Class.new(Representable::Decorator) do
35
+ include mod
36
+ property :name
37
+
38
+ collection_representer :class => Song
39
+ end
40
+ }
41
+
42
+ it { render(representer.represent(songs)).must_equal_document output }
43
+ it { parse(representer.represent([]), input).must_equal songs }
44
+ end
45
+ end
46
+
47
+
48
+ for_formats(
49
+ :hash => [Representable::Hash, out={"name" => "Days Go By"}, out],
50
+ :json => [Representable::JSON, out="{\"name\":\"Days Go By\"}", out],
51
+ # :xml => [Representable::XML, out="<a><song></song><song></song></a>", out]
52
+ ) do |format, mod, output, input|
53
+
54
+ # Representer.represents detects singular.
55
+ describe "Module#to_/from_#{format}" do
56
+ let (:format) { format }
57
+
58
+ let (:representer) {
59
+ Module.new do
60
+ include mod
61
+ property :name
62
+
63
+ collection_representer :class => Song
64
+ end
65
+ }
66
+
67
+ it { render(representer.represent(song)).must_equal_document output }
68
+ it { parse(representer.represent(Song.new), input).must_equal song }
69
+ end
70
+
71
+
72
+ # Decorator.represents detects singular.
73
+ describe "Decorator#to_/from_#{format}" do
74
+ let (:format) { format }
75
+ let (:representer) {
76
+ Class.new(Representable::Decorator) do
77
+ include mod
78
+ property :name
79
+
80
+ collection_representer :class => Song
81
+ end
82
+ }
83
+
84
+ it { render(representer.represent(song)).must_equal_document output }
85
+ it { parse(representer.represent(Song.new), input).must_equal song }
86
+ end
87
+ end
88
+ end
@@ -27,34 +27,7 @@ class RepresentableTest < MiniTest::Spec
27
27
 
28
28
 
29
29
  describe "#representable_attrs" do
30
- it "responds to #representable_attrs" do
31
- assert_equal 1, Band.representable_attrs.size
32
- assert_equal "name", Band.representable_attrs.first.name
33
- end
34
-
35
30
  describe "in module" do
36
- it "returns definitions" do
37
- assert_equal 1, BandRepresentation.representable_attrs.size
38
- assert_equal "name", BandRepresentation.representable_attrs.first.name
39
- end
40
-
41
- it "inherits to including modules xxx " do
42
- assert_equal 2, PunkBandRepresentation.representable_attrs.size
43
- assert_equal "name", PunkBandRepresentation.representable_attrs[:name].name
44
- assert_equal "street_cred", PunkBandRepresentation.representable_attrs[:street_cred].name
45
- end
46
-
47
- it "inherits to including class" do
48
- band = Class.new do
49
- include Representable
50
- include PunkBandRepresentation
51
- end
52
-
53
- assert_equal 2, band.representable_attrs.size
54
- assert_equal "name", band.representable_attrs[:name].name
55
- assert_equal "street_cred", band.representable_attrs[:street_cred].name
56
- end
57
-
58
31
  it "allows including the concrete representer module later" do
59
32
  vd = class VD
60
33
  attr_accessor :name, :street_cred
@@ -78,20 +51,6 @@ class RepresentableTest < MiniTest::Spec
78
51
  # vd.name = "Van Halen"
79
52
  # assert_equal "{\"name\":\"Van Halen\"}", vd.to_json
80
53
  #end
81
-
82
- it "doesn't share inherited properties between family members" do
83
- parent = Module.new do
84
- include Representable
85
- property :id
86
- end
87
-
88
- child = Module.new do
89
- include Representable
90
- include parent
91
- end
92
-
93
- assert parent.representable_attrs.first.object_id != child.representable_attrs.first.object_id, "definitions shouldn't be identical"
94
- end
95
54
  end
96
55
  end
97
56
 
@@ -173,8 +132,8 @@ class RepresentableTest < MiniTest::Spec
173
132
  end
174
133
 
175
134
  it "creates correct Definition" do
176
- assert_equal "albums", RockBand.representable_attrs[:albums].name
177
- assert RockBand.representable_attrs[:albums].array?
135
+ assert_equal "albums", RockBand.representable_attrs.get(:albums).name
136
+ assert RockBand.representable_attrs.get(:albums).array?
178
137
  end
179
138
  end
180
139
 
@@ -185,13 +144,6 @@ class RepresentableTest < MiniTest::Spec
185
144
  end
186
145
 
187
146
 
188
- describe "#definition_class" do
189
- it "returns Definition class" do
190
- assert_equal Representable::Definition, Band.send(:definition_class)
191
- end
192
- end
193
-
194
-
195
147
  # DISCUSS: i don't like the JSON requirement here, what about some generic test module?
196
148
  class PopBand
197
149
  include Representable::JSON
@@ -211,6 +163,7 @@ class RepresentableTest < MiniTest::Spec
211
163
  assert_equal 2, @band.groupies
212
164
  end
213
165
 
166
+
214
167
  it "accepts :exclude option" do
215
168
  @band.from_hash({"name"=>"No One's Choice", "groupies"=>2}, {:exclude => [:groupies]})
216
169
  assert_equal "No One's Choice", @band.name