representable 2.2.3 → 2.3.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/.travis.yml +1 -1
- data/CHANGES.md +28 -0
- data/Gemfile +5 -0
- data/README.md +48 -0
- data/lib/representable.rb +18 -11
- data/lib/representable/binding.rb +13 -2
- data/lib/representable/config.rb +0 -4
- data/lib/representable/decorator.rb +4 -0
- data/lib/representable/definition.rb +0 -1
- data/lib/representable/deserializer.rb +1 -0
- data/lib/representable/hash.rb +2 -3
- data/lib/representable/hash/binding.rb +1 -1
- data/lib/representable/json.rb +6 -1
- data/lib/representable/populator.rb +1 -1
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +6 -1
- data/representable.gemspec +0 -2
- data/test/as_test.rb +22 -0
- data/test/config/inherit_test.rb +9 -3
- data/test/hash_test.rb +120 -109
- data/test/private_options_test.rb +18 -0
- data/test/representable_test.rb +11 -1
- data/test/wrap_test.rb +104 -0
- metadata +4 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 304ec8f66cc93beff15aba84d2d4b5d5214fa5d1
|
4
|
+
data.tar.gz: 83a928ef451bd032509abb6c41d8ababda744a97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5eb021bc55145ff44e467db742d2842c8971413b44f084df9046f9eb6ee66e5250fa523fae0f8d0f3f0f3938c2e38339ca08876bcd04f6fb291ab8fb4368b0b7
|
7
|
+
data.tar.gz: fc426cbb854f6aef32a99b5bdec3e0f364176cf9ece0d35f19c43200434db12a2d8f545430f139cc8f452baf3598183ba8bd1ab53b6ce6d944512a5ac31c1fa5
|
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
# 2.3.0
|
2
|
+
|
3
|
+
* Remove dependency to Nokogiri and Multi_JSON. You have to add what you need to your `Gemfile`/`gemspec` now.
|
4
|
+
* `to_*`/`from_*` with options do no longer change the hash but work on copies.
|
5
|
+
* `to_*`/`from_*` now respect `wrap: false`. This will suppress the wrapping on the first level.
|
6
|
+
* Introduce `property "name", wrap: false`. This allows reusing existing representers with `representation_wrap` set as nested representers but suppressing the wrapping.
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
class BandDecorator < Representable::Decorator
|
10
|
+
include Representable::Hash
|
11
|
+
self.representation_wrap = :bands # wrap set!
|
12
|
+
|
13
|
+
property :name
|
14
|
+
end
|
15
|
+
|
16
|
+
class AlbumDecorator < Representable::Decorator
|
17
|
+
include Representable::Hash
|
18
|
+
self.representation_wrap = :albums # wrap set!
|
19
|
+
|
20
|
+
property :band, decorator: BandDecorator, wrap: false # you can now set :wrap to false!
|
21
|
+
end
|
22
|
+
|
23
|
+
album.to_hash #=> {"albums" => {"band" => {"name"=>"Social Distortion"}}}
|
24
|
+
```
|
25
|
+
|
26
|
+
Thanks to @fighella for inspiring this feature when working on [roarify](https://github.com/fighella/roarify).
|
27
|
+
* `from_hash` no longer crashes with "NoMethodError: undefined method 'has_key?' for nil:NilClass" when an incoming nested object's value is `nil`. This was a problem with documents like `{song: nil}` where `song` is a nested property. Thanks to @mhuggins and @moxley for helping here.
|
28
|
+
|
1
29
|
# 2.2.3
|
2
30
|
|
3
31
|
* Introduce `Decorator::clone` to make sure cloning properly copies representable_attrs. in former versions, this would partially share definitions with subclasses when the decorator was an `inheritable_attr`.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -15,6 +15,23 @@ The representable gem runs with all Ruby versions >= 1.9.3.
|
|
15
15
|
gem 'representable'
|
16
16
|
```
|
17
17
|
|
18
|
+
### Dependencies
|
19
|
+
|
20
|
+
Representable does a great job with JSON, it also features support for XML, YAML and pure ruby
|
21
|
+
hashes. But Representable did not bundle dependencies for JSON and XML.
|
22
|
+
|
23
|
+
If you want to use JSON, add the following to your Gemfile:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
gem 'multi_json'
|
27
|
+
```
|
28
|
+
|
29
|
+
If you want to use XML, add the following to your Gemfile:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
gem 'nokogiri'
|
33
|
+
```
|
34
|
+
|
18
35
|
## Example
|
19
36
|
|
20
37
|
What if we're writing an API for music - songs, albums, bands.
|
@@ -176,6 +193,37 @@ Album.new.extend(AlbumRepresenter).
|
|
176
193
|
#=> #<Album name="Offspring", songs=[#<Song title="Genocide">, #<Song title="Nitro", composers=["Offspring"]>]>
|
177
194
|
```
|
178
195
|
|
196
|
+
## Suppressing Nested Wraps
|
197
|
+
|
198
|
+
When reusing a representer for a nested document, you might want to suppress the wrap for the nested fragment.
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
module SongRepresenter
|
202
|
+
include Representable::JSON
|
203
|
+
|
204
|
+
self.representation_wrap = :songs
|
205
|
+
property :title
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
When reusing `SongRepresenter` in a nested setup you can suppress the wrapping using the `:wrap` option.
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
module AlbumRepresenter
|
213
|
+
include Representable::JSON
|
214
|
+
|
215
|
+
collection :songs, extend: SongRepresenter, wrap: false
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
The `representation_wrap` from the nested representer now won't be rendered and parsed.
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
album.extend(AlbumRepresenter).to_json #=> "{\"songs\": [{\"name\": \"Roxanne\"}]}"
|
223
|
+
```
|
224
|
+
|
225
|
+
Note that this only works for JSON and Hash at the moment.
|
226
|
+
|
179
227
|
|
180
228
|
## Parse Strategies
|
181
229
|
|
data/lib/representable.rb
CHANGED
@@ -14,8 +14,8 @@ require "representable/serializer"
|
|
14
14
|
require "representable/cached"
|
15
15
|
|
16
16
|
|
17
|
-
require
|
18
|
-
require
|
17
|
+
require "uber/callable"
|
18
|
+
require "representable/pipeline"
|
19
19
|
|
20
20
|
module Representable
|
21
21
|
attr_writer :representable_attrs
|
@@ -36,16 +36,16 @@ module Representable
|
|
36
36
|
private
|
37
37
|
# Reads values from +doc+ and sets properties accordingly.
|
38
38
|
def update_properties_from(doc, options, format)
|
39
|
-
private_options = normalize_options!(options)
|
39
|
+
propagated_options, private_options = normalize_options!(options)
|
40
40
|
|
41
|
-
representable_mapper(format,
|
41
|
+
representable_mapper(format, propagated_options).deserialize(represented, doc, propagated_options, private_options)
|
42
42
|
end
|
43
43
|
|
44
44
|
# Compiles the document going through all properties.
|
45
45
|
def create_representation_with(doc, options, format)
|
46
|
-
private_options = normalize_options!(options)
|
46
|
+
propagated_options, private_options = normalize_options!(options)
|
47
47
|
|
48
|
-
representable_mapper(format,
|
48
|
+
representable_mapper(format, propagated_options).serialize(represented, doc, propagated_options, private_options)
|
49
49
|
end
|
50
50
|
|
51
51
|
def representable_bindings_for(format, options)
|
@@ -56,13 +56,20 @@ private
|
|
56
56
|
format.build(definition, self)
|
57
57
|
end
|
58
58
|
|
59
|
+
# Make sure we do not change original options. However, private options like :include or :wrap are
|
60
|
+
# not passed on to child representers.
|
59
61
|
def normalize_options!(options)
|
60
|
-
#
|
62
|
+
# here, we could also filter out local options e.g. like options[:band].
|
61
63
|
private_options = {}
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
64
|
+
return [options, private_options] if options.size == 0
|
65
|
+
|
66
|
+
propagated_options = options.dup
|
67
|
+
|
68
|
+
private_options[:include] = propagated_options.delete(:include) if options[:include]
|
69
|
+
private_options[:exclude] = propagated_options.delete(:exclude) if options[:exclude]
|
70
|
+
propagated_options.delete(:wrap) # FIXME.
|
71
|
+
|
72
|
+
[propagated_options, private_options]
|
66
73
|
end
|
67
74
|
|
68
75
|
def representable_attrs
|
@@ -44,6 +44,9 @@ module Representable
|
|
44
44
|
@as ||= evaluate_option(:as)
|
45
45
|
end
|
46
46
|
|
47
|
+
# Single entry points for rendering and parsing a property are #compile_fragment
|
48
|
+
# and #uncompile_fragment in Mapper.
|
49
|
+
#
|
47
50
|
# DISCUSS:
|
48
51
|
# currently, we need to call B#update! before compile_fragment/uncompile_fragment.
|
49
52
|
# this will change to B#renderer(represented, options).call
|
@@ -146,15 +149,23 @@ module Representable
|
|
146
149
|
|
147
150
|
# Note: this method is experimental.
|
148
151
|
def update!(represented, user_options)
|
149
|
-
@represented
|
150
|
-
@user_options = user_options
|
152
|
+
@represented = represented
|
151
153
|
|
154
|
+
setup_user_options!(user_options)
|
152
155
|
setup_exec_context!
|
153
156
|
end
|
154
157
|
|
155
158
|
attr_accessor :cached_representer
|
156
159
|
|
157
160
|
private
|
161
|
+
def setup_user_options!(user_options)
|
162
|
+
@user_options = user_options
|
163
|
+
# this is the propagated_options.
|
164
|
+
@user_options = user_options.merge(wrap: false) if self[:wrap] == false
|
165
|
+
|
166
|
+
# @user_options = user_options.merge(wrap: self[:wrap]) if self[:wrap]
|
167
|
+
end
|
168
|
+
|
158
169
|
# 1.80 0.066 0.027 0.000 0.039 30002 Representable::Binding#setup_exec_context!
|
159
170
|
# 0.98 0.034 0.014 0.000 0.020 30002 Representable::Binding#setup_exec_context!
|
160
171
|
def setup_exec_context!
|
data/lib/representable/config.rb
CHANGED
@@ -15,6 +15,7 @@ module Representable
|
|
15
15
|
|
16
16
|
def call(fragment, *args) # FIXME: args is always i.
|
17
17
|
return fragment unless @binding.typed? # customize with :extend. this is not really straight-forward.
|
18
|
+
return fragment if fragment.nil?
|
18
19
|
|
19
20
|
# what if create_object is responsible for providing the deserialize-to object?
|
20
21
|
object = create_object(fragment, *args) # customize with :instance and :class.
|
data/lib/representable/hash.rb
CHANGED
@@ -22,9 +22,6 @@ module Representable
|
|
22
22
|
end
|
23
23
|
|
24
24
|
|
25
|
-
# Note: `#from_hash` still does _not_ stringify incoming hashes. This is per design: Representable is not made for hashes, only,
|
26
|
-
# but for any arbitrary data structure. A generic `key.to_s` with non-hash data would result in weird issues.
|
27
|
-
# I decided it's more predictable to require the user to provide stringified keys.
|
28
25
|
def from_hash(data, options={}, binding_builder=Binding)
|
29
26
|
data = filter_wrap(data, options)
|
30
27
|
|
@@ -34,6 +31,7 @@ module Representable
|
|
34
31
|
def to_hash(options={}, binding_builder=Binding)
|
35
32
|
hash = create_representation_with({}, options, binding_builder)
|
36
33
|
|
34
|
+
return hash if options[:wrap] == false # TODO: same for parse.
|
37
35
|
return hash unless wrap = options[:wrap] || representation_wrap(options)
|
38
36
|
|
39
37
|
{wrap => hash}
|
@@ -41,6 +39,7 @@ module Representable
|
|
41
39
|
|
42
40
|
private
|
43
41
|
def filter_wrap(data, options)
|
42
|
+
return data if options[:wrap] == false
|
44
43
|
return data unless wrap = options[:wrap] || representation_wrap(options)
|
45
44
|
filter_wrap_for(data, wrap)
|
46
45
|
end
|
@@ -11,7 +11,7 @@ module Representable
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def read(hash)
|
14
|
-
return FragmentNotFound unless hash.has_key?(as)
|
14
|
+
return FragmentNotFound unless hash.has_key?(as)
|
15
15
|
|
16
16
|
hash[as] # fragment
|
17
17
|
end
|
data/lib/representable/json.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
-
require 'multi_json'
|
2
1
|
require 'representable/hash'
|
3
2
|
require 'representable/json/collection'
|
4
3
|
|
4
|
+
begin
|
5
|
+
require 'multi_json'
|
6
|
+
rescue LoadError => _
|
7
|
+
abort "Missing dependency 'multi_json' for Representable::JSON. See dependencies section in README.md for details."
|
8
|
+
end
|
9
|
+
|
5
10
|
module Representable
|
6
11
|
# Brings #to_json and #from_json to your object.
|
7
12
|
module JSON
|
@@ -18,7 +18,7 @@ module Representable
|
|
18
18
|
value = @binding[:default]
|
19
19
|
else
|
20
20
|
# DISCUSS: should we return a Skip object instead of this block trick? (same in Binding#serialize?)
|
21
|
-
value = deserialize(fragment) { return } # stop here if skip_parse
|
21
|
+
value = deserialize(fragment) { return } # stop here if skip_parse.
|
22
22
|
end
|
23
23
|
|
24
24
|
value = @binding.parse_filter(value, doc)
|
data/lib/representable/xml.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
require 'representable'
|
2
2
|
require 'representable/xml/binding'
|
3
3
|
require 'representable/xml/collection'
|
4
|
-
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'nokogiri'
|
7
|
+
rescue LoadError => _
|
8
|
+
abort "Missing dependency 'nokogiri' for Representable::XML. See dependencies section in README.md for details."
|
9
|
+
end
|
5
10
|
|
6
11
|
module Representable
|
7
12
|
module XML
|
data/representable.gemspec
CHANGED
data/test/as_test.rb
CHANGED
@@ -40,4 +40,26 @@ class AsTest < MiniTest::Spec
|
|
40
40
|
it { parse(song, {"[{:volume=>1}]" => "Wie Es Geht"}, :volume => 1).name.must_equal "Wie Es Geht" }
|
41
41
|
end
|
42
42
|
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# hash: to_hash(wrap: ) is representation_wrap
|
47
|
+
|
48
|
+
class AsXmlTest < MiniTest::Spec
|
49
|
+
Band = Struct.new(:name, :label)
|
50
|
+
Album = Struct.new(:band)
|
51
|
+
Label = Struct.new(:name)
|
52
|
+
|
53
|
+
representer!(module: Representable::XML, decorator: true) do
|
54
|
+
self.representation_wrap = :album
|
55
|
+
property :band, as: :combo do
|
56
|
+
self.representation_wrap = :band
|
57
|
+
property :name
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it do
|
62
|
+
skip
|
63
|
+
representer.new(Album.new(Band.new("Offspring"))).to_xml.must_equal ""
|
64
|
+
end
|
43
65
|
end
|
data/test/config/inherit_test.rb
CHANGED
@@ -24,9 +24,12 @@ class ConfigInheritTest < MiniTest::Spec
|
|
24
24
|
# in Decorator ------------------------------------------------
|
25
25
|
class Decorator < Representable::Decorator
|
26
26
|
property :title
|
27
|
+
property :artist do
|
28
|
+
property :id
|
29
|
+
end
|
27
30
|
end
|
28
31
|
|
29
|
-
it { Decorator.representable_attrs[:definitions].keys.must_equal ["title"] }
|
32
|
+
it { Decorator.representable_attrs[:definitions].keys.must_equal ["title", "artist"] }
|
30
33
|
|
31
34
|
# in inheriting Decorator
|
32
35
|
|
@@ -34,8 +37,11 @@ class ConfigInheritTest < MiniTest::Spec
|
|
34
37
|
property :location
|
35
38
|
end
|
36
39
|
|
37
|
-
it { InheritingDecorator.representable_attrs[:definitions].keys.must_equal ["title", "location"] }
|
40
|
+
it { InheritingDecorator.representable_attrs[:definitions].keys.must_equal ["title", "artist", "location"] }
|
38
41
|
it { assert_cloned(InheritingDecorator, Decorator, "title") }
|
42
|
+
it do
|
43
|
+
InheritingDecorator.representable_attrs.get(:artist).representer_module.object_id.wont_equal Decorator.representable_attrs.get(:artist).representer_module.object_id
|
44
|
+
end
|
39
45
|
|
40
46
|
# in inheriting and including Decorator
|
41
47
|
|
@@ -44,7 +50,7 @@ class ConfigInheritTest < MiniTest::Spec
|
|
44
50
|
property :location
|
45
51
|
end
|
46
52
|
|
47
|
-
it { InheritingAndIncludingDecorator.representable_attrs[:definitions].keys.must_equal ["title", "genre", "location"] }
|
53
|
+
it { InheritingAndIncludingDecorator.representable_attrs[:definitions].keys.must_equal ["title", "artist", "genre", "location"] }
|
48
54
|
it { assert_cloned(InheritingAndIncludingDecorator, GenreModule, :genre) }
|
49
55
|
|
50
56
|
|
data/test/hash_test.rb
CHANGED
@@ -1,149 +1,160 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
class HashWithScalarPropertyTest < MiniTest::Spec
|
4
|
+
Album = Struct.new(:title)
|
5
|
+
|
6
|
+
representer! do
|
7
|
+
property :title
|
8
|
+
end
|
9
|
+
|
10
|
+
let (:album) { Album.new("Liar") }
|
11
|
+
|
12
|
+
describe "#to_hash" do
|
13
|
+
it "renders plain property" do
|
14
|
+
album.extend(representer).to_hash.must_equal("title" => "Liar")
|
8
15
|
end
|
9
16
|
end
|
10
17
|
|
11
|
-
|
12
|
-
|
18
|
+
|
19
|
+
describe "#from_hash" do
|
20
|
+
it "parses plain property" do
|
21
|
+
album.extend(representer).from_hash("title" => "This Song Is Recycled").title.must_equal "This Song Is Recycled"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Fixes issue #115
|
25
|
+
it "allows nil value in the incoming document and corresponding nil value for the represented" do
|
26
|
+
album = Album.new
|
27
|
+
album.title.must_equal nil
|
28
|
+
album.extend(representer).from_hash("title" => nil)
|
29
|
+
end
|
13
30
|
end
|
31
|
+
end
|
32
|
+
|
14
33
|
|
34
|
+
class HashWithTypedPropertyTest < MiniTest::Spec
|
35
|
+
Album = Struct.new(:best_song)
|
15
36
|
|
16
|
-
|
17
|
-
|
37
|
+
representer! do
|
38
|
+
property :best_song, :class => Song do
|
39
|
+
property :name
|
40
|
+
end
|
41
|
+
end
|
18
42
|
|
19
|
-
|
20
|
-
album.best_song = "Liar"
|
21
|
-
end }
|
43
|
+
let (:album) { Album.new(Song.new("Liar")) }
|
22
44
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
45
|
+
describe "#to_hash" do
|
46
|
+
it "renders embedded typed property" do
|
47
|
+
album.extend(representer).to_hash.must_equal("best_song" => {"name" => "Liar"})
|
27
48
|
end
|
49
|
+
end
|
28
50
|
|
51
|
+
describe "#from_hash" do
|
52
|
+
it "parses embedded typed property" do
|
53
|
+
album.extend(representer).from_hash("best_song" => {"name" => "Go With Me"})
|
54
|
+
album.best_song.name.must_equal "Go With Me"
|
55
|
+
end
|
29
56
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
57
|
+
# nested nil removes nested object.
|
58
|
+
it do
|
59
|
+
album = Album.new(Song.new("Pre-medicated Murder"))
|
60
|
+
album.extend(representer).from_hash("best_song" => nil)
|
61
|
+
album.best_song.must_equal nil
|
34
62
|
end
|
35
63
|
|
64
|
+
# nested blank hash creates blank object when not populated.
|
65
|
+
it do
|
66
|
+
album = Album.new#(Song.new("Pre-medicated Murder"))
|
67
|
+
album.extend(representer).from_hash("best_song" => {})
|
68
|
+
album.best_song.name.must_equal nil
|
69
|
+
end
|
36
70
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
71
|
+
# Fixes issue #115
|
72
|
+
it "allows nil value in the incoming document and corresponding nil value for the represented" do
|
73
|
+
album = Album.new
|
74
|
+
album.extend(representer).from_hash("best_song" => nil)
|
75
|
+
album.best_song.must_equal nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
43
79
|
|
44
|
-
|
45
|
-
|
46
|
-
|
80
|
+
# TODO: move to AsTest.
|
81
|
+
class HashWithTypedPropertyAndAs < MiniTest::Spec
|
82
|
+
representer! do
|
83
|
+
property :song, :class => Song, :as => :hit do
|
84
|
+
property :name
|
85
|
+
end
|
86
|
+
end
|
47
87
|
|
88
|
+
let (:album) { OpenStruct.new(:song => Song.new("Liar")).extend(representer) }
|
48
89
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
90
|
+
it { album.to_hash.must_equal("hit" => {"name" => "Liar"}) }
|
91
|
+
it { album.from_hash("hit" => {"name" => "Go With Me"}).must_equal OpenStruct.new(:song => Song.new("Go With Me")) }
|
92
|
+
end
|
93
|
+
# # describe "FIXME COMBINE WITH ABOVE with :extend and :as" do
|
94
|
+
# # hash_song = Module.new do
|
95
|
+
# # include Representable::XML
|
96
|
+
# # self.representation_wrap = :song
|
97
|
+
# # property :name
|
98
|
+
# # end
|
54
99
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
100
|
+
# # let (:hash_album) { Module.new do
|
101
|
+
# # include Representable::XML
|
102
|
+
# # self.representation_wrap = :album
|
103
|
+
# # property :song, :extend => hash_song, :class => Song, :as => :hit
|
104
|
+
# # end }
|
105
|
+
|
106
|
+
# # let (:album) { OpenStruct.new(:song => Song.new("Liar")).extend(hash_album) }
|
61
107
|
|
108
|
+
# # it { album.to_xml.must_equal_xml("<album><hit><name>Liar</name></hit></album>") }
|
109
|
+
# # #it { album.from_hash("hit" => {"name" => "Go With Me"}).must_equal OpenStruct.new(:song => Song.new("Go With Me")) }
|
110
|
+
# # end
|
111
|
+
# end
|
62
112
|
|
63
|
-
describe "with :extend and :as" do
|
64
|
-
hash_song = hash_representer do property :name end
|
65
113
|
|
66
|
-
let (:hash_album) { Module.new do
|
67
|
-
include Representable::Hash
|
68
|
-
property :song, :extend => hash_song, :class => Song, :as => :hit
|
69
|
-
end }
|
70
114
|
|
71
|
-
|
115
|
+
class HashWithTypedCollectionTest < MiniTest::Spec
|
116
|
+
Album = Struct.new(:songs)
|
72
117
|
|
73
|
-
|
74
|
-
|
118
|
+
representer! do
|
119
|
+
collection :songs, class: Song do
|
120
|
+
property :name
|
121
|
+
property :track
|
75
122
|
end
|
76
|
-
# describe "FIXME COMBINE WITH ABOVE with :extend and :as" do
|
77
|
-
# hash_song = Module.new do
|
78
|
-
# include Representable::XML
|
79
|
-
# self.representation_wrap = :song
|
80
|
-
# property :name
|
81
|
-
# end
|
82
|
-
|
83
|
-
# let (:hash_album) { Module.new do
|
84
|
-
# include Representable::XML
|
85
|
-
# self.representation_wrap = :album
|
86
|
-
# property :song, :extend => hash_song, :class => Song, :as => :hit
|
87
|
-
# end }
|
88
|
-
|
89
|
-
# let (:album) { OpenStruct.new(:song => Song.new("Liar")).extend(hash_album) }
|
90
|
-
|
91
|
-
# it { album.to_xml.must_equal_xml("<album><hit><name>Liar</name></hit></album>") }
|
92
|
-
# #it { album.from_hash("hit" => {"name" => "Go With Me"}).must_equal OpenStruct.new(:song => Song.new("Go With Me")) }
|
93
|
-
# end
|
94
123
|
end
|
95
124
|
|
125
|
+
let (:album) { Album.new([Song.new("Liar", 1), Song.new("What I Know", 2)]) }
|
96
126
|
|
97
|
-
describe "
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end }
|
103
|
-
|
127
|
+
describe "#to_hash" do
|
128
|
+
it "renders collection of typed property" do
|
129
|
+
album.extend(representer).to_hash.must_equal("songs" => [{"name" => "Liar", "track" => 1}, {"name" => "What I Know", "track" => 2}])
|
130
|
+
end
|
131
|
+
end
|
104
132
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
133
|
+
describe "#from_hash" do
|
134
|
+
it "parses collection of typed property" do
|
135
|
+
album.extend(representer).from_hash("songs" => [{"name" => "One Shot Deal", "track" => 4},
|
136
|
+
{"name" => "Three Way Dance", "track" => 5}]).must_equal Album.new([Song.new("One Shot Deal", 4), Song.new("Three Way Dance", 5)])
|
109
137
|
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class HashWithScalarCollectionTest < MiniTest::Spec
|
142
|
+
Album = Struct.new(:songs)
|
143
|
+
representer! { collection :songs }
|
110
144
|
|
145
|
+
let (:album) { Album.new(["Jackhammer", "Terrible Man"]) }
|
111
146
|
|
112
|
-
describe "#from_hash" do
|
113
|
-
it "parses a block style list" do
|
114
|
-
album.extend(hsh).from_hash("songs" => ["Off Key Melody", "Sinking"]).must_equal Album.new(["Off Key Melody", "Sinking"])
|
115
147
|
|
116
|
-
|
148
|
+
describe "#to_hash" do
|
149
|
+
it "renders a block style list per default" do
|
150
|
+
album.extend(representer).to_hash.must_equal("songs" => ["Jackhammer", "Terrible Man"])
|
117
151
|
end
|
152
|
+
end
|
118
153
|
|
119
154
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
property :track
|
124
|
-
end
|
125
|
-
let (:hash_album) { Module.new do
|
126
|
-
include Representable::Hash
|
127
|
-
collection :songs, :extend => hash_song, :class => Song
|
128
|
-
end }
|
129
|
-
|
130
|
-
let (:album) { Album.new.tap do |album|
|
131
|
-
album.songs = [Song.new("Liar", 1), Song.new("What I Know", 2)]
|
132
|
-
end }
|
133
|
-
|
134
|
-
|
135
|
-
describe "#to_hash" do
|
136
|
-
it "renders collection of typed property" do
|
137
|
-
album.extend(hash_album).to_hash.must_equal("songs" => [{"name" => "Liar", "track" => 1}, {"name" => "What I Know", "track" => 2}])
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
describe "#from_hash" do
|
142
|
-
it "parses collection of typed property" do
|
143
|
-
album.extend(hash_album).from_hash("songs" => [{"name" => "One Shot Deal", "track" => 4},
|
144
|
-
{"name" => "Three Way Dance", "track" => 5}]).must_equal Album.new([Song.new("One Shot Deal", 4), Song.new("Three Way Dance", 5)])
|
145
|
-
end
|
146
|
-
end
|
155
|
+
describe "#from_hash" do
|
156
|
+
it "parses a block style list" do
|
157
|
+
album.extend(representer).from_hash("songs" => ["Off Key Melody", "Sinking"]).must_equal Album.new(["Off Key Melody", "Sinking"])
|
147
158
|
end
|
148
159
|
end
|
149
160
|
end
|
@@ -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
|
data/test/representable_test.rb
CHANGED
@@ -193,13 +193,22 @@ class RepresentableTest < MiniTest::Spec
|
|
193
193
|
end
|
194
194
|
end
|
195
195
|
|
196
|
+
class Hometown
|
197
|
+
attr_accessor :name
|
198
|
+
end
|
199
|
+
|
200
|
+
module HometownRepresentable
|
201
|
+
include Representable::JSON
|
202
|
+
property :name
|
203
|
+
end
|
196
204
|
|
197
205
|
# DISCUSS: i don't like the JSON requirement here, what about some generic test module?
|
198
206
|
class PopBand
|
199
207
|
include Representable::JSON
|
200
208
|
property :name
|
201
209
|
property :groupies
|
202
|
-
|
210
|
+
property :hometown, class: Hometown, extend: HometownRepresentable
|
211
|
+
attr_accessor :name, :groupies, :hometown
|
203
212
|
end
|
204
213
|
|
205
214
|
describe "#update_properties_from" do
|
@@ -249,6 +258,7 @@ class RepresentableTest < MiniTest::Spec
|
|
249
258
|
@band.from_hash({})
|
250
259
|
end
|
251
260
|
|
261
|
+
|
252
262
|
# FIXME: do we need this test with XML _and_ JSON?
|
253
263
|
it "ignores (no-default) properties not present in the incoming document" do
|
254
264
|
{ Representable::Hash => [:from_hash, {}],
|
data/test/wrap_test.rb
CHANGED
@@ -46,3 +46,107 @@ class WrapTest < MiniTest::Spec
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
49
|
+
|
50
|
+
|
51
|
+
class HashDisableWrapTest < MiniTest::Spec
|
52
|
+
Band = Struct.new(:name, :label)
|
53
|
+
Album = Struct.new(:band)
|
54
|
+
Label = Struct.new(:name)
|
55
|
+
|
56
|
+
class BandDecorator < Representable::Decorator
|
57
|
+
include Representable::Hash
|
58
|
+
|
59
|
+
self.representation_wrap = :bands
|
60
|
+
property :name
|
61
|
+
|
62
|
+
property :label do # this should have a wrap!
|
63
|
+
self.representation_wrap = :important
|
64
|
+
property :name
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
let (:band) { BandDecorator.prepare(Band.new("Social Distortion")) }
|
69
|
+
|
70
|
+
# direct, local api.
|
71
|
+
it do
|
72
|
+
band.to_hash.must_equal({"bands" => {"name"=>"Social Distortion"}})
|
73
|
+
band.to_hash(wrap: false).must_equal({"name"=>"Social Distortion"})
|
74
|
+
band.to_hash(wrap: :band).must_equal(:band=>{"name"=>"Social Distortion"})
|
75
|
+
end
|
76
|
+
|
77
|
+
it do
|
78
|
+
band.from_hash({"bands" => {"name"=>"Social Distortion"}}).name.must_equal "Social Distortion"
|
79
|
+
band.from_hash({"name"=>"Social Distortion"}, wrap: false).name.must_equal "Social Distortion"
|
80
|
+
band.from_hash({band: {"name"=>"Social Distortion"}}, wrap: :band).name.must_equal "Social Distortion"
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
class AlbumDecorator < Representable::Decorator
|
85
|
+
include Representable::Hash
|
86
|
+
|
87
|
+
self.representation_wrap = :albums
|
88
|
+
|
89
|
+
property :band, decorator: BandDecorator, wrap: false, class: Band
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
let (:album) { AlbumDecorator.prepare(Album.new(Band.new("Social Distortion", Label.new("Epitaph")))) }
|
94
|
+
|
95
|
+
# band has wrap turned of per property definition, however, label still has wrap.
|
96
|
+
it "renders" do
|
97
|
+
album.to_hash.must_equal({"albums" => {"band" => {"name"=>"Social Distortion", "label"=>{"important"=>{"name"=>"Epitaph"}}}}})
|
98
|
+
end
|
99
|
+
|
100
|
+
it "parses" do
|
101
|
+
album.from_hash({"albums" => {"band" => {"name"=>"Rvivr"}}}).band.name.must_equal "Rvivr"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
class XMLDisableWrapTest < MiniTest::Spec
|
107
|
+
Band = Struct.new(:name, :label)
|
108
|
+
Album = Struct.new(:band)
|
109
|
+
Label = Struct.new(:name)
|
110
|
+
|
111
|
+
class BandDecorator < Representable::Decorator
|
112
|
+
include Representable::XML
|
113
|
+
|
114
|
+
self.representation_wrap = :bands # when nested, it uses this.
|
115
|
+
property :name
|
116
|
+
|
117
|
+
# property :label do # this should have a wrap!
|
118
|
+
# self.representation_wrap = :important
|
119
|
+
# property :name
|
120
|
+
# end
|
121
|
+
end
|
122
|
+
|
123
|
+
let (:band) { BandDecorator.prepare(Band.new("Social Distortion")) }
|
124
|
+
|
125
|
+
it do
|
126
|
+
band.to_xml.must_equal_xml "<bands><name>Social Distortion</name></bands>"
|
127
|
+
band.to_xml(wrap: "combo").must_equal_xml "<combo><name>Social Distortion</name></combo>"
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
class AlbumDecorator < Representable::Decorator
|
132
|
+
include Representable::XML
|
133
|
+
|
134
|
+
self.representation_wrap = :albums
|
135
|
+
|
136
|
+
property :band, decorator: BandDecorator, wrap: "po", class: Band
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
let (:album) { AlbumDecorator.prepare(Album.new(Band.new("Social Distortion", Label.new("Epitaph")))) }
|
141
|
+
|
142
|
+
# band has wrap turned of per property definition, however, label still has wrap.
|
143
|
+
it "rendersxx" do
|
144
|
+
skip
|
145
|
+
album.to_xml.must_equal({"albums" => {"band" => {"name"=>"Social Distortion", "label"=>{"important"=>{"name"=>"Epitaph"}}}}})
|
146
|
+
end
|
147
|
+
|
148
|
+
# it "parses" do
|
149
|
+
# album.from_hash({"albums" => {"band" => {"name"=>"Rvivr"}}}).band.name.must_equal "Rvivr"
|
150
|
+
# end
|
151
|
+
end
|
152
|
+
|
metadata
CHANGED
@@ -1,43 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: representable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-08-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: nokogiri
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: multi_json
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
13
|
- !ruby/object:Gem::Dependency
|
42
14
|
name: uber
|
43
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -240,6 +212,7 @@ files:
|
|
240
212
|
- test/parse_strategy_test.rb
|
241
213
|
- test/pass_options_test.rb
|
242
214
|
- test/prepare_test.rb
|
215
|
+
- test/private_options_test.rb
|
243
216
|
- test/reader_writer_test.rb
|
244
217
|
- test/realistic_benchmark.rb
|
245
218
|
- test/represent_test.rb
|
@@ -274,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
274
247
|
version: '0'
|
275
248
|
requirements: []
|
276
249
|
rubyforge_project:
|
277
|
-
rubygems_version: 2.
|
250
|
+
rubygems_version: 2.4.8
|
278
251
|
signing_key:
|
279
252
|
specification_version: 4
|
280
253
|
summary: Renders and parses JSON/XML/YAML documents from and to Ruby objects. Includes
|