representable 1.5.3 → 1.6.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.
@@ -3,82 +3,111 @@ require 'representable/coercion'
3
3
  require 'representable/decorator/coercion'
4
4
 
5
5
  class VirtusCoercionTest < MiniTest::Spec
6
- class Song # note that we don't define accessors for the properties here.
7
- attr_accessor :title
6
+ class Song # note that we have to define accessors for the properties here.
7
+ attr_accessor :title, :composed_at, :track
8
8
  end
9
9
 
10
- describe "Coercion with Virtus" do
11
- describe "on object level" do
12
- module SongRepresenter
13
- include Representable::JSON
14
- include Representable::Coercion
15
- property :composed_at, :type => DateTime
16
- property :track, :type => Integer
17
- property :title # no coercion.
18
- end
19
-
20
- it "coerces properties in #from_json" do
21
- song = Song.new.extend(SongRepresenter).from_json('{"composed_at":"November 18th, 1983","track":"18","title":"Scarified"}')
22
- assert_kind_of DateTime, song.composed_at
23
- assert_equal DateTime.parse("Fri, 18 Nov 1983 00:00:00 +0000"), song.composed_at
24
- assert_equal 18, song.track
25
- song.title.must_equal "Scarified"
26
- end
27
-
28
- # it "coerces when rendering" do
29
- # song = Song.new
30
- # song.title = "Scarified"
31
- # song.to_json.must_equal ''
32
- # end
10
+ let (:date) { DateTime.parse("Fri, 18 Nov 1983 00:00:00 +0000") }
11
+
12
+ describe "on object level" do
13
+ module SongRepresenter
14
+ include Representable::JSON
15
+ include Representable::Coercion
16
+ property :composed_at, :type => DateTime
17
+ property :track, :type => Integer
18
+ property :title # no coercion.
19
+ end
20
+
21
+ it "coerces properties in #from_json" do
22
+ song = Song.new.extend(SongRepresenter).from_json('{"composed_at":"November 18th, 1983","track":"18","title":"Scarified"}')
23
+ song.composed_at.must_equal date
24
+ song.track.must_equal 18
25
+ song.title.must_equal "Scarified"
33
26
  end
34
27
 
28
+ it "coerces when rendering" do
29
+ song = Song.new.extend(SongRepresenter)
30
+ song.title = "Scarified"
31
+ song.composed_at = "Fri, 18 Nov 1983"
35
32
 
36
- describe "on class level" do
37
- class ImmigrantSong
38
- include Representable::JSON
39
- include Representable::Coercion
33
+ song.to_hash.must_equal({"title" => "Scarified", "composed_at" => date})
34
+ end
35
+ end
36
+
37
+ describe "on class level" do
38
+ class ImmigrantSong
39
+ include Representable::JSON
40
+ include Representable::Coercion
40
41
 
41
- property :composed_at, :type => DateTime, :default => "May 12th, 2012"
42
- property :track, :type => Integer
43
- end
42
+ property :composed_at, :type => DateTime, :default => "May 12th, 2012"
43
+ property :track, :type => Integer
44
44
 
45
- it "coerces into the provided type" do
46
- song = ImmigrantSong.new.from_json("{\"composed_at\":\"November 18th, 1983\",\"track\":\"18\"}")
47
- assert_equal DateTime.parse("Fri, 18 Nov 1983 00:00:00 +0000"), song.composed_at
48
- assert_equal 18, song.track
49
- end
45
+ attr_accessor :composed_at, :track
46
+ end
50
47
 
51
- it "respects the :default options" do
52
- song = ImmigrantSong.new.from_json("{}")
53
- assert_kind_of DateTime, song.composed_at
54
- assert_equal DateTime.parse("Mon, 12 May 2012 00:00:00 +0000"), song.composed_at
55
- end
48
+ it "coerces into the provided type" do
49
+ song = ImmigrantSong.new.from_json("{\"composed_at\":\"November 18th, 1983\",\"track\":\"18\"}")
50
+ song.composed_at.must_equal date
51
+ song.track.must_equal 18
56
52
  end
57
53
 
