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,78 @@
1
+ require 'test_helper'
2
+
3
+ class IfTest < MiniTest::Spec
4
+ let (:band_class) { Class.new do
5
+ include Representable::Hash
6
+ attr_accessor :fame
7
+ self
8
+ end }
9
+
10
+ it "respects property when condition true" do
11
+ band_class.class_eval { property :fame, :if => lambda { |*| true } }
12
+ band = band_class.new
13
+ band.from_hash({"fame"=>"oh yes"})
14
+ assert_equal "oh yes", band.fame
15
+ end
16
+
17
+ it "ignores property when condition false" do
18
+ band_class.class_eval { property :fame, :if => lambda { |*| false } }
19
+ band = band_class.new
20
+ band.from_hash({"fame"=>"oh yes"})
21
+ assert_equal nil, band.fame
22
+ end
23
+
24
+ it "ignores property when :exclude'ed even when condition is true" do
25
+ band_class.class_eval { property :fame, :if => lambda { |*| true } }
26
+ band = band_class.new
27
+ band.from_hash({"fame"=>"oh yes"}, {:exclude => [:fame]})
28
+ assert_equal nil, band.fame
29
+ end
30
+
31
+ it "executes block in instance context" do
32
+ band_class.class_eval { property :fame, :if => lambda { |*| groupies }; attr_accessor :groupies }
33
+ band = band_class.new
34
+ band.groupies = true
35
+ band.from_hash({"fame"=>"oh yes"})
36
+ assert_equal "oh yes", band.fame
37
+ end
38
+
39
+ describe "executing :if lambda in represented instance context" do
40
+ representer! do
41
+ property :label, :if => lambda { |*| signed_contract }
42
+ end
43
+
44
+ subject { OpenStruct.new(:signed_contract => false, :label => "Fat") }
45
+
46
+ it "skips when false" do
47
+ subject.extend(representer).to_hash.must_equal({})
48
+ end
49
+
50
+ it "represents when true" do
51
+ subject.signed_contract= true
52
+ subject.extend(representer).to_hash.must_equal({"label"=>"Fat"})
53
+ end
54
+
55
+ it "works with decorator" do
56
+ rpr = representer
57
+ Class.new(Representable::Decorator) do
58
+ include rpr
59
+ end.new(subject).to_hash.must_equal({})
60
+ end
61
+ end
62
+
63
+
64
+ describe "propagating user options to the block" do
65
+ representer! do
66
+ property :name, :if => lambda { |opts| opts[:include_name] }
67
+ end
68
+ subject { OpenStruct.new(:name => "Outbound").extend(representer) }
69
+
70
+ it "works without specifying options" do
71
+ subject.to_hash.must_equal({})
72
+ end
73
+
74
+ it "passes user options to block" do
75
+ subject.to_hash(:include_name => true).must_equal({"name" => "Outbound"})
76
+ end
77
+ end
78
+ end
@@ -66,11 +66,31 @@ class InheritTest < MiniTest::Spec
66
66
  representer! do
67
67
  include SongRepresenter
68
68
 
69
- property :name, :inherit => true, :as => :name
69
+ property :name, :inherit => true, :as => :name # FIXME: add :getter or something else dynamic since this is double-wrapped.
70
70
  end
71
71
 
