representable 2.4.0.rc3 → 2.4.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +7 -2
  3. data/Rakefile +6 -0
  4. data/lib/representable.rb +21 -17
  5. data/lib/representable/binding.rb +3 -2
  6. data/lib/representable/definition.rb +1 -1
  7. data/lib/representable/deprecations.rb +31 -5
  8. data/lib/representable/deserializer.rb +24 -34
  9. data/lib/representable/hash/collection.rb +9 -2
  10. data/lib/representable/hash_methods.rb +2 -2
  11. data/lib/representable/parse_strategies.rb +6 -7
  12. data/lib/representable/pipeline.rb +12 -1
  13. data/lib/representable/pipeline_factories.rb +9 -2
  14. data/lib/representable/serializer.rb +3 -3
  15. data/lib/representable/version.rb +1 -1
  16. data/test-with-deprecations/as_test.rb +65 -0
  17. data/test-with-deprecations/benchmarking.rb +83 -0
  18. data/test-with-deprecations/binding_test.rb +46 -0
  19. data/test-with-deprecations/blaaaaaaaa_test.rb +69 -0
  20. data/test-with-deprecations/cached_test.rb +147 -0
  21. data/test-with-deprecations/class_test.rb +119 -0
  22. data/test-with-deprecations/coercion_test.rb +52 -0
  23. data/test-with-deprecations/config/inherit_test.rb +135 -0
  24. data/test-with-deprecations/config_test.rb +122 -0
  25. data/test-with-deprecations/decorator_scope_test.rb +28 -0
  26. data/test-with-deprecations/decorator_test.rb +96 -0
  27. data/test-with-deprecations/default_test.rb +34 -0
  28. data/test-with-deprecations/defaults_options_test.rb +93 -0
  29. data/test-with-deprecations/definition_test.rb +264 -0
  30. data/test-with-deprecations/example.rb +310 -0
  31. data/test-with-deprecations/examples/object.rb +31 -0
  32. data/test-with-deprecations/exec_context_test.rb +93 -0
  33. data/test-with-deprecations/features_test.rb +70 -0
  34. data/test-with-deprecations/filter_test.rb +57 -0
  35. data/test-with-deprecations/for_collection_test.rb +74 -0
  36. data/test-with-deprecations/generic_test.rb +116 -0
  37. data/test-with-deprecations/getter_setter_test.rb +21 -0
  38. data/test-with-deprecations/hash_bindings_test.rb +87 -0
  39. data/test-with-deprecations/hash_test.rb +160 -0
  40. data/test-with-deprecations/heritage_test.rb +62 -0
  41. data/test-with-deprecations/if_test.rb +79 -0
  42. data/test-with-deprecations/include_exclude_test.rb +88 -0
  43. data/test-with-deprecations/inherit_test.rb +159 -0
  44. data/test-with-deprecations/inline_test.rb +272 -0
  45. data/test-with-deprecations/instance_test.rb +266 -0
  46. data/test-with-deprecations/is_representable_test.rb +77 -0
  47. data/test-with-deprecations/json_test.rb +355 -0
  48. data/test-with-deprecations/lonely_test.rb +239 -0
  49. data/test-with-deprecations/mongoid_test.rb +31 -0
  50. data/test-with-deprecations/nested_test.rb +115 -0
  51. data/test-with-deprecations/object_test.rb +60 -0
  52. data/{test/---deserialize-pipeline_test.rb → test-with-deprecations/parse_pipeline_test.rb} +29 -2
  53. data/test-with-deprecations/parse_strategy_test.rb +279 -0
  54. data/{test → test-with-deprecations}/pass_options_test.rb +0 -0
  55. data/test-with-deprecations/pipeline_test.rb +277 -0
  56. data/test-with-deprecations/populator_test.rb +105 -0
  57. data/test-with-deprecations/prepare_test.rb +67 -0
  58. data/test-with-deprecations/private_options_test.rb +18 -0
  59. data/test-with-deprecations/reader_writer_test.rb +19 -0
  60. data/test-with-deprecations/realistic_benchmark.rb +115 -0
  61. data/test-with-deprecations/render_nil_test.rb +21 -0
  62. data/test-with-deprecations/represent_test.rb +88 -0
  63. data/test-with-deprecations/representable_test.rb +511 -0
  64. data/test-with-deprecations/schema_test.rb +148 -0
  65. data/test-with-deprecations/serialize_deserialize_test.rb +33 -0
  66. data/test-with-deprecations/skip_test.rb +81 -0
  67. data/test-with-deprecations/stringify_hash_test.rb +41 -0
  68. data/test-with-deprecations/test_helper.rb +135 -0
  69. data/test-with-deprecations/test_helper_test.rb +25 -0
  70. data/test-with-deprecations/uncategorized_test.rb +67 -0
  71. data/test-with-deprecations/user_options_test.rb +15 -0
  72. data/test-with-deprecations/wrap_test.rb +152 -0
  73. data/test-with-deprecations/xml_bindings_test.rb +62 -0
  74. data/test-with-deprecations/xml_test.rb +503 -0
  75. data/test-with-deprecations/yaml_test.rb +162 -0
  76. data/test/as_test.rb +3 -3
  77. data/test/cached_test.rb +2 -2
  78. data/test/class_test.rb +5 -5
  79. data/test/exec_context_test.rb +2 -2
  80. data/test/filter_test.rb +1 -1
  81. data/test/getter_setter_test.rb +4 -4
  82. data/test/if_test.rb +2 -2
  83. data/test/include_exclude_test.rb +88 -0
  84. data/test/instance_test.rb +15 -15
  85. data/test/lonely_test.rb +18 -2
  86. data/test/object_test.rb +4 -4
  87. data/test/parse_pipeline_test.rb +64 -0
  88. data/test/parse_strategy_test.rb +3 -3
  89. data/test/pipeline_test.rb +8 -12
  90. data/test/prepare_test.rb +2 -3
  91. data/test/reader_writer_test.rb +3 -3
  92. data/test/representable_test.rb +12 -48
  93. data/test/serialize_deserialize_test.rb +9 -9
  94. data/test/skip_test.rb +11 -11
  95. data/test/test_helper.rb +2 -0
  96. data/test/uncategorized_test.rb +10 -10
  97. data/test/user_options_test.rb +15 -0
  98. data/test/wrap_test.rb +1 -1
  99. metadata +65 -4