58
- describe "on decorator" do
59
- class SongRepresentation < Representable::Decorator
60
- include Representable::JSON
61
- include Representable::Decorator::Coercion
62
-
63
- property :composed_at, :type => DateTime
64
- property :title
65
- end
66
-
67
- it "coerces when parsing" do
68
- song = SongRepresentation.new(OpenStruct.new).from_json("{\"composed_at\":\"November 18th, 1983\", \"title\": \"Scarified\"}")
69
- song.must_be_kind_of OpenStruct
70
- song.composed_at.must_equal DateTime.parse("Fri, 18 Nov 1983")
71
- song.title.must_equal "Scarified"
72
- end
73
-
74
- it "coerces when rendering" do
75
- SongRepresentation.new(
76
- OpenStruct.new(
77
- :composed_at => "November 18th, 1983",
78
- :title => "Scarified"
79
- )
80
- ).to_hash.must_equal({"composed_at"=>DateTime.parse("Fri, 18 Nov 1983"), "title"=>"Scarified"})
81
- end
54
+ it "respects the :default options" do
55
+ song = ImmigrantSong.new.from_json("{}")
56
+ song.composed_at.must_equal DateTime.parse("Mon, 12 May 2012 00:00:00 +0000")
82
57
  end
83
58
  end
59
+
60
+ describe "on decorator" do
61
+ class SongRepresentation < Representable::Decorator
62
+ include Representable::JSON
63
+ include Representable::Coercion
64
+
65
+ property :composed_at, :type => DateTime
66
+ property :title
67
+ end
68
+
69
+ it "coerces when parsing" do
70
+ song = SongRepresentation.new(OpenStruct.new).from_json("{\"composed_at\":\"November 18th, 1983\", \"title\": \"Scarified\"}")
71
+ song.must_be_kind_of OpenStruct
72
+ song.composed_at.must_equal date
73
+ song.title.must_equal "Scarified"
74
+ end
75
+
76
+ it "coerses with inherited decorator" do
77
+ song = Class.new(SongRepresentation).new(OpenStruct.new).from_json("{\"composed_at\":\"November 18th, 1983\", \"title\": \"Scarified\"}")
78
+ song.composed_at.must_equal date
79
+ end
80
+
81
+ it "coerces when rendering" do
82
+ SongRepresentation.new(
83
+ OpenStruct.new(
84
+ :composed_at => "November 18th, 1983",
85
+ :title => "Scarified"
86
+ )
87
+ ).to_hash.must_equal({"composed_at"=>date, "title"=>"Scarified"})
88
+ end
89
+ end
90
+
91
+ # DISCUSS: do we actually wanna have accessors in a decorator/module? i guess the better idea is to let coercion happen through from_/to_,
92
+ # only, to make it as simple as possible.
93
+ # describe "without serialization/deserialization" do
94
+ # let (:coercer_class) do
95
+ # class SongCoercer < Representable::Decorator
96
+ # include Representable::Decorator::Coercion
97
+
98
+ # property :composed_at, :type => DateTime
99
+ # property :title
100
+
101
+ # self
102
+ # end
103
+ # end
104
+
105
+ # it "coerces when setting" do
106
+ # coercer = coercer_class.new(song = OpenStruct.new)
107
+ # coercer.composed_at = "November 18th, 1983"
108
+ # #coercer.title = "Scarified"
109
+
110
+ # song.composed_at.must_equal date
111
+ # end
112
+ # end
84
113
  end
@@ -17,6 +17,16 @@ class DecoratorTest < MiniTest::Spec
17
17
  let (:album) { Album.new([song]) }
18
18
  let (:decorator) { AlbumRepresentation.new(album) }
19
19
 
20
+ describe "inheritance" do
21
+ let (:inherited_decorator) do
22
+ Class.new(AlbumRepresentation) do
23
+ property :best_song
24
+ end.new(Album.new([song], "Stand Up"))
25
+ end
26
+
27
+ it { inherited_decorator.to_hash.must_equal({"songs"=>[{"name"=>"Mama, I'm Coming Home"}], "best_song"=>"Stand Up"}) }
28
+ end
29
+
20
30
  it "renders" do
