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.
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