@@ -0,0 +1,18 @@
1
+ require "test_helper"
2
+
3
+ class PrivateOptionsTest < MiniTest::Spec # TODO: move me to separate file.
4
+ representer!(decorator: true) do
5
+ end
6
+
7
+ options = {exclude: "name"}
8
+
9
+ it "render: doesn't modify options" do
10
+ representer.new(nil).to_hash(options)
11
+ options.must_equal({exclude: "name"})
12
+ end
13
+
14
+ it "parse: doesn't modify options" do
15
+ representer.new(nil).from_hash(options)
16
+ options.must_equal({exclude: "name"})
17
+ end
18
+ 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
@@ -0,0 +1,115 @@
1
+ require 'test_helper'
2
+ require 'benchmark'
3
+
4
+ Kernel.class_eval do
5
+ # def respond_to_missing?
6
+ # raise
7
+ # end
8
+ # alias_method :orig_hash, :hash
9
+ # def hash
10
+ # puts "hash in #{self.class}"
11
+ # raise self.inspect if self.class == Symbol
12
+ # orig_hash
13
+ # end
14
+ end
15
+ Representable.deprecations = false
16
+
17
+ SONG_PROPERTIES = 50.times.collect do |i|
18
+ "song_property_#{i}"
19
+ end
20
+
21
+
22
+ module SongRepresenter
23
+ include Representable::JSON
24
+
25
+ SONG_PROPERTIES.each { |p| property p }
26
+ end
27
+
28
+ class NestedProperty < Representable::Decorator
29
+ include Representable::JSON
30
+
31
+ SONG_PROPERTIES.each { |p| property p }
32
+ end
33
+
34
+
35
+ class SongDecorator < Representable::Decorator
36
+ include Representable::JSON
37
+
38
+ SONG_PROPERTIES.each { |p| property p, extend: NestedProperty }
39
+ end
40
+
41
+ class AlbumRepresenter < Representable::Decorator
42
+ include Representable::JSON
43
+
44
+ # collection :songs, extend: SongRepresenter
45
+ collection :songs, extend: SongDecorator
46
+ end
47
+
48
+ Song = Struct.new(*SONG_PROPERTIES.map(&:to_sym))
49
+ Album = Struct.new(:songs)
50
+
51
+ def random_song
52
+ Song.new(*SONG_PROPERTIES.collect { |p| Song.new(*SONG_PROPERTIES) })
53
+ end
54
+
55
+ times = []
56
+
57
+ 3.times.each do
58
+ album = Album.new(100.times.collect { random_song })
59
+
60
+ times << Benchmark.measure do
61
+ puts "================ next!"
62
+ AlbumRepresenter.new(album).to_json
63
+ end
64
+ end
65
+
66
+ puts times.join("")
67
+
68
+ album = Album.new(100.times.collect { random_song })
69
+ require 'ruby-prof'
70
+ RubyProf.start
71
+ AlbumRepresenter.new(album).to_hash
72
+ res = RubyProf.stop
73
+ printer = RubyProf::FlatPrinter.new(res)
74
+ printer.print(array = [])
75
+
76
+ array[0..60].each { |a| puts a }
77
+
78
+ # 100 songs, 100 attrs
79
+ # 0.050000 0.000000 0.050000 ( 0.093157)
80
+
81
+ ## 100 songs, 1000 attrs
82
+ # 0.470000 0.010000 0.480000 ( 0.483708)
83
+
84
+
85
+ ### without binding cache:
86
+ # 2.790000 0.030000 2.820000 ( 2.820190)
87
+
88
+
89
+
90
+ ### with extend: on Song, with binding cache>
91
+ # 2.490000 0.030000 2.520000 ( 2.517433) 2.4-3.0
92
+ ### without skip?
93
+ # 2.030000 0.020000 2.050000 ( 2.050796) 2.1-2.3
94
+
95
+ ### without :writer
96
+ # 2.270000 0.010000 2.280000 ( 2.284530 1.9-2.2
97
+ ### without :render_filter
98
+ # 2.020000 0.000000 2.020000 ( 2.030234) 1.5-2.0
99
+ ###without default_for and skipable?
100
+ # 1.730000 0.010000 1.740000 ( 1.735597 1.4-1.7
101
+ ### without :serialize
102
+ # 1.780000 0.010000 1.790000 ( 1.786791) 1.4-1.7
103
+ ### using decorator
104
+ # 1.400000 0.030000 1.430000 ( 1.434206) 1.4-1.6
105
+ ### with prepare AFTER representable?
106
+ # 1.330000 0.010000 1.340000 ( 1.335900) 1.1-1.3
107
+
108
+
109
+ # representable 2.0
110
+ # 3.000000 0.020000 3.020000 ( 3.013031) 2.7-3.0
111
+
112
+ # no method missing
113
+ # 2.280000 0.030000 2.310000 ( 2.313522) 2.2-2.5
114
+ # no def_delegator in Definition
115
+ # 2.130000 0.010000 2.140000 ( 2.136115) 1.7-2.1
@@ -0,0 +1,21 @@
1
+ require "test_helper"
2
+
3
+ class RenderNilTest < MiniTest::Spec
4
+ Song = Struct.new(:title)
5
+
6
+ describe "render_nil: true" do
7
+ representer! do
8
+ property :title, render_nil: true
9
+ end
10
+
11
+ it { Song.new.extend(representer).to_hash.must_equal({"title"=>nil}) }
12
+ end
13
+
14
+ describe "with :extend it shouldn't extend nil" do
15
+ representer! do
16
+ property :title, render_nil: true, extend: Class
17
+ end
18
+
19
+ it { Song.new.extend(representer).to_hash.must_equal({"title"=>nil}) }
20
+ end
21
+ end
@@ -0,0 +1,88 @@
1
+ require 'test_helper'
2
+
3
+ class RepresentTest < MiniTest::Spec
4
+ let (:songs) { [song, Song.new("Can't Take Them All")] }
5
+ let (:song) { Song.new("Days Go By") }
6
+
7
+ for_formats(
8
+ :hash => [Representable::Hash, out=[{"name" => "Days Go By"}, {"name"=>"Can't Take Them All"}], out],
9
+ # :json => [Representable::JSON, out="[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]", out],
10
+ # :xml => [Representable::XML, out="<a><song></song><song></song></a>", out]
11
+ ) do |format, mod, output, input|
12
+
13
+ # Representer.represents detects collection.
14
+ describe "Module#to_/from_#{format}" do
15
+ let (:format) { format }
16
+
17
+ let (:representer) {
18
+ Module.new do
19
+ include mod
20
+ property :name
21
+
22
+ collection_representer :class => Song # TODOOOOOOOOOOOO: test without Song and fix THIS FUCKINGNoMethodError: undefined method `name=' for {"name"=>"Days Go By"}:Hash ERROR!!!!!!!!!!!!!!!
23
+ end
24
+ }
25
+
26
+ it { render(representer.represent(songs)).must_equal_document output }
27
+ it { parse(representer.represent([]), input).must_equal songs }
28
+ end
29
+
30
+ # Decorator.represents detects collection.
31
+ describe "Decorator#to_/from_#{format}" do
32
+ let (:format) { format }
33
+ let (:representer) {
34
+ Class.new(Representable::Decorator) do
35
+ include mod
36
+ property :name
37
+
38
+ collection_representer :class => Song
39
+ end
40
+ }
41
+
42
+ it { render(representer.represent(songs)).must_equal_document output }
43
+ it("ficken") { parse(representer.represent([]), input).must_equal songs }
44
+ end
45
+ end
46
+
47
+
48
+ for_formats(
49
+ :hash => [Representable::Hash, out={"name" => "Days Go By"}, out],
50
+ :json => [Representable::JSON, out="{\"name\":\"Days Go By\"}", out],
51
+ # :xml => [Representable::XML, out="<a><song></song><song></song></a>", out]
52
+ ) do |format, mod, output, input|
53
+
54
+ # Representer.represents detects singular.
55
+ describe "Module#to_/from_#{format}" do
56
+ let (:format) { format }
57
+
58
+ let (:representer) {
59
+ Module.new do
60
+ include mod
61
+ property :name
62
+
63
+ collection_representer :class => Song
64
+ end
65
+ }
66
+
67
+ it { render(representer.represent(song)).must_equal_document output }
68
+ it { parse(representer.represent(Song.new), input).must_equal song }
69
+ end
70
+
71
+
72
+ # Decorator.represents detects singular.
73
+ describe "Decorator#to_/from_#{format}" do
74
+ let (:format) { format }
75
+ let (:representer) {
76
+ Class.new(Representable::Decorator) do
77
+ include mod
78
+ property :name
79
+
80
+ collection_representer :class => Song
81
+ end
82
+ }
83
+
84
+ it { render(representer.represent(song)).must_equal_document output }
85
+ it { parse(representer.represent(Song.new), input).must_equal song }
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,511 @@
1
+ require 'test_helper'
2
+
3
+ class RepresentableTest < MiniTest::Spec
4
+ class Band
5
+ include Representable::Hash
6
+ property :name
7
+ attr_accessor :name
8
+ end
9
+
10
+ class PunkBand < Band
11
+ property :street_cred
12
+ attr_accessor :street_cred
13
+ end
14
+
15
+ module BandRepresentation
16
+ include Representable
17
+
18
+ property :name
19
+ end
20
+
21
+ module PunkBandRepresentation
22
+ include Representable
23
+ include BandRepresentation
24
+
25
+ property :street_cred
26
+ end
27
+
28
+
29
+ describe "#representable_attrs" do
30
+ describe "in module" do
31
+ it "allows including the concrete representer module later" do
32
+ vd = class VD
33
+ attr_accessor :name, :street_cred
34
+ include Representable::JSON
35
+ include PunkBandRepresentation
36
+ end.new
37
+ vd.name = "Vention Dention"
38
+ vd.street_cred = 1
39
+ assert_json "{\"name\":\"Vention Dention\",\"street_cred\":1}", vd.to_json
40
+ end
41
+
42
+ #it "allows including the concrete representer module only" do
43
+ # require 'representable/json'
44
+ # module RockBandRepresentation
45
+ # include Representable::JSON
46
+ # property :name
47
+ # end
48
+ # vd = class VH
49
+ # include RockBandRepresentation
50
+ # end.new
51
+ # vd.name = "Van Halen"
52
+ # assert_equal "{\"name\":\"Van Halen\"}", vd.to_json
53
+ #end
54
+ end
55
+ end
56
+
57
+
58
+ describe "inheritance" do
59
+ class CoverSong < OpenStruct
60
+ end
61
+
62
+ module SongRepresenter
63
+ include Representable::Hash
64
+ property :name
65
+ end
66
+
67
+ module CoverSongRepresenter
68
+ include Representable::Hash
69
+ include SongRepresenter
70
+ property :by
71
+ end
72
+
73
+ it "merges properties from all ancestors" do
74
+ props = {"name"=>"The Brews", "by"=>"Nofx"}
75
+ assert_equal(props, CoverSong.new(props).extend(CoverSongRepresenter).to_hash)
76
+ end
77
+
78
+ it "allows mixing in multiple representers" do
79
+ require 'representable/json'
80
+ require 'representable/xml'
81
+ class Bodyjar
82
+ include Representable::XML
83
+ include Representable::JSON
84
+ include PunkBandRepresentation
85
+
86
+ self.representation_wrap = "band"
87
+ attr_accessor :name, :street_cred
88
+ end
89
+
90
+ band = Bodyjar.new
91
+ band.name = "Bodyjar"
92
+
93
+ assert_json "{\"band\":{\"name\":\"Bodyjar\"}}", band.to_json
94
+ assert_xml_equal "<band><name>Bodyjar</name></band>", band.to_xml
95
+ end
96
+
97
+ it "allows extending with different representers subsequentially" do
98
+ module SongXmlRepresenter
99
+ include Representable::XML
100
+ property :name, :as => "name", :attribute => true
101
+ end
102
+
103
+ module SongJsonRepresenter
104
+ include Representable::JSON
105
+ property :name
106
+ end
107
+
108
+ @song = Song.new("Days Go By")
109
+ assert_xml_equal "<song name=\"Days Go By\"/>", @song.extend(SongXmlRepresenter).to_xml
110
+ assert_json "{\"name\":\"Days Go By\"}", @song.extend(SongJsonRepresenter).to_json
111
+ end
112
+
113
+
114
+ # test if we call super in
115
+ # ::inherited
116
+ # ::included
117
+ # ::extended
118
+ module Representer
119
+ include Representable # overrides ::inherited.
120
+ end
121
+
122
+ class BaseClass
123
+ def self.inherited(subclass)
124
+ super
125
+ subclass.instance_eval { def other; end }
126
+ end
127
+
128
+ include Representable # overrides ::inherited.
129
+ include Representer
130
+ end
131
+
132
+ class SubClass < BaseClass # triggers Representable::inherited, then OtherModule::inherited.
133
+ end
134
+
135
+ # test ::inherited.
136
+ it do
137
+ BaseClass.respond_to?(:other).must_equal false
138
+ SubClass.respond_to?(:other).must_equal true
139
+ end
140
+
141
+ module DifferentIncluded
142
+ def included(includer)
143
+ includer.instance_eval { def different; end }
144
+ end
145
+ end
146
+
147
+ module CombinedIncluded
148
+ extend DifferentIncluded # defines ::included.
149
+ include Representable # overrides ::included.
150
+ end
151
+
152
+ class IncludingClass
153
+ include Representable
154
+ include CombinedIncluded
155
+ end
156
+
157
+ # test ::included.
158
+ it do
159
+ IncludingClass.respond_to?(:representable_attrs) # from Representable
160
+ IncludingClass.respond_to?(:different)
161
+ end
162
+ end
163
+
164
+
165
+ describe "#property" do
166
+ it "doesn't modify options hash" do
167
+ options = {}
168
+ representer.property(:title, options)
169
+ options.must_equal({})
170
+ end
171
+
172
+ representer! {}
173
+
174
+ it "returns the Definition instance" do
175
+ representer.property(:name).must_be_kind_of Representable::Definition
176
+ end
177
+ end
178
+
179
+ describe "#collection" do
180
+ class RockBand < Band
181
+ collection :albums
182
+ end
183
+
184
+ it "creates correct Definition" do
185
+ assert_equal "albums", RockBand.representable_attrs.get(:albums).name
186
+ assert RockBand.representable_attrs.get(:albums).array?
187
+ end
188
+ end
189
+
190
+ describe "#hash" do
191
+ it "also responds to the original method" do
192
+ assert_kind_of Integer, BandRepresentation.hash
193
+ end
194
+ end
195
+
196
+ class Hometown
197
+ attr_accessor :name
198
+ end
199
+
200
+ module HometownRepresentable
201
+ include Representable::JSON
202
+ property :name
203
+ end
204
+
205
+ # DISCUSS: i don't like the JSON requirement here, what about some generic test module?
206
+ class PopBand
207
+ include Representable::JSON
208
+ property :name
209
+ property :groupies
210
+ property :hometown, class: Hometown, extend: HometownRepresentable
211
+ attr_accessor :name, :groupies, :hometown
212
+ end
213
+
214
+ describe "#update_properties_from" do
215
+ before do
216
+ @band = PopBand.new
217
+ end
218
+
219
+ it "copies values from document to object" do
220
+ @band.from_hash({"name"=>"No One's Choice", "groupies"=>2})
221
+ assert_equal "No One's Choice", @band.name
222
+ assert_equal 2, @band.groupies
223
+ end
224
+
225
+ it "ignores non-writeable properties" do
226
+ @band = Class.new(Band) { property :name; collection :founders, :writeable => false; attr_accessor :founders }.new
227
+ @band.from_hash("name" => "Iron Maiden", "groupies" => 2, "founders" => ["Steve Harris"])
228
+ assert_equal "Iron Maiden", @band.name
229
+ assert_equal nil, @band.founders
230
+ end
231
+
232
+ it "always returns the represented" do
233
+ assert_equal @band, @band.from_hash({"name"=>"Nofx"})
234
+ end
235
+
236
+ it "includes false attributes" do
237
+ @band.from_hash({"groupies"=>false})
238
+ assert_equal false, @band.groupies
239
+ end
240
+
241
+ it "ignores properties not present in the incoming document" do
242
+ @band.instance_eval do
243
+ def name=(*); raise "I should never be called!"; end
244
+ end
245
+ @band.from_hash({})
246
+ end
247
+
248
+
249
+ # FIXME: do we need this test with XML _and_ JSON?
250
+ it "ignores (no-default) properties not present in the incoming document" do
251
+ { Representable::Hash => [:from_hash, {}],
252
+ Representable::XML => [:from_xml, xml(%{<band/>}).to_s]
253
+ }.each do |format, config|
254
+ nested_repr = Module.new do # this module is never applied. # FIXME: can we make that a simpler test?
255
+ include format
256
+ property :created_at
257
+ end
258
+
259
+ repr = Module.new do
260
+ include format
261
+ property :name, :class => Object, :extend => nested_repr
262
+ end
263
+
264
+ @band = Band.new.extend(repr)
265
+ @band.send(config.first, config.last)
266
+ assert_equal nil, @band.name, "Failed in #{format}"
267
+ end
268
+ end
269
+
270
+ describe "passing options" do
271
+ module TrackRepresenter
272
+ include Representable::Hash
273
+
274
+ end
275
+
276
+ representer! do
277
+ property :track, class: OpenStruct do
278
+ property :nr
279
+
280
+ property :length, class: OpenStruct do
281
+ def to_hash(options)
282
+ {seconds: options[:user_options][:nr]}
283
+ end
284
+
285
+ def from_hash(hash, options)
286
+ super.tap do
287
+ self.seconds = options[:user_options][:nr]
288
+ end
289
+ end
290
+ end
291
+
292
+ def to_hash(options)
293
+ super.merge({"nr" => options[:user_options][:nr]})
294
+ end
295
+
296
+ def from_hash(data, options)
297
+ super.tap do
298
+ self.nr = options[:user_options][:nr]
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ it "#to_hash propagates to nested objects" do
305
+ OpenStruct.new(track: OpenStruct.new(nr: 1, length: OpenStruct.new(seconds: nil))).extend(representer).extend(Representable::Debug).
306
+ to_hash(nr: 9).must_equal({"track"=>{"nr"=>9, "length"=>{seconds: 9}}})
307
+ end
308
+
309
+ it "#from_hash propagates to nested objects" do
310
+ song = OpenStruct.new.extend(representer).from_hash({"track"=>{"nr" => "replace me", "length"=>{"seconds"=>"replacing"}}}, :nr => 9)
311
+ song.track.nr.must_equal 9
312
+ song.track.length.seconds.must_equal 9
313
+ end
314
+ end
315
+ end
316
+
317
+ describe "#create_representation_with" do
318
+ before do
319
+ @band = PopBand.new
320
+ @band.name = "No One's Choice"
321
+ @band.groupies = 2
322
+ end
323
+
324
+ it "compiles document from properties in object" do
325
+ assert_equal({"name"=>"No One's Choice", "groupies"=>2}, @band.to_hash)
326
+ end
327
+
328
+ it "ignores non-readable properties" do
329
+ @band = Class.new(Band) { property :name; collection :founder_ids, :readable => false; attr_accessor :founder_ids }.new
330
+ @band.name = "Iron Maiden"
331
+ @band.founder_ids = [1,2,3]
332
+
333
+ hash = @band.to_hash
334
+ assert_equal({"name" => "Iron Maiden"}, hash)
335
+ end
336
+
337
+ it "does not write nil attributes" do
338
+ @band.groupies = nil
339
+ assert_equal({"name"=>"No One's Choice"}, @band.to_hash)
340
+ end
341
+
342
+ it "writes false attributes" do
343
+ @band.groupies = false
344
+ assert_equal({"name"=>"No One's Choice","groupies"=>false}, @band.to_hash)
345
+ end
346
+ end
347
+
348
+
349
+ describe ":extend and :class" do
350
+ module UpcaseRepresenter
351
+ include Representable
352
+ def to_hash(*); upcase; end
353
+ def from_hash(hsh, *args); replace hsh.upcase; end # DISCUSS: from_hash must return self.
354
+ end
355
+ module DowncaseRepresenter
356
+ include Representable
357
+ def to_hash(*); downcase; end
358
+ def from_hash(hsh, *args); replace hsh.downcase; end
359
+ end
360
+ class UpcaseString < String; end
361
+
362
+
363
+ describe "lambda blocks" do
364
+ representer! do
365
+ property :name, :extend => lambda { |name, *| compute_representer(name) }
366
+ end
367
+
368
+ it "executes lambda in represented instance context" do
369
+ Song.new("Carnage").instance_eval do
370
+ def compute_representer(name)
371
+ UpcaseRepresenter
372
+ end
373
+ self
374
+ end.extend(representer).to_hash.must_equal({"name" => "CARNAGE"})
375
+ end
376
+ end
377
+
378
+ describe ":instance" do
379
+ obj = String.new("Fate")
380
+ mod = Module.new { include Representable; def from_hash(*); self; end }
381
+ representer! do
382
+ property :name, :extend => mod, :instance => lambda { |*| obj }
383
+ end
384
+
385
+ it "uses object from :instance but still extends it" do
386
+ song = Song.new.extend(representer).from_hash("name" => "Eric's Had A Bad Day")
387
+ song.name.must_equal obj
388
+ song.name.must_be_kind_of mod
389
+ end
390
+ end
391
+
392
+ describe "property with :extend" do
393
+ representer! do
394
+ property :name, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String
395
+ end
396
+
397
+ it "uses lambda when rendering" do
398
+ assert_equal({"name" => "you make me thick"}, Song.new("You Make Me Thick").extend(representer).to_hash )
399
+ assert_equal({"name" => "STEPSTRANGER"}, Song.new(UpcaseString.new "Stepstranger").extend(representer).to_hash )
400
+ end
401
+
402
+ it "uses lambda when parsing" do
403
+ Song.new.extend(representer).from_hash({"name" => "You Make Me Thick"}).name.must_equal "you make me thick"
404
+ Song.new.extend(representer).from_hash({"name" => "Stepstranger"}).name.must_equal "stepstranger" # DISCUSS: we compare "".is_a?(UpcaseString)
405
+ end
406
+
407
+ describe "with :class lambda" do
408
+ representer! do
409
+ property :name, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter },
410
+ :class => lambda { |fragment, *| fragment == "Still Failing?" ? String : UpcaseString }
411
+ end
412
+
413
+ it "creates instance from :class lambda when parsing" do
414
+ song = OpenStruct.new.extend(representer).from_hash({"name" => "Quitters Never Win"})
415
+ song.name.must_be_kind_of UpcaseString
416
+ song.name.must_equal "QUITTERS NEVER WIN"
417
+
418
+ song = OpenStruct.new.extend(representer).from_hash({"name" => "Still Failing?"})
419
+ song.name.must_be_kind_of String
420
+ song.name.must_equal "still failing?"
421
+ end
422
+ end
423
+ end
424
+
425
+
426
+ describe "collection with :extend" do
427
+ representer! do
428
+ collection :songs, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String
429
+ end
430
+
431
+ it "uses lambda for each item when rendering" do
432
+ Album.new([UpcaseString.new("Dean Martin"), "Charlie Still Smirks"]).extend(representer).to_hash.must_equal("songs"=>["DEAN MARTIN", "charlie still smirks"])
433
+ end
434
+
435
+ it "uses lambda for each item when parsing" do
436
+ album = Album.new.extend(representer).from_hash("songs"=>["DEAN MARTIN", "charlie still smirks"])
437
+ album.songs.must_equal ["dean martin", "charlie still smirks"] # DISCUSS: we compare "".is_a?(UpcaseString)
438
+ end
439
+
440
+ describe "with :class lambda" do
441
+ representer! do
442
+ collection :songs, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter },
443
+ :class => lambda { |fragment, *| fragment == "Still Failing?" ? String : UpcaseString }
444
+ end
445
+
446
+ it "creates instance from :class lambda for each item when parsing" do
447
+ album = Album.new.extend(representer).from_hash("songs"=>["Still Failing?", "charlie still smirks"])
448
+ album.songs.must_equal ["still failing?", "CHARLIE STILL SMIRKS"]
449
+ end
450
+ end
451
+ end
452
+
453
+ describe ":decorator" do
454
+ let (:extend_rpr) { Module.new { include Representable::Hash; collection :songs, :extend => SongRepresenter } }
455
+ let (:decorator_rpr) { Module.new { include Representable::Hash; collection :songs, :decorator => SongRepresenter } }
456
+ let (:songs) { [Song.new("Bloody Mary")] }
457
+
458
+ it "is aliased to :extend" do
459
+ Album.new(songs).extend(extend_rpr).to_hash.must_equal Album.new(songs).extend(decorator_rpr).to_hash
460
+ end
461
+ end
462
+
463
+ describe ":binding" do
464
+ representer! do
465
+ class MyBinding < Representable::Binding
466
+ def write(doc, *args)
467
+ doc[:title] = represented.title
468
+ end
469
+ end
470
+ property :title, :binding => lambda { |*args| MyBinding.new(*args) }
471
+ end
472
+
473
+ it "uses the specified binding instance" do
474
+ OpenStruct.new(:title => "Affliction").extend(representer).to_hash.must_equal({:title => "Affliction"})
475
+ end
476
+ end
477
+
478
+
479
+ # TODO: Move to global place since it's used twice.
480
+ class SongRepresentation < Representable::Decorator
481
+ include Representable::JSON
482
+ property :name
483
+ end
484
+ class AlbumRepresentation < Representable::Decorator
485
+ include Representable::JSON
486
+
487
+ collection :songs, :class => Song, :extend => SongRepresentation
488
+ end
489
+
490
+ describe "::prepare" do
491
+ let (:song) { Song.new("Still Friends In The End") }
492
+ let (:album) { Album.new([song]) }
493
+
494
+ describe "module including Representable" do
495
+ it "uses :extend strategy" do
496
+ album_rpr = Module.new { include Representable::Hash; collection :songs, :class => Song, :extend => SongRepresenter}
497
+
498
+ album_rpr.prepare(album).to_hash.must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]})
499
+ album.must_respond_to :to_hash
500
+ end
501
+ end
502
+
503
+ describe "Decorator subclass" do
504
+ it "uses :decorate strategy" do
505
+ AlbumRepresentation.prepare(album).to_hash.must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]})
506
+ album.wont_respond_to :to_hash
507
+ end
508
+ end
509
+ end
510
+ end
511
+ end