representable 1.7.7 → 1.8.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +42 -8
  3. data/README.md +208 -55
  4. data/Rakefile +0 -6
  5. data/lib/representable.rb +39 -43
  6. data/lib/representable/binding.rb +59 -37
  7. data/lib/representable/bindings/hash_bindings.rb +3 -4
  8. data/lib/representable/bindings/xml_bindings.rb +10 -10
  9. data/lib/representable/bindings/yaml_bindings.rb +2 -2
  10. data/lib/representable/coercion.rb +1 -1
  11. data/lib/representable/config.rb +11 -5
  12. data/lib/representable/definition.rb +67 -35
  13. data/lib/representable/deserializer.rb +23 -27
  14. data/lib/representable/hash.rb +15 -4
  15. data/lib/representable/hash/allow_symbols.rb +27 -0
  16. data/lib/representable/json.rb +0 -1
  17. data/lib/representable/json/collection.rb +0 -2
  18. data/lib/representable/mapper.rb +6 -13
  19. data/lib/representable/parse_strategies.rb +57 -0
  20. data/lib/representable/readable_writeable.rb +29 -0
  21. data/lib/representable/serializer.rb +9 -4
  22. data/lib/representable/version.rb +1 -1
  23. data/lib/representable/xml.rb +1 -1
  24. data/lib/representable/xml/collection.rb +0 -2
  25. data/lib/representable/yaml.rb +0 -1
  26. data/representable.gemspec +1 -0
  27. data/test/as_test.rb +43 -0
  28. data/test/class_test.rb +124 -0
  29. data/test/config_test.rb +13 -3
  30. data/test/decorator_scope_test.rb +28 -0
  31. data/test/definition_test.rb +46 -35
  32. data/test/exec_context_test.rb +93 -0
  33. data/test/generic_test.rb +0 -154
  34. data/test/getter_setter_test.rb +28 -0
  35. data/test/hash_bindings_test.rb +35 -35
  36. data/test/hash_test.rb +0 -20
  37. data/test/if_test.rb +78 -0
  38. data/test/inherit_test.rb +21 -1
  39. data/test/inheritance_test.rb +1 -1
  40. data/test/inline_test.rb +40 -2
  41. data/test/instance_test.rb +286 -0
  42. data/test/is_representable_test.rb +77 -0
  43. data/test/json_test.rb +6 -29
  44. data/test/nested_test.rb +30 -0
  45. data/test/parse_strategy_test.rb +249 -0
  46. data/test/pass_options_test.rb +27 -0
  47. data/test/prepare_test.rb +67 -0
  48. data/test/reader_writer_test.rb +19 -0
  49. data/test/representable_test.rb +25 -265
  50. data/test/stringify_hash_test.rb +41 -0
  51. data/test/test_helper.rb +12 -4
  52. data/test/wrap_test.rb +48 -0
  53. data/test/xml_bindings_test.rb +37 -37
  54. data/test/xml_test.rb +14 -14
  55. metadata +94 -30
  56. data/lib/representable/deprecations.rb +0 -4
  57. data/lib/representable/feature/readable_writeable.rb +0 -30
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+
3
+ class PrepareTest < BaseTest
4
+ class PreparerClass
5
+ def initialize(object)
6
+ @object = object
7
+ end
8
+
9
+ def ==(b)
10
+ return unless b.instance_of?(PreparerClass)
11
+
12
+ object == b.object
13
+ end
14
+
15
+ attr_reader :object
16
+ end
17
+
18
+ describe "#to_hash" do # TODO: introduce :representable option?
19
+ representer! do
20
+ property :song,
21
+ :prepare => lambda { |obj, args| args.binding[:arbitrary].new(obj) },
22
+ :arbitrary => PreparerClass,
23
+ :extend => true,
24
+ :pass_options => true,
25
+ :representable => false # don't call #to_hash.
26
+ end
27
+
28
+ let (:hit) { Struct.new(:song).new(song).extend(representer) }
29
+
30
+ it "calls prepare:, nothing else" do
31
+ # render(hit).must_equal_document(output)
32
+ hit.to_hash.must_equal({"song" => PreparerClass.new(song)})
33
+ end
34
+
35
+
36
+ # it "calls #from_hash on the existing song instance, nothing else" do
37
+ # song_id = hit.song.object_id
38
+
39
+ # parse(hit, input)
40
+
41
+ # hit.song.title.must_equal "Suffer"
42
+ # hit.song.object_id.must_equal song_id
43
+ # end
44
+ end
45
+
46
+
47
+ describe "#from_hash" do
48
+ representer! do
49
+ property :song,
50
+ :prepare => lambda { |obj, args| args.binding[:arbitrary].new(obj) },
51
+ :arbitrary => PreparerClass,
52
+ #:extend => true, # TODO: typed: true would be better.
53
+ :instance => String.new, # pass_fragment
54
+ :pass_options => true,
55
+ :representable => false # don't call #to_hash.
56
+ end
57
+
58
+ let (:hit) { Struct.new(:song).new.extend(representer) }
59
+
60
+ it "calls prepare:, nothing else" do
61
+ # render(hit).must_equal_document(output)
62
+ hit.from_hash("song" => {})
63
+
64
+ hit.song.must_equal(PreparerClass.new(String.new))
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ class ReaderWriterTest < BaseTest
4
+ representer! do
5
+ property :name,
6
+ :writer => lambda { |doc, args| doc["title"] = "#{args[:nr]}) #{name}" },
7
+ :reader => lambda { |doc, args| self.name = doc["title"].split(") ").last }
8
+ end
9
+
10
+ subject { OpenStruct.new(:name => "Disorder And Disarray").extend(representer) }
11
+
12
+ it "uses :writer when rendering" do
13
+ subject.to_hash(:nr => 14).must_equal({"title" => "14) Disorder And Disarray"})
14
+ end
15
+
16
+ it "uses :reader when parsing" do
17
+ subject.from_hash({"title" => "15) The Wars End"}).name.must_equal "The Wars End"
18
+ end
19
+ end
@@ -90,7 +90,7 @@ class RepresentableTest < MiniTest::Spec
90
90
  include parent
