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.
- checksums.yaml +4 -4
- data/CHANGES.md +42 -8
- data/README.md +208 -55
- data/Rakefile +0 -6
- data/lib/representable.rb +39 -43
- data/lib/representable/binding.rb +59 -37
- data/lib/representable/bindings/hash_bindings.rb +3 -4
- data/lib/representable/bindings/xml_bindings.rb +10 -10
- data/lib/representable/bindings/yaml_bindings.rb +2 -2
- data/lib/representable/coercion.rb +1 -1
- data/lib/representable/config.rb +11 -5
- data/lib/representable/definition.rb +67 -35
- data/lib/representable/deserializer.rb +23 -27
- data/lib/representable/hash.rb +15 -4
- data/lib/representable/hash/allow_symbols.rb +27 -0
- data/lib/representable/json.rb +0 -1
- data/lib/representable/json/collection.rb +0 -2
- data/lib/representable/mapper.rb +6 -13
- data/lib/representable/parse_strategies.rb +57 -0
- data/lib/representable/readable_writeable.rb +29 -0
- data/lib/representable/serializer.rb +9 -4
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +1 -1
- data/lib/representable/xml/collection.rb +0 -2
- data/lib/representable/yaml.rb +0 -1
- data/representable.gemspec +1 -0
- data/test/as_test.rb +43 -0
- data/test/class_test.rb +124 -0
- data/test/config_test.rb +13 -3
- data/test/decorator_scope_test.rb +28 -0
- data/test/definition_test.rb +46 -35
- data/test/exec_context_test.rb +93 -0
- data/test/generic_test.rb +0 -154
- data/test/getter_setter_test.rb +28 -0
- data/test/hash_bindings_test.rb +35 -35
- data/test/hash_test.rb +0 -20
- data/test/if_test.rb +78 -0
- data/test/inherit_test.rb +21 -1
- data/test/inheritance_test.rb +1 -1
- data/test/inline_test.rb +40 -2
- data/test/instance_test.rb +286 -0
- data/test/is_representable_test.rb +77 -0
- data/test/json_test.rb +6 -29
- data/test/nested_test.rb +30 -0
- data/test/parse_strategy_test.rb +249 -0
- data/test/pass_options_test.rb +27 -0
- data/test/prepare_test.rb +67 -0
- data/test/reader_writer_test.rb +19 -0
- data/test/representable_test.rb +25 -265
- data/test/stringify_hash_test.rb +41 -0
- data/test/test_helper.rb +12 -4
- data/test/wrap_test.rb +48 -0
- data/test/xml_bindings_test.rb +37 -37
- data/test/xml_test.rb +14 -14
- metadata +94 -30
- data/lib/representable/deprecations.rb +0 -4
- 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
|
data/test/representable_test.rb
CHANGED
@@ -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, :
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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 =
|
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 =
|
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
|