21
31
  decorator.to_hash.must_equal({"songs"=>[{"name"=>"Mama, I'm Coming Home"}]})
22
32
  album.wont_respond_to :to_hash
@@ -0,0 +1,21 @@
1
+ require 'test_helper'
2
+
3
+ class InheritanceTest < MiniTest::Spec
4
+ let (:decorator) do
5
+ Class.new(Representable::Decorator) do
6
+ property :title
7
+ end
8
+ end
9
+
10
+ # Decorator.new.representable_attrs != Decorator.representable_attrs
11
+ it "doesn't clone for instantiated decorator" do
12
+ instance = decorator.new(Object.new)
13
+ instance.send(:representable_attrs).first.options[:instance] = true
14
+
15
+ # we didn't clone and thereby change the original config:
16
+ instance.send(:representable_attrs).to_s.must_equal decorator.representable_attrs.to_s
17
+ end
18
+
19
+ # TODO: ? (performance?)
20
+ # more tests on cloning
21
+ end
@@ -144,13 +144,13 @@ module JsonTest
144
144
  end
145
145
  end
146
146
 
147
- describe "#representable_bindings_for" do
148
- it "returns bindings for each property" do
149
- bins = @band.send(:representable_bindings_for, Representable::JSON::PropertyBinding, {})
150
- assert_equal 2, bins.size
151
- assert_equal "name", bins.first.name
152
- end
153
- end
147
+ # describe "#representable_bindings_for" do
148
+ # it "returns bindings for each property" do
149
+ # bins = @band.send(:representable_bindings_for, Representable::JSON::PropertyBinding, {})
150
+ # assert_equal 2, bins.size
151
+ # assert_equal "name", bins.first.name
152
+ # end
153
+ # end
154
154
  end
155
155
 
156
156
 
@@ -2,7 +2,7 @@ require 'test_helper'
2
2
 
3
3
  class RepresentableTest < MiniTest::Spec
4
4
  class Band
5
- include Representable
5
+ include Representable::Hash
6
6
  property :name
7
7
  attr_accessor :name
8
8
  end
@@ -92,7 +92,6 @@ class RepresentableTest < MiniTest::Spec
92
92
 
93
93
  assert parent.representable_attrs.first != child.representable_attrs.first, "definitions shouldn't be identical"
94
94
  end
95
-
96
95
  end
97
96
  end
98
97
 
@@ -172,6 +171,12 @@ class RepresentableTest < MiniTest::Spec
172
171
  assert_equal "friends", band.representable_attrs.last.from
173
172
  end
174
173
  end
174
+
175
+ representer! {}
176
+
177
+ it "returns the Definition instance" do
178
+ representer.property(:name).must_be_kind_of Representable::Definition
179
+ end
175
180
  end
176
181
 
177
182
  describe "#collection" do
@@ -242,36 +247,36 @@ class RepresentableTest < MiniTest::Spec
242
247
  end
243
248
 
244
249
  it "copies values from document to object" do
245
- @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {}, Representable::Hash::PropertyBinding)
250
+ @band.from_hash({"name"=>"No One's Choice", "groupies"=>2})
246
251
  assert_equal "No One's Choice", @band.name
247
252
  assert_equal 2, @band.groupies
248
253
  end
249
254
 
250
255
  it "accepts :exclude option" do
251
- @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:exclude => [:groupies]}, Representable::Hash::PropertyBinding)
256
+ @band.from_hash({"name"=>"No One's Choice", "groupies"=>2}, {:exclude => [:groupies]})
252
257
  assert_equal "No One's Choice", @band.name
253
258
  assert_equal nil, @band.groupies
254
259
  end
255
260
 
256
261
  it "accepts :include option" do
257
- @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:include => [:groupies]}, Representable::Hash::PropertyBinding)
262
+ @band.from_hash({"name"=>"No One's Choice", "groupies"=>2}, :include => [:groupies])
258
263
  assert_equal 2, @band.groupies
259
264
  assert_equal nil, @band.name
260
265
  end
261
266
 
262
267
  it "ignores non-writeable properties" do