91
91
  end
92
92
 
93
- assert parent.representable_attrs.first != child.representable_attrs.first, "definitions shouldn't be identical"
93
+ assert parent.representable_attrs.first.object_id != child.representable_attrs.first.object_id, "definitions shouldn't be identical"
94
94
  end
95
95
  end
96
96
  end
@@ -138,7 +138,7 @@ class RepresentableTest < MiniTest::Spec
138
138
  it "allows extending with different representers subsequentially" do
139
139
  module SongXmlRepresenter
140
140
  include Representable::XML
141
- property :name, :from => "name", :attribute => true
141
+ property :name, :as => "name", :attribute => true
142
142
  end
143
143
 
144
144
  module SongJsonRepresenter
@@ -154,51 +154,10 @@ class RepresentableTest < MiniTest::Spec
154
154
 
155
155
 
156
156
  describe "#property" do
157
- describe "overriding" do
158
- representer! do
159
- property :title, :as => :name
160
- end
161
-
162
- it { representer.representable_attrs.size.must_equal 1 }
163
- it { representer.representable_attrs[:title].options.must_equal({:as => :name}) }
164
-
165
- it "overrides property when called again" do
166
- representer.class_eval do
167
- property :title, :representable => true
168
- end
169
-
170
- representer.representable_attrs.size.must_equal 1
171
- representer.representable_attrs[:title].options.must_equal({:representable => true})
172
- end
173
-
174
- it "overrides when inheriting same property" do
175
- overriding = representer! { property :title, :representable => true }
176
-
177
- representer.class_eval do
178
- include overriding
179
- end
180
-
181
- representer.representable_attrs.size.must_equal 1
182
- representer.representable_attrs[:title].options.must_equal({:representable => true})
183
- end
184
- end
185
-
186
- describe ":from" do
187
- # TODO: do this with all options.
188
- it "can be set explicitly" do
189
- band = Class.new(Band) { property :friends, :from => :friend }
190
- assert_equal "friend", band.representable_attrs[:friends].from
191
- end
192
-
193
- it "can be set explicitly with as" do
194
- band = Class.new(Band) { property :friends, :as => :friend }
195
- assert_equal "friend", band.representable_attrs[:friends].from
196
- end
197
-
198
- it "is infered from the name implicitly" do
199
- band = Class.new(Band) { property :friends }
200
- assert_equal "friends", band.representable_attrs[:friends].from
201
- end
157
+ it "doesn't modify options hash" do
158
+ options = {}
159
+ representer.property(:title, options)
160
+ options.must_equal({})
202
161
  end
