representable 1.7.7 → 1.8.0

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