263
268
  @band = Class.new(Band) { property :name; collection :founders, :writeable => false; attr_accessor :founders }.new
264
- @band.update_properties_from({"name" => "Iron Maiden", "groupies" => 2, "founders" => [{ "name" => "Steve Harris" }] }, {}, Representable::Hash::PropertyBinding)
269
+ @band.from_hash("name" => "Iron Maiden", "groupies" => 2, "founders" => ["Steve Harris"])
265
270
  assert_equal "Iron Maiden", @band.name
266
271
  assert_equal nil, @band.founders
267
272
  end
268
273
 
269
274
  it "always returns the represented" do
270
- assert_equal @band, @band.update_properties_from({"name"=>"Nofx"}, {}, Representable::Hash::PropertyBinding)
275
+ assert_equal @band, @band.from_hash({"name"=>"Nofx"})
271
276
  end
272
277
 
273
278
  it "includes false attributes" do
274
- @band.update_properties_from({"groupies"=>false}, {}, Representable::Hash::PropertyBinding)
279
+ @band.from_hash({"groupies"=>false})
275
280
  assert_equal false, @band.groupies
276
281
  end
277
282
 
@@ -279,14 +284,15 @@ class RepresentableTest < MiniTest::Spec
279
284
  @band.instance_eval do
280
285
  def name=(*); raise "I should never be called!"; end
281
286
  end
282
- @band.update_properties_from({}, {}, Representable::Hash::PropertyBinding)
287
+ @band.from_hash({})
283
288
  end
284
289
 
290
+ # FIXME: do we need this test with XML _and_ JSON?
285
291
  it "ignores (no-default) properties not present in the incoming document" do
286
- { Representable::JSON => [{}, Representable::Hash::PropertyBinding],
287
- Representable::XML => [xml(%{<band/>}), Representable::XML::PropertyBinding]
292
+ { Representable::Hash => [:from_hash, {}],
293
+ Representable::XML => [:from_xml, xml(%{<band/>}).to_s]
288
294
  }.each do |format, config|
289
- nested_repr = Module.new do # this module is never applied.
295
+ nested_repr = Module.new do # this module is never applied. # FIXME: can we make that a simpler test?
290
296
  include format
291
297
  property :created_at
292
298
  end
@@ -297,7 +303,7 @@ class RepresentableTest < MiniTest::Spec
297
303
  end
298
304
 
299
305
  @band = Band.new.extend(repr)
300
- @band.update_properties_from(config.first, {}, config.last)
306
+ @band.send(config.first, config.last)
301
307
  assert_equal nil, @band.name, "Failed in #{format}"
302
308
  end
303
309
  end
@@ -348,16 +354,16 @@ class RepresentableTest < MiniTest::Spec
348
354
  end
349
355
 
350
356
  it "compiles document from properties in object" do
351
- assert_equal({"name"=>"No One's Choice", "groupies"=>2}, @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding))
357
+ assert_equal({"name"=>"No One's Choice", "groupies"=>2}, @band.to_hash)
352
358
  end
353
359
 
354
360
  it "accepts :exclude option" do
355
- hash = @band.send(:create_representation_with, {}, {:exclude => [:groupies]}, Representable::Hash::PropertyBinding)
361
+ hash = @band.to_hash({:exclude => [:groupies]})
356
362
  assert_equal({"name"=>"No One's Choice"}, hash)
357
363
  end
358
364
 
359
365
  it "accepts :include option" do
360
- hash = @band.send(:create_representation_with, {}, {:include => [:groupies]}, Representable::Hash::PropertyBinding)
366
+ hash = @band.to_hash({:include => [:groupies]})
361
367
  assert_equal({"groupies"=>2}, hash)
362
368
  end
363
369
 
@@ -366,18 +372,18 @@ class RepresentableTest < MiniTest::Spec
366
372
  @band.name = "Iron Maiden"
367
373
  @band.founder_ids = [1,2,3]
368
374
 
369
- hash = @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding)
375
+ hash = @band.to_hash
370
376
  assert_equal({"name" => "Iron Maiden"}, hash)
371
377
  end