203
162
 
204
163
  representer! {}
@@ -226,35 +185,6 @@ class RepresentableTest < MiniTest::Spec
226
185
  end
227
186
 
228
187
 
229
- describe "#representation_wrap" do
230
- class HardcoreBand
231
- include Representable
232
- end
233
-
234
- class SoftcoreBand < HardcoreBand
235
- end
236
-
237
- before do
238
- @band = HardcoreBand.new
239
- end
240
-
241
-
242
- it "returns false per default" do
243
- assert_equal nil, SoftcoreBand.new.send(:representation_wrap)
244
- end
245
-
246
- it "infers a printable class name if set to true" do
247
- HardcoreBand.representation_wrap = true
248
- assert_equal "hardcore_band", @band.send(:representation_wrap)
249
- end
250
-
251
- it "can be set explicitely" do
252
- HardcoreBand.representation_wrap = "breach"
253
- assert_equal "breach", @band.send(:representation_wrap)
254
- end
255
- end
256
-
257
-
258
188
  describe "#definition_class" do
259
189
  it "returns Definition class" do
260
190
  assert_equal Representable::Definition, Band.send(:definition_class)
@@ -457,125 +387,6 @@ class RepresentableTest < MiniTest::Spec
457
387
  end
458
388
  end
459
389
 
460
- describe ":if" do
461
- before do
462
- @pop = Class.new(PopBand) { attr_accessor :fame }
463
- end
464
-
465
- it "respects property when condition true" do
466
- @pop.class_eval { property :fame, :if => lambda { true } }
467
- band = @pop.new
468
- band.from_hash({"fame"=>"oh yes"})
469
- assert_equal "oh yes", band.fame
470
- end
471
-
472
- it "ignores property when condition false" do
473
- @pop.class_eval { property :fame, :if => lambda { false } }
474
- band = @pop.new
475
- band.from_hash({"fame"=>"oh yes"})
476
- assert_equal nil, band.fame
477
- end
478
-
479
- it "ignores property when :exclude'ed even when condition is true" do
480
- @pop.class_eval { property :fame, :if => lambda { true } }
481
- band = @pop.new
482
- band.from_hash({"fame"=>"oh yes"}, {:exclude => [:fame]})
483
- assert_equal nil, band.fame
484
- end
485
-
486
- it "executes block in instance context" do
487
- @pop.class_eval { property :fame, :if => lambda { groupies } }
488
- band = @pop.new
489
- band.groupies = true
490
- band.from_hash({"fame"=>"oh yes"})
491
- assert_equal "oh yes", band.fame
492
- end
493
-
494
- describe "executing :if lambda in represented instance context" do
495
- representer! do
496
- property :label, :if => lambda { signed_contract }
497
- end
498
-
499
- subject { OpenStruct.new(:signed_contract => false, :label => "Fat") }
500
-
501
- it "skips when false" do
502
- subject.extend(representer).to_hash.must_equal({})
503
- end
504
-
505
- it "represents when true" do
506
- subject.signed_contract= true
507
- subject.extend(representer).to_hash.must_equal({"label"=>"Fat"})
508
- end
509
-
510
- it "works with decorator" do
511
- rpr = representer
512
- Class.new(Representable::Decorator) do
513
- include rpr
514
- end.new(subject).to_hash.must_equal({})
515
- end
516
- end
517
-
518
-
519
- describe "propagating user options to the block" do
520
- representer! do
521
- property :name, :if => lambda { |opts| opts[:include_name] }
522
- end
523
- subject { OpenStruct.new(:name => "Outbound").extend(representer) }
524
-
525
- it "works without specifying options" do
526
- subject.to_hash.must_equal({})
527
- end
528
-
529
- it "passes user options to block" do
530
- subject.to_hash(:include_name => true).must_equal({"name" => "Outbound"})
531
- end
532
- end
533
- end
534
-
535
- describe ":getter and :setter" do
536
- representer! do
537
- property :name, # key under :name.
538
- :getter => lambda { |args| "#{args[:welcome]} #{song_name}" },
539
- :setter => lambda { |val, args| self.song_name = "#{args[:welcome]} #{val}" }
540
- end
541
-
542
- subject { OpenStruct.new(:song_name => "Mony Mony").extend(representer) }
543
-
544
- it "uses :getter when rendering" do
545
- subject.instance_eval { def name; raise; end }
546
- subject.to_hash(:welcome => "Hi").must_equal({"name" => "Hi Mony Mony"})
547
- end
548
-
549
- it "does not call original reader when rendering" do
550
- subject.instance_eval { def name; raise; end; self }.to_hash
551
- end
552
-
553
- it "uses :setter when parsing" do
554
- subject.from_hash({"name" => "Eyes Without A Face"}, :welcome => "Hello").song_name.must_equal "Hello Eyes Without A Face"
555
- end
556
-
557
- it "does not call original writer when parsing" do
558
- subject.instance_eval { def name=(*); raise; end; self }.from_hash({"name"=>"Dana D And Talle T"})
559
- end
560
- end
561
-
562
- describe ":reader and :writer" do
563
- representer! do
564
- property :name,
565
- :writer => lambda { |doc, args| doc["title"] = "#{args[:nr]}) #{name}" },
566
- :reader => lambda { |doc, args| self.name = doc["title"].split(") ").last }
567
- end
568
-
569
- subject { OpenStruct.new(:name => "Disorder And Disarray").extend(representer) }
570
-
571
- it "uses :writer when rendering" do
572
- subject.to_hash(:nr => 14).must_equal({"title" => "14) Disorder And Disarray"})
573
- end
574
-
575
- it "uses :reader when parsing" do
576
- subject.from_hash({"title" => "15) The Wars End"}).name.must_equal "The Wars End"
577
- end
578
- end
579
390
 
