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