372
378
 
373
379
  it "does not write nil attributes" do
374
380
  @band.groupies = nil
375
- assert_equal({"name"=>"No One's Choice"}, @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding))
381
+ assert_equal({"name"=>"No One's Choice"}, @band.to_hash)
376
382
  end
377
383
 
378
384
  it "writes false attributes" do
379
385
  @band.groupies = false
380
- assert_equal({"name"=>"No One's Choice","groupies"=>false}, @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding))
386
+ assert_equal({"name"=>"No One's Choice","groupies"=>false}, @band.to_hash)
381
387
  end
382
388
 
383
389
  describe "when :render_nil is true" do
@@ -390,7 +396,7 @@ class RepresentableTest < MiniTest::Spec
390
396
 
391
397
  @band.extend(mod) # FIXME: use clean object.
392
398
  @band.groupies = nil
393
- hash = @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding)
399
+ hash = @band.to_hash
394
400
  assert_equal({"name"=>"No One's Choice", "groupies" => nil}, hash)
395
401
  end
396
402
 
@@ -403,7 +409,7 @@ class RepresentableTest < MiniTest::Spec
403
409
 
404
410
  @band.extend(mod) # FIXME: use clean object.
405
411
  @band.groupies = nil
406
- hash = @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding)
412
+ hash = @band.to_hash
407
413
  assert_equal({"name"=>"No One's Choice", "groupies" => nil}, hash)
408
414
  end
409
415
  end
@@ -422,6 +428,63 @@ class RepresentableTest < MiniTest::Spec
422
428
  end
423
429
  end
424
430
 
431
+ describe "inline representers" do
432
+ let (:song) { Song.new("Alive") }
433
+ let (:request) { representer.prepare(OpenStruct.new(:song => song)) }
434
+
435
+ {
436
+ :hash => [Representable::Hash, {"song"=>{"name"=>"Alive"}}, {"song"=>{"name"=>"You've Taken Everything"}}],
437
+ :json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"],
438
+ :xml => [Representable::XML, "<open_struct>\n <song>\n <name>Alive</name>\n </song>\n</open_struct>", "<open_struct><song><name>You've Taken Everything</name></song>/open_struct>"],
439
+ :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"],
440
+ }.each do |format, cfg|
441
+ mod, output, input = cfg
442
+
443
+ describe "[#{format}] with :class" do
444
+ representer!(mod) do
445
+ property :song, :class => Song do
446
+ property :name
447
+ end
448
+ end
449
+
450
+ it { request.send("to_#{format}").must_equal output }
451
+ it { request.send("from_#{format}", input).song.name.must_equal "You've Taken Everything"}
452
+ end
453
+ end
454
+
455
+ describe "without :class" do
456
+ representer! do
457
+ property :song do
458
+ property :name
459
+ end
460
+ end
461
+
462
+ it { request.to_hash.must_equal({"song"=>{"name"=>"Alive"}}) }
463
+ end
464
+
465
+ describe "decorator" do
466
+ let (:representer) do
467
+ Class.new(Representable::Decorator) do
468
+ include Representable::Hash
469
+
470
+ property :song, :class => Song do
471
+ property :name
472
+ end
473
+
474
+ self
475
+ end
476
+ end
477
+
478
+ it { request.to_hash.must_equal({"song"=>{"name"=>"Alive"}}) }
479
+ it { request.from_hash({"song"=>{"name"=>"You've Taken Everything"}}).song.name.must_equal "You've Taken Everything"}
480
+
481
+ it "uses an inline decorator" do
482
+ request.to_hash
483
+ song.wont_be_kind_of Representable
484
+ end
485
+ end
486
+ end
487
+
425
488
  describe ":if" do
426
489
  before do
427
490
  @pop = Class.new(PopBand) { attr_accessor :fame }
@@ -430,21 +493,21 @@ class RepresentableTest < MiniTest::Spec
430
493
  it "respects property when condition true" do
431
494
  @pop.class_eval { property :fame, :if => lambda { true } }
432
495
  band = @pop.new