580
391
  describe ":extend and :class" do
581
392
  module UpcaseRepresenter
@@ -642,26 +453,14 @@ class RepresentableTest < MiniTest::Spec
642
453
  end
643
454
 
644
455
  it "creates instance from :class lambda when parsing" do
645
- song = Song.new.extend(representer).from_hash({"name" => "Quitters Never Win"})
456
+ song = OpenStruct.new.extend(representer).from_hash({"name" => "Quitters Never Win"})
646
457
  song.name.must_be_kind_of UpcaseString
647
458
  song.name.must_equal "QUITTERS NEVER WIN"
648
459
 
649
- song = Song.new.extend(representer).from_hash({"name" => "Still Failing?"})
460
+ song = OpenStruct.new.extend(representer).from_hash({"name" => "Still Failing?"})
650
461
  song.name.must_be_kind_of String
651
462
  song.name.must_equal "still failing?"
652
463
  end
653
-
654
- describe "when :class lambda returns nil" do
655
- representer! do
656
- property :name, :extend => lambda { |*| Module.new { include Representable; def from_hash(data, *args); data; end } },
657
- :class => nil
658
- end
659
-
660
- it "skips creating new instance" do
661
- song = Song.new.extend(representer).from_hash({"name" => string = "Satellite"})
662
- song.name.object_id.must_equal string.object_id
663
- end
664
- end
665
464
  end
666
465
  end
667
466
 
@@ -718,62 +517,6 @@ class RepresentableTest < MiniTest::Spec
718
517
  end
719
518
  end
720
519
 