72
72
  it { SongRepresenter.prepare(Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash.must_equal({"title"=>{"str"=>"Believe It"}, "no"=>1}) }
73
73
  it { representer.prepare( Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash.must_equal({"name"=>{"str"=>"Believe It"}, "no"=>1}) }
74
74
  end
75
75
 
76
+
77
+
78
+ # no :inherit
79
+ describe "overwriting without :inherit" do
80
+ representer! do
81
+ include SongRepresenter
82
+
83
+ property :track, :representable => true
84
+ end
85
+
86
+ it "replaces inherited property" do
87
+ representer.representable_attrs.size.must_equal 2
88
+
89
+ definition = representer.representable_attrs[:track] # TODO: find a better way to assert Definition identity.
90
+ definition.keys.size.must_equal 2
91
+ definition[:representable]. must_equal true
92
+ definition[:as].evaluate(nil).must_equal "track" # was "no".
93
+ end
94
+ end
95
+
76
96
  end
@@ -10,7 +10,7 @@ class InheritanceTest < MiniTest::Spec
10
10
  # Decorator.new.representable_attrs != Decorator.representable_attrs
11
11
  it "doesn't clone for instantiated decorator" do
12
12
  instance = decorator.new(Object.new)
13
- instance.send(:representable_attrs).first.options[:instance] = true
13
+ instance.send(:representable_attrs).first.merge!(:instance => true)
14
14
 
15
15
  # we didn't clone and thereby change the original config:
16
16
  instance.send(:representable_attrs).to_s.must_equal decorator.representable_attrs.to_s
@@ -27,7 +27,7 @@ class InlineTest < MiniTest::Spec
27
27
  {
28
28
  :hash => [Representable::Hash, {"songs"=>[{"name"=>"Alive"}]}, {"songs"=>[{"name"=>"You've Taken Everything"}]}],
29
29
  :json => [Representable::JSON, "{\"songs\":[{\"name\":\"Alive\"}]}", "{\"songs\":[{\"name\":\"You've Taken Everything\"}]}"],
30
- :xml => [Representable::XML, "<open_struct>\n <song>\n <name>Alive</name>\n </song>\n</open_struct>", "<open_struct><song><name>You've Taken Everything</name></song></open_struct>", { :from => :song }],
30
+ :xml => [Representable::XML, "<open_struct>\n <song>\n <name>Alive</name>\n </song>\n</open_struct>", "<open_struct><song><name>You've Taken Everything</name></song></open_struct>", { :as => :song }],
31
31
  :yaml => [Representable::YAML, "---\nsongs:\n- name: Alive\n", "---\nsongs:\n- name: You've Taken Everything\n"],
32
32
  }.each do |format, cfg|
33
33
  mod, output, input, collection_options = cfg
@@ -185,7 +185,7 @@ class InlineTest < MiniTest::Spec
185
185
 
186
186
  property :requester
187
187
  property :song do
188
- property :name, :decorator_scope => true
188
+ property :name, :exec_context => :decorator
189
189
 
190
190
  define_method :name do
191
191
  represented.name.upcase
@@ -202,4 +202,42 @@ class InlineTest < MiniTest::Spec
202
202
  end
203
203
  end
204
204
  end
205
+
206
+
207
+ describe "deprecate mixing :extend and inline representers" do # TODO: remove in 2.0.
208
+ representer! do
209
+ rpr_module = Module.new do
210
+ include Representable::Hash
211
+ property :title
212
+ end
213
+ property :song, :extend => rpr_module do
214
+ property :artist
215
+ end
216
+ end
217
+
218
+ it do OpenStruct.new(:song => OpenStruct.new(:title => "The Fever And The Sound", :artist => "Strung Out")).extend(representer).
219
+ to_hash.
220
+ must_equal({"song"=>{"artist"=>"Strung Out", "title"=>"The Fever And The Sound"}})
221
+ end
222
+ end
223
+
224
+
225
+ describe "include module in inline representers" do
226
+ representer! do
227
+ extension = Module.new do
228
+ include Representable::Hash
229
+ property :title
230
+ end
231
+
232
+ property :song do
233
+ include extension
234
+ property :artist
235
+ end
236
+ end
237
+
238
+ it do OpenStruct.new(:song => OpenStruct.new(:title => "The Fever And The Sound", :artist => "Strung Out")).extend(representer).
239
+ to_hash.
240
+ must_equal({"song"=>{"artist"=>"Strung Out", "title"=>"The Fever And The Sound"}})
241
+ end
242
+ end
205
243
  end
@@ -0,0 +1,286 @@
1
+ require 'test_helper'
2
+
3
+ class InstanceTest < BaseTest
4
+ Song = Struct.new(:id, :title)
5
+ Song.class_eval do
6
+ def self.find(id)
7
+ new(id, "Invincible")
8
+ end
9
+ end
10
+
11
+ describe "lambda { fragment } (new way of class: lambda { nil })" do
12
+ representer! do
13
+ property :title, :instance => lambda { |fragment, args| fragment }
14
+ end
15
+
16
+ it "skips creating new instance" do
17
+ object = Object.new
18
+ object.instance_eval do
19
+ def from_hash(hash, *args)
20
+ hash
21
+ end
22
+ end
23
+
24
+ song = OpenStruct.new.extend(representer).from_hash(hash = {"title" => object})
25
+ song.title.must_equal object
26
+ end
27
+ end
28
+
29
+
30
+ # TODO: use *args in from_hash.
31
+ # DISCUSS: do we need parse_strategy?
32
+ describe "property with :instance" do
33
+ representer!(:inject => :song_representer) do
34
+ property :song,
35
+ :instance => lambda { |fragment, *args| fragment["id"] == song.id ? song : Song.find(fragment["id"]) },
36
+ :extend => song_representer
37
+ end
38
+
39
+ it { OpenStruct.new(:song => Song.new(1, "The Answer Is Still No")).extend(representer).
40
+ from_hash("song" => {"id" => 1}).song.must_equal Song.new(1, "The Answer Is Still No") }
41
+
42
+ it { OpenStruct.new(:song => Song.new(1, "The Answer Is Still No")).extend(representer).
43
+ from_hash("song" => {"id" => 2}).song.must_equal Song.new(2, "Invincible") }
44
+ end
45
+
46
+
47
+ describe "collection with :instance" do
48
+ representer!(:inject => :song_representer) do
49
+ collection :songs,
50
+ :instance => lambda { |fragment, i, *args|
51
+
52
+ fragment["id"] == songs[i].id ? songs[i] : Song.find(fragment["id"])
53
+ }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync.
54
+
55
+ :extend => song_representer
56
+ end
57
+
58
+ it {
59
+ album= Struct.new(:songs).new(songs = [
60
+ Song.new(1, "The Answer Is Still No"),
61
+ Song.new(2, "")])
62
+
63
+ album.
64
+ extend(representer).
65
+ from_hash("songs" => [{"id" => 2},{"id" => 2, "title"=>"The Answer Is Still No"}]).songs.must_equal [
66
+ Song.new(2, "Invincible"), Song.new(2, "The Answer Is Still No")]
67
+ }
68
+ end
69
+
70
+ describe "property with lambda receiving fragment and args" do
71
+ representer!(:inject => :song_representer) do
72
+ property :song, :instance => lambda { |fragment, args| Struct.new(:args, :id).new([fragment, args]) }, :extend => song_representer
73
+ end
74
+
75
+ it { OpenStruct.new(:song => Song.new(1, "The Answer Is Still No")).extend(representer).
76
+ from_hash({"song" => {"id" => 1}}, {:volume => 1}).song.args.must_equal([{"id"=>1}, {:volume=>1}]) }
77
+ end
78
+
79
+ # TODO: raise and test instance:{nil}
80
+ # describe "property with instance: { nil }" do # TODO: introduce :representable option?
81
+ # representer!(:inject => :song_representer) do
82
+ # property :song, :instance => lambda { |*| nil }, :extend => song_representer
83
+ # end
84
+
85
+ # let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
86
+
87
+ # it "calls #to_hash on song instance, nothing else" do
88
+ # hit.to_hash.must_equal("song"=>{"title"=>"Resist Stance"})
89
+ # end
90
+
91
+ # it "calls #from_hash on the existing song instance, nothing else" do
92
+ # song_id = hit.song.object_id
93
+ # hit.from_hash("song"=>{"title"=>"Suffer"})
94
+ # hit.song.title.must_equal "Suffer"
95
+ # hit.song.object_id.must_equal song_id
96
+ # end
97
+ # end
98
+
99
+ # lambda { |fragment, i, Context(binding: <..>, args: [..])| }
100
+
101
+ describe "sync" do
102
+ representer!(:inject => :song_representer) do
103
+ collection :songs,
104
+ :instance => lambda { |fragment, i, *args|
105
+ songs[i]
106
+ },
107
+ :extend => song_representer,
108
+ # :parse_strategy => :sync
109
+ :setter => lambda { |*| }
110
+ end
111
+
112
+ it {
113
+ album= Struct.new(:songs).new(songs = [
114
+ Song.new(1, "The Answer Is Still No"),
115
+ Song.new(2, "Invncble")])
116
+
117
+ album.
118
+ extend(representer).
119
+ from_hash("songs" => [{"title" => "The Answer Is Still No"}, {"title" => "Invincible"}])
120
+
121
+ album.songs.must_equal [
122
+ Song.new(1, "The Answer Is Still No"),
123
+ Song.new(2, "Invincible")]
124
+
125
+ songs.object_id.must_equal album.songs.object_id
126
+ songs[0].object_id.must_equal album.songs[0].object_id
127
+ songs[1].object_id.must_equal album.songs[1].object_id
128
+ }
129
+ end
130
+
131
+ describe "update existing elements, only" do
132
+ representer!(:inject => :song_representer) do
133
+ collection :songs,
134
+ :instance => lambda { |fragment, i, *args|
135
+
136
+ #fragment["id"] == songs[i].id ? songs[i] : Song.find(fragment["id"])
137
+ songs.find { |s| s.id == fragment["id"] }
138
+ }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync.
139
+
140
+ :extend => song_representer,
141
+ # :parse_strategy => :sync
142
+ :setter => lambda { |*| }
143
+ end
144
+
145
+ it {
146
+ album= Struct.new(:songs).new(songs = [
147
+ Song.new(1, "The Answer Is Still No"),
148
+ Song.new(2, "Invncble")])
149
+
150
+ album.
151
+ extend(representer).
152
+ from_hash("songs" => [{"id" => 2, "title" => "Invincible"}]).
153
+ songs.must_equal [
154
+ Song.new(1, "The Answer Is Still No"),
155
+ Song.new(2, "Invincible")]
156
+ # TODO: check elements object_id!
157
+
158
+ songs.object_id.must_equal album.songs.object_id
159
+ songs[0].object_id.must_equal album.songs[0].object_id
160
+ songs[1].object_id.must_equal album.songs[1].object_id
161
+ }
162
+ end
163
+
164
+
165
+ describe "add incoming elements, only" do
166
+ representer!(:inject => :song_representer) do
167
+ collection :songs,
168
+ :instance => lambda { |fragment, i, *args|
169
+ songs << song=Song.new(2)
170
+ song
171
+ }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync.
172
+
173
+ :extend => song_representer,
174
+ # :parse_strategy => :sync
175
+ :setter => lambda { |*| }
176
+ end
177
+
178
+ it {
179
+ album= Struct.new(:songs).new(songs = [
180
+ Song.new(1, "The Answer Is Still No")])
181
+
182
+ album.
183
+ extend(representer).
184
+ from_hash("songs" => [{"title" => "Invincible"}]).
185
+ songs.must_equal [
186
+ Song.new(1, "The Answer Is Still No"),
187
+ Song.new(2, "Invincible")]
188
+
189
+ songs.object_id.must_equal album.songs.object_id
190
+ songs[0].object_id.must_equal album.songs[0].object_id
191
+ }
192
+ end
193
+
194
+
195
+ # not sure if this must be a library strategy
196
+ describe "replace existing element" do
197
+ representer!(:inject => :song_representer) do
198
+ collection :songs,
199
+ :instance => lambda { |fragment, i, *args|
200
+ id = fragment.delete("replace_id")
201
+ replaced = songs.find { |s| s.id == id }
202
+ songs[songs.index(replaced)] = song=Song.new(3)
203
+ song
204
+ }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync.
205
+
206
+ :extend => song_representer,
207
+ # :parse_strategy => :sync
208
+ :setter => lambda { |*| }
209
+ end
210
+
211
+ it {
212
+ album= Struct.new(:songs).new(songs = [
213
+ Song.new(1, "The Answer Is Still No"),
214
+ Song.new(2, "Invincible")])
215
+
216
+ album.
217
+ extend(representer).
218
+ from_hash("songs" => [{"replace_id"=>2, "id" => 3, "title" => "Soulmate"}]).
219
+ songs.must_equal [
220
+ Song.new(1, "The Answer Is Still No"),
221
+ Song.new(3, "Soulmate")]
222
+
223
+ songs.object_id.must_equal album.songs.object_id
224
+ songs[0].object_id.must_equal album.songs[0].object_id
225
+ }
226
+ end
227
+
228
+
229
+ describe "replace collection" do
230
+ representer!(:inject => :song_representer) do
231
+ collection :songs,
232
+ :extend => song_representer, :class => Song
233
+ end
234
+
235
+ it {
236
+ album= Struct.new(:songs).new(songs = [
237
+ Song.new(1, "The Answer Is Still No")])
238
+
239
+ album.
240
+ extend(representer).
241
+ from_hash("songs" => [{"title" => "Invincible"}]).
242
+ songs.must_equal [
243
+ Song.new(nil, "Invincible")]
244
+
245
+ songs.object_id.wont_equal album.songs.object_id
246
+ }
247
+ end
248
+
249
+
250
+ describe "instance: true" do
251
+ representer!(:inject => :song_representer) do
252
+ property :song,
253
+ :extend => song_representer, :instance => true
254
+ end
255
+
256
+ it "uses Binding#get instead of creating an instance, but deprecates" do
257
+ album= Struct.new(:song).new(song = Song.new(1, "The Answer Is Still No"))
258
+
259
+ album.
260
+ extend(representer).
261
+ from_hash("song" => {"title" => "Invincible"}).
262
+ song.must_equal Song.new(1, "Invincible")
263
+
264
+ album.song.object_id.must_equal song.object_id
265
+ end
266
+ end
267
+
268
+
269
+ describe "new syntax for instance: true" do
270
+ representer!(:inject => :song_representer) do
271
+ property :song, :pass_options => true,
272
+ :extend => song_representer, :instance => lambda { |fragment, args| args.binding.get }
273
+ end
274
+
275
+ it "uses Binding#get instead of creating an instance, but deprecates" do
276
+ album= Struct.new(:song).new(song = Song.new(1, "The Answer Is Still No"))
277
+
278
+ album.
279
+ extend(representer).
280
+ from_hash("song" => {"title" => "Invincible"}).
281
+ song.must_equal Song.new(1, "Invincible")
282
+
283
+ album.song.object_id.must_equal song.object_id
284
+ end
285
+ end
286
+ end