433
- band.update_properties_from({"fame"=>"oh yes"}, {}, Representable::Hash::PropertyBinding)
496
+ band.from_hash({"fame"=>"oh yes"})
434
497
  assert_equal "oh yes", band.fame
435
498
  end
436
499
 
437
500
  it "ignores property when condition false" do
438
501
  @pop.class_eval { property :fame, :if => lambda { false } }
439
502
  band = @pop.new
440
- band.update_properties_from({"fame"=>"oh yes"}, {}, Representable::Hash::PropertyBinding)
503
+ band.from_hash({"fame"=>"oh yes"})
441
504
  assert_equal nil, band.fame
442
505
  end
443
506
 
444
507
  it "ignores property when :exclude'ed even when condition is true" do
445
508
  @pop.class_eval { property :fame, :if => lambda { true } }
446
509
  band = @pop.new
447
- band.update_properties_from({"fame"=>"oh yes"}, {:exclude => [:fame]}, Representable::Hash::PropertyBinding)
510
+ band.from_hash({"fame"=>"oh yes"}, {:exclude => [:fame]})
448
511
  assert_equal nil, band.fame
449
512
  end
450
513
 
@@ -452,7 +515,7 @@ class RepresentableTest < MiniTest::Spec
452
515
  @pop.class_eval { property :fame, :if => lambda { groupies } }
453
516
  band = @pop.new
454
517
  band.groupies = true
455
- band.update_properties_from({"fame"=>"oh yes"}, {}, Representable::Hash::PropertyBinding)
518
+ band.from_hash({"fame"=>"oh yes"})
456
519
  assert_equal "oh yes", band.fame
457
520
  end
458
521
 
@@ -793,11 +856,68 @@ class RepresentableTest < MiniTest::Spec
793
856
  end
794
857
  end
795
858
 
796
- describe "clone" do
859
+ describe "#clone" do
797
860
  it "clones all definitions" do
798
861
  subject << Object.new
799
862
  assert subject.first != subject.clone.first
800
863
  end
801
864
  end
865
+
866
+ describe "Config inheritance" do
867
+ # TODO: this section will soon be moved to uber.
868
+ describe "inheritance when including" do
869
+ # TODO: test all the below issues AND if cloning works.
870
+ module TestMethods
871
+ def representer_for(modules=[Representable], &block)
872
+ Module.new do
873
+ extend TestMethods
874
+ include *modules
875
+ module_exec(&block)
876
+ end
877
+ end
878
+ end
879
+ include TestMethods
880
+
881
+ it "inherits to uninitialized child" do
882
+ representer_for do # child
883
+ include(representer_for do # parent
884
+ representable_attrs.inheritable_array(:links) << "bar"
885
+ end)
886
+ end.representable_attrs.inheritable_array(:links).must_equal(["bar"])
887
+ end
888
+
889
+ it "works with uninitialized parent" do
890
+ representer_for do # child
891
+ representable_attrs.inheritable_array(:links) << "bar"
892
+
893
+ include(representer_for do # parent
894
+ end)
895
+ end.representable_attrs.inheritable_array(:links).must_equal(["bar"])
896
+ end
897
+
898
+ it "inherits when both are initialized" do
899
+ representer_for do # child
900
+ representable_attrs.inheritable_array(:links) << "bar"
901
+
902
+ include(representer_for do # parent
903
+ representable_attrs.inheritable_array(:links) << "stadium"
904
+ end)
905
+ end.representable_attrs.inheritable_array(:links).must_equal(["bar", "stadium"])
906
+ end
907
+
908
+ it "clones parent inheritables" do # FIXME: actually we don't clone here!
909
+ representer_for do # child
910
+ representable_attrs.inheritable_array(:links) << "bar"
911
+
912
+ include(parent = representer_for do # parent
913
+ representable_attrs.inheritable_array(:links) << "stadium"
914
+ end)
915
+
916
+ parent.representable_attrs.inheritable_array(:links) << "park" # modify parent array.
917
+
918
+ end.representable_attrs.inheritable_array(:links).must_equal(["bar", "stadium"])
919
+ end
920
+ end
921
+ end
802
922
  end
803
923
  end