721
- describe ":decorator_scope" do
722
- representer! do
723
- property :title, :getter => lambda { |*| title_from_representer }, :decorator_scope => true
724
- end
725
-
726
- let (:representer_with_method) {
727
- Module.new do
728
- include Representable::Hash
729
- property :title, :decorator_scope => true
730
- def title; "Crystal Planet"; end
731
- end
732
- }
733
-
734
- it "executes lambdas in represented context" do
735
- Class.new do
736
- def title_from_representer
737
- "Sounds Of Silence"
738
- end
739
- end.new.extend(representer).to_hash.must_equal({"title"=>"Sounds Of Silence"})
740
- end
741
-
742
- it "executes method in represented context" do
743
- Object.new.extend(representer_with_method).to_hash.must_equal({"title"=>"Crystal Planet"})
744
- end
745
-
746
- describe "with decorator" do
747
- it "executes lambdas in representer context" do
748
- rpr = representer
749
- Class.new(Representable::Decorator) do
750
- include rpr
751
-
752
- def title_from_representer
753
- "Sounds Of Silence"
754
- end
755
- end.new(Object.new).to_hash.must_equal({"title"=>"Sounds Of Silence"})
756
- end
757
-
758
- it "executes method in representer context" do
759
- rpr = representer_with_method
760
- Class.new(Representable::Decorator) do
761
- include rpr # mixes in #title.
762
- end.new(Object.new).to_hash.must_equal({"title"=>"Crystal Planet"})
763
- end
764
-
765
- it "still allows accessing the represented object" do
766
- Class.new(Representable::Decorator) do
767
- include Representable::Hash
768
- property :title, :getter => lambda { |*| represented.title }, :decorator_scope => true
769
-
770
- def title
771
- "Sounds Of Silence"
772
- end
773
- end.new(OpenStruct.new(:title => "Secret")).to_hash.must_equal({"title"=>"Secret"})
774
- end
775
- end
776
- end
777
520
 
778
521
  # TODO: Move to global place since it's used twice.
779
522
  class SongRepresentation < Representable::Decorator
@@ -807,4 +550,21 @@ class RepresentableTest < MiniTest::Spec
807
550
  end
808
551
  end
809
552
  end
553
+
554
+
555
+ describe "#use_decorator" do
556
+ representer! do
557
+ property :title, :use_decorator => true do
558
+ property :lower
559
+ end
560
+ end
561
+
562
+ it "uses a Decorator for inline representer" do
563
+ outer = Struct.new(:title, :lower, :band, :bla).new(inner = Struct.new(:lower).new("paper wings"))
564
+
565
+ outer.extend(representer).to_hash.must_equal({"title"=>{"lower"=>"paper wings"}})
566
+ outer.must_be_kind_of Representable::Hash
567
+ inner.wont_be_kind_of Representable::Hash
568
+ end
569
+ end
810
570
  end
@@ -0,0 +1,41 @@
1
+ require 'test_helper'
2
+
3
+ class StringifyHashTest < MiniTest::Spec
4
+ describe "#from_hash" do
5
+ representer!(:name => :song_representer) do
6
+
7
+ include Representable::Hash
8
+ include Representable::Hash::AllowSymbols
9
+
10
+ property :title
11
+ end
12
+
13
+ representer!(:inject => :song_representer) do
14
+ include Representable::Hash::AllowSymbols
15
+
16
+ property :song, :extend => song_representer, :class => OpenStruct
17
+ end
18
+
19
+ it "parses symbols, too" do
20
+ OpenStruct.new.extend(representer).from_hash({:song => {:title => "Der Optimist"}}).song.title.must_equal "Der Optimist"
21
+ end
22
+
23
+ it "still parses strings" do
24
+ OpenStruct.new.extend(representer).from_hash({"song" => {"title" => "Der Optimist"}}).song.title.must_equal "Der Optimist"
25
+ end
26
+
27
+ describe "with :wrap" do
28
+ representer!(:inject => :song_representer) do
29
+ include Representable::Hash::AllowSymbols
30
+
31
+ self.representation_wrap = :album
32
+ property :song, :extend => song_representer, :class => OpenStruct
33
+ end
34
+
35
+ it "parses symbols, too" do
36
+ OpenStruct.new.extend(representer).from_hash({:album => {:song => {:title => "Der Optimist"}}}).song.title.must_equal "Der Optimist"
37
+ end
38
+ end
39
+ end
40
+
41
+ end