representable 1.5.3 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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