representable 1.7.5 → 1.7.6
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 +2 -0
- data/CHANGES.md +5 -0
- data/Gemfile +0 -2
- data/README.md +47 -10
- data/lib/representable.rb +1 -1
- data/lib/representable/decorator.rb +24 -5
- data/lib/representable/version.rb +1 -1
- data/representable.gemspec +2 -2
- data/test/generic_test.rb +48 -29
- data/test/inline_test.rb +144 -0
- data/test/json_test.rb +2 -2
- data/test/nested_test.rb +97 -0
- data/test/representable_test.rb +0 -80
- data/test/test_helper.rb +33 -9
- data/test/xml_test.rb +2 -2
- metadata +53 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26bff301d2f96c009077a3675aabf598c4754a49
|
4
|
+
data.tar.gz: 6b21930dea9bf7bd327e536d78d2d4b71cb46110
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f28a2101a9eeac36b6c93edc4ceae8716b2f95918ed78ee5a9179727883ff42208b0041110f1bafbc3ba5b099e3ff46f547538a0f93e07156283254f7838147a
|
7
|
+
data.tar.gz: d2e5365454d29a47ee40829fbd3fe47bdbe0cccd4067493c26a4b700c50466ddb3ddece6f5e23fbb40d2d7437f928a3c051aab4256a666b1799e42ac2a2f15a8
|
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
@@ -7,6 +7,11 @@ h2. 1.8.0
|
|
7
7
|
-> make major steps lambda-able
|
8
8
|
-> strategies for deserialization (lambda-able!)
|
9
9
|
|
10
|
+
h2. 1.7.6
|
11
|
+
|
12
|
+
* Add `::nested` to nest blocks in the document whilst still using the same represented object. Use with `Decorator` only.
|
13
|
+
* Fixing a bug (thanks @rsutphin) where inline decorators would inherit the properties from the outer decorator.
|
14
|
+
|
10
15
|
h2. 1.7.5
|
11
16
|
|
12
17
|
* propagate all options for ::property to ::inline_representer.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@ Representable is helpful for all kind of rendering and parsing workflows. Howeve
|
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
12
|
-
The representable gem
|
12
|
+
The representable gem runs with all Ruby versions >= 1.8.7.
|
13
13
|
|
14
14
|
```ruby
|
15
15
|
gem 'representable'
|
@@ -233,6 +233,40 @@ module AlbumRepresenter
|
|
233
233
|
end
|
234
234
|
```
|
235
235
|
|
236
|
+
## Document Nesting
|
237
|
+
|
238
|
+
Not always does the structure of the desired document map to your objects. The `::nested` method allows you to structure properties in a separate section while still mapping the properties to the outer object.
|
239
|
+
|
240
|
+
Imagine the following document.
|
241
|
+
|
242
|
+
```json
|
243
|
+
{"title": "Roxanne",
|
244
|
+
"details":
|
245
|
+
{"track": 3,
|
246
|
+
"length": "4:10"}
|
247
|
+
}
|
248
|
+
```
|
249
|
+
|
250
|
+
However, both `track` and `length` are properties of the song object `<Song#0x999 title: "Roxanne", track: 3 ...>`, there is no such concept as `details` in the `Song` class. Representable gives you `::nested` to achieve this.
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
class SongRepresenter < Representable::Decorator
|
254
|
+
include Representable::JSON
|
255
|
+
|
256
|
+
property :title
|
257
|
+
|
258
|
+
nested :details do
|
259
|
+
property :track
|
260
|
+
property :length
|
261
|
+
end
|
262
|
+
end
|
263
|
+
```
|
264
|
+
|
265
|
+
Just use an inline representer or the `extend:` option to define nested properties. Accessors for nested properties will still be called on the outer object (here, `song`). And as always, this works both ways for rendering and parsing.
|
266
|
+
|
267
|
+
Note that `nested` works with decorators, only. We might add it for modules soon.
|
268
|
+
|
269
|
+
|
236
270
|
## Decorator vs. Extend
|
237
271
|
|
238
272
|
People who dislike `:extend` go use the `Decorator` strategy!
|
@@ -796,41 +830,44 @@ Coercing values only happens when rendering or parsing a document. Representable
|
|
796
830
|
|
797
831
|
## Undocumented Features
|
798
832
|
|
799
|
-
(Please don't read this section!)
|
833
|
+
*(Please don't read this section!)*
|
834
|
+
|
835
|
+
### Custom Binding
|
800
836
|
|
801
|
-
|
837
|
+
If you need a special binding for a property you're free to create it using the `:binding` option.
|
802
838
|
|
803
|
-
<!-- here comes some code -->
|
804
839
|
```ruby
|
805
840
|
property :title, :binding => lambda { |*args| JSON::TitleBinding.new(*args) }
|
806
841
|
```
|
807
842
|
|
808
|
-
|
843
|
+
### Syncing Parsing
|
844
|
+
|
845
|
+
You can use the parsed document fragment directly as a representable instance by returning `nil` in `:class`.
|
809
846
|
|
810
|
-
<!-- here comes some code -->
|
811
847
|
```ruby
|
812
848
|
property :song, :class => lambda { |*| nil }
|
813
849
|
```
|
814
850
|
|
815
851
|
This makes sense when your parsing looks like this.
|
816
852
|
|
817
|
-
<!-- here comes some code -->
|
818
853
|
```ruby
|
819
854
|
hit.from_hash(song: <#Song ..>)
|
820
855
|
```
|
821
856
|
|
822
857
|
Representable will not attempt to create a `Song` instance for you but use the provided from the document.
|
823
858
|
|
824
|
-
|
859
|
+
Note that this is now the [official option](#syncing-objects) `:parse_strategy`.
|
860
|
+
|
861
|
+
### Rendering Without Extend
|
862
|
+
|
863
|
+
The same goes the other way when rendering. Just provide an empty `:instance` block.
|
825
864
|
|
826
|
-
<!-- here comes some code -->
|
827
865
|
```ruby
|
828
866
|
property :song, :instance => lambda { |*| nil }
|
829
867
|
```
|
830
868
|
|
831
869
|
This will treat the `song` property instance as a representable object.
|
832
870
|
|
833
|
-
<!-- here comes some code -->
|
834
871
|
```ruby
|
835
872
|
hit.to_json # this will call hit.song.to_json
|
836
873
|
```
|
data/lib/representable.rb
CHANGED
@@ -128,7 +128,7 @@ private
|
|
128
128
|
# collection :products, :from => :item
|
129
129
|
# collection :products, :class => Product
|
130
130
|
def collection(name, options={}, &block)
|
131
|
-
options[:collection] = true
|
131
|
+
options[:collection] = true # FIXME: don't override original.
|
132
132
|
property(name, options, &block)
|
133
133
|
end
|
134
134
|
|
@@ -7,17 +7,36 @@ module Representable
|
|
7
7
|
new(represented)
|
8
8
|
end
|
9
9
|
|
10
|
-
def self.inline_representer(base_module, name, options, &block)
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
def self.inline_representer(base_module, name, options, &block)
|
11
|
+
# FIXME: it is wrong to inherit from self here as we just want to "inherit" the included modules but nothing else.
|
12
|
+
Class.new(self).tap do |decorator|
|
13
|
+
decorator.class_eval do # Ruby 1.8.7 wouldn't properly execute the block passed to Class.new!
|
14
|
+
# Remove parent's property definitions before defining the inline ones. #FIXME: don't inherit from self, remove those 2 lines.
|
15
|
+
representable_attrs.clear
|
16
|
+
representable_attrs.inheritable_arrays.clear
|
17
|
+
|
18
|
+
include base_module
|
19
|
+
instance_exec &block
|
20
|
+
end
|
14
21
|
end
|
15
22
|
end
|
16
23
|
|
24
|
+
# Allows you to nest a block of properties in a separate section while still mapping them to the outer object.
|
25
|
+
def self.nested(name, options={}, &block)
|
26
|
+
options = options.merge(
|
27
|
+
:nested => true,
|
28
|
+
:getter => lambda { |*| self },
|
29
|
+
:setter => lambda { |*| },
|
30
|
+
:instance => lambda { |*| self }
|
31
|
+
)
|
32
|
+
|
33
|
+
property(name, options, &block)
|
34
|
+
end
|
35
|
+
|
17
36
|
include Representable # include after class methods so Decorator::prepare can't be overwritten by Representable::prepare.
|
18
37
|
|
19
38
|
def initialize(represented)
|
20
39
|
@represented = represented
|
21
40
|
end
|
22
41
|
end
|
23
|
-
end
|
42
|
+
end
|
data/representable.gemspec
CHANGED
@@ -11,8 +11,8 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.authors = ["Nick Sutterer"]
|
12
12
|
s.email = ["apotonick@gmail.com"]
|
13
13
|
s.homepage = "https://github.com/apotonick/representable/"
|
14
|
-
s.summary = %q{
|
15
|
-
s.description =
|
14
|
+
s.summary = %q{Renders and parses JSON/XML/YAML documents from and to Ruby objects. Includes plain properties, collections, nesting, coercion and more.}
|
15
|
+
s.description = s.summary
|
16
16
|
|
17
17
|
s.files = `git ls-files`.split("\n")
|
18
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/test/generic_test.rb
CHANGED
@@ -21,7 +21,54 @@ class GenericTest < MiniTest::Spec
|
|
21
21
|
it "leaves properties untouched" do
|
22
22
|
album.from_hash({})
|
23
23
|
# TODO: test property.
|
24
|
-
album.songs.must_equal ["Fuck Armageddon"] #
|
24
|
+
album.songs.must_equal ["Fuck Armageddon"] # when the collection is not present in the incoming hash, this propery stays untouched.
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# when collection is nil, it doesn't get rendered:
|
29
|
+
for_formats(
|
30
|
+
:hash => [Representable::Hash, {}],
|
31
|
+
:xml => [Representable::XML, "<open_struct></open_struct>"],
|
32
|
+
:yaml => [Representable::YAML, "--- {}\n"], # FIXME: this doesn't look right.
|
33
|
+
) do |format, mod, output, input|
|
34
|
+
|
35
|
+
describe "nil collections" do
|
36
|
+
let (:format) { format }
|
37
|
+
|
38
|
+
representer!(:module => mod) do
|
39
|
+
collection :songs
|
40
|
+
self.representation_wrap = :album if format == :xml
|
41
|
+
end
|
42
|
+
|
43
|
+
let (:album) { Album.new.extend(representer) }
|
44
|
+
|
45
|
+
it "doesn't render collection in #{format}" do
|
46
|
+
render(album).must_equal_document output
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# when collection is set but empty, render the empty collection.
|
52
|
+
for_formats(
|
53
|
+
:hash => [Representable::Hash, {"songs" => []}],
|
54
|
+
#:xml => [Representable::XML, "<open_struct><songs/></open_struct>"],
|
55
|
+
:yaml => [Representable::YAML, "---\nsongs: []\n"],
|
56
|
+
) do |format, mod, output, input|
|
57
|
+
|
58
|
+
describe "empty collections" do
|
59
|
+
let (:format) { format }
|
60
|
+
|
61
|
+
representer!(:module => mod) do
|
62
|
+
collection :songs
|
63
|
+
self.representation_wrap = :album if format == :xml
|
64
|
+
end
|
65
|
+
|
66
|
+
let (:album) { OpenStruct.new(:songs => []).extend(representer) }
|
67
|
+
|
68
|
+
it "renders empty collection in #{format}" do
|
69
|
+
render(album).must_equal_document output
|
70
|
+
end
|
71
|
+
end
|
25
72
|
end
|
26
73
|
end
|
27
74
|
|
@@ -45,13 +92,6 @@ class GenericTest < MiniTest::Spec
|
|
45
92
|
end
|
46
93
|
end
|
47
94
|
|
48
|
-
def self.for_formats(formats)
|
49
|
-
formats.each do |format, cfg|
|
50
|
-
mod, output, input = cfg
|
51
|
-
yield format, mod, output, input
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
95
|
|
56
96
|
for_formats(
|
57
97
|
:hash => [Representable::Hash, {"song"=>{"title"=>"Resist Stance"}}, {"song"=>{"title"=>"Suffer"}}],
|
@@ -129,27 +169,6 @@ class GenericTest < MiniTest::Spec
|
|
129
169
|
end
|
130
170
|
end
|
131
171
|
|
132
|
-
def render(object)
|
133
|
-
AssertableDocument.new(object.send("to_#{format}"), format)
|
134
|
-
end
|
135
|
-
|
136
|
-
def parse(object, input)
|
137
|
-
object.send("from_#{format}", input)
|
138
|
-
end
|
139
|
-
|
140
|
-
class AssertableDocument
|
141
|
-
attr_reader :document
|
142
|
-
|
143
|
-
def initialize(document, format)
|
144
|
-
@document, @format = document, format
|
145
|
-
end
|
146
|
-
|
147
|
-
def must_equal_document(*args)
|
148
|
-
return document.must_equal_xml(*args) if @format == :xml
|
149
|
-
document.must_equal(*args)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
172
|
|
154
173
|
# Lonely Collection
|
155
174
|
require "representable/hash/collection"
|
data/test/inline_test.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class InlineTest < MiniTest::Spec
|
4
|
+
let (:song) { Song.new("Alive") }
|
5
|
+
let (:request) { representer.prepare(OpenStruct.new(:song => song)) }
|
6
|
+
|
7
|
+
{
|
8
|
+
:hash => [Representable::Hash, {"song"=>{"name"=>"Alive"}}, {"song"=>{"name"=>"You've Taken Everything"}}],
|
9
|
+
:json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"],
|
10
|
+
: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>"],
|
11
|
+
:yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"],
|
12
|
+
}.each do |format, cfg|
|
13
|
+
mod, output, input = cfg
|
14
|
+
|
15
|
+
describe "[#{format}] with :class" do
|
16
|
+
representer!(:module => mod) do
|
17
|
+
property :song, :class => Song do
|
18
|
+
property :name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it { request.send("to_#{format}").must_equal output }
|
23
|
+
it { request.send("from_#{format}", input).song.name.must_equal "You've Taken Everything"}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
{
|
28
|
+
:hash => [Representable::Hash, {"songs"=>[{"name"=>"Alive"}]}, {"songs"=>[{"name"=>"You've Taken Everything"}]}],
|
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 }],
|
31
|
+
:yaml => [Representable::YAML, "---\nsongs:\n- name: Alive\n", "---\nsongs:\n- name: You've Taken Everything\n"],
|
32
|
+
}.each do |format, cfg|
|
33
|
+
mod, output, input, collection_options = cfg
|
34
|
+
collection_options ||= {}
|
35
|
+
|
36
|
+
describe "[#{format}] collection with :class" do
|
37
|
+
let (:request) { representer.prepare(OpenStruct.new(:songs => [song])) }
|
38
|
+
|
39
|
+
representer!(:module => mod) do
|
40
|
+
collection :songs, collection_options.merge(:class => Song) do
|
41
|
+
property :name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it { request.send("to_#{format}").must_equal output }
|
46
|
+
it { request.send("from_#{format}", input).songs.first.name.must_equal "You've Taken Everything"}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "without :class" do
|
51
|
+
representer! do
|
52
|
+
property :song do
|
53
|
+
property :name
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it { request.to_hash.must_equal({"song"=>{"name"=>"Alive"}}) }
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "decorator" do
|
61
|
+
let (:request) { representer.prepare(OpenStruct.new(:song => song, :who => "Josephine")) }
|
62
|
+
|
63
|
+
let (:representer) do
|
64
|
+
Class.new(Representable::Decorator) do
|
65
|
+
include Representable::Hash
|
66
|
+
|
67
|
+
property :who
|
68
|
+
|
69
|
+
property :song, :class => Song do
|
70
|
+
property :name
|
71
|
+
end
|
72
|
+
|
73
|
+
self
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it { request.to_hash.must_equal({"who"=>"Josephine", "song"=>{"name"=>"Alive"}}) }
|
78
|
+
it { request.from_hash({"song"=>{"name"=>"You've Taken Everything"}}).song.name.must_equal "You've Taken Everything"}
|
79
|
+
|
80
|
+
it "uses an inline decorator" do
|
81
|
+
request.to_hash
|
82
|
+
song.wont_be_kind_of Representable
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# TODO: should be in extend:/decorator: test.
|
87
|
+
# FIXME: this tests :getter{represented}+:extend - represented gets extended twice and the inline decorator overrides original config.
|
88
|
+
# for_formats(
|
89
|
+
# :hash => [Representable::Hash, {"album" => {"artist" => {"label"=>"Epitaph"}}}],
|
90
|
+
# # :xml => [Representable::XML, "<open_struct></open_struct>"],
|
91
|
+
# #:yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n"]
|
92
|
+
# ) do |format, mod, output, input|
|
93
|
+
|
94
|
+
# module ArtistRepresenter
|
95
|
+
# include Representable::JSON
|
96
|
+
# property :label
|
97
|
+
# end
|
98
|
+
|
99
|
+
# describe ":getter with inline representer" do
|
100
|
+
# let (:format) { format }
|
101
|
+
|
102
|
+
# representer!(:module => mod) do
|
103
|
+
# self.representation_wrap = :album
|
104
|
+
|
105
|
+
# property :artist, :getter => lambda { |args| represented }, :extend => ArtistRepresenter
|
106
|
+
# end
|
107
|
+
|
108
|
+
# let (:album) { OpenStruct.new(:label => "Epitaph").extend(representer) }
|
109
|
+
|
110
|
+
# it "renders nested Album-properties in separate section" do
|
111
|
+
# render(album).must_equal_document output
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
|
116
|
+
|
117
|
+
for_formats({
|
118
|
+
:hash => [Representable::Hash, {"album" => {"artist" => {"label"=>"Epitaph"}}}],
|
119
|
+
# :xml => [Representable::XML, "<open_struct></open_struct>"],
|
120
|
+
#:yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n"]
|
121
|
+
}) do |format, mod, output, input|
|
122
|
+
|
123
|
+
class ArtistDecorator < Representable::Decorator
|
124
|
+
include Representable::JSON
|
125
|
+
property :label
|
126
|
+
end
|
127
|
+
|
128
|
+
describe ":getter with :decorator" do
|
129
|
+
let (:format) { format }
|
130
|
+
|
131
|
+
representer!(:module => mod) do
|
132
|
+
self.representation_wrap = "album"
|
133
|
+
|
134
|
+
property :artist, :getter => lambda { |args| represented }, :decorator => ArtistDecorator
|
135
|
+
end
|
136
|
+
|
137
|
+
let (:album) { OpenStruct.new(:label => "Epitaph").extend(representer) }
|
138
|
+
|
139
|
+
it "renders nested Album-properties in separate section" do
|
140
|
+
render(album).must_equal_document output
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/test/json_test.rb
CHANGED
@@ -426,7 +426,7 @@ end
|
|
426
426
|
|
427
427
|
class HashTest < MiniTest::Spec
|
428
428
|
describe "hash :songs" do
|
429
|
-
representer!(Representable::JSON) do
|
429
|
+
representer!(:module => Representable::JSON) do
|
430
430
|
hash :songs
|
431
431
|
end
|
432
432
|
|
@@ -443,7 +443,7 @@ end
|
|
443
443
|
end
|
444
444
|
|
445
445
|
describe "hash :songs, :class => Song" do
|
446
|
-
representer!(Representable::JSON) do
|
446
|
+
representer!(:module => Representable::JSON) do
|
447
447
|
hash :songs, :extend => Module.new { include Representable::JSON; property :name }, :class => Song
|
448
448
|
end
|
449
449
|
|
data/test/nested_test.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class NestedTest < MiniTest::Spec
|
4
|
+
Album = Struct.new(:label, :owner)
|
5
|
+
|
6
|
+
for_formats(
|
7
|
+
:hash => [Representable::Hash, {"label" => {"label"=>"Epitaph", "owner"=>"Brett Gurewitz"}}],
|
8
|
+
# :xml => [Representable::XML, "<open_struct></open_struct>"],
|
9
|
+
:yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n"]
|
10
|
+
) do |format, mod, output, input|
|
11
|
+
|
12
|
+
# describe "::nested with inline representer" do
|
13
|
+
# let (:format) { format }
|
14
|
+
|
15
|
+
# representer!(:module => mod) do
|
16
|
+
# nested :label do
|
17
|
+
# property :label
|
18
|
+
# property :owner
|
19
|
+
|
20
|
+
# # self.representation_wrap = nil if format == :xml
|
21
|
+
# end
|
22
|
+
|
23
|
+
|
24
|
+
# self.representation_wrap = :album if format == :xml
|
25
|
+
# end
|
26
|
+
|
27
|
+
# let (:album) { Album.new("Epitaph", "Brett Gurewitz").extend(representer) }
|
28
|
+
|
29
|
+
# it "renders nested Album-properties in separate section" do
|
30
|
+
# render(album).must_equal_document output
|
31
|
+
# end
|
32
|
+
|
33
|
+
# it "parses nested properties to Album instance" do
|
34
|
+
# album = parse(Album.new.extend(representer), output)
|
35
|
+
# album.label.must_equal "Epitaph"
|
36
|
+
# album.owner.must_equal "Brett Gurewitz"
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
|
40
|
+
describe "Decorator ::nested with extend:" do
|
41
|
+
let (:format) { format }
|
42
|
+
|
43
|
+
representer!(:name => :label_rpr) do
|
44
|
+
include mod
|
45
|
+
property :label
|
46
|
+
property :owner
|
47
|
+
end
|
48
|
+
|
49
|
+
representer!(:module => mod, :decorator => true, :inject => :label_rpr) do
|
50
|
+
nested :label, :extend => label_rpr
|
51
|
+
|
52
|
+
self.representation_wrap = :album if format == :xml
|
53
|
+
end
|
54
|
+
|
55
|
+
let (:album) { representer.prepare(Album.new("Epitaph", "Brett Gurewitz")) }
|
56
|
+
|
57
|
+
# TODO: shared example with above.
|
58
|
+
it "renders nested Album-properties in separate section" do
|
59
|
+
render(album).must_equal_document output
|
60
|
+
end
|
61
|
+
|
62
|
+
it "parses nested properties to Album instance" do
|
63
|
+
album = parse(representer.prepare(Album.new), output)
|
64
|
+
album.label.must_equal "Epitaph"
|
65
|
+
album.owner.must_equal "Brett Gurewitz"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "Decorator ::nested" do
|
70
|
+
let (:format) { format }
|
71
|
+
|
72
|
+
representer!(:module => mod, :decorator => true) do
|
73
|
+
nested :label do
|
74
|
+
property :label
|
75
|
+
property :owner
|
76
|
+
|
77
|
+
# self.representation_wrap = nil if format == :xml
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
self.representation_wrap = :album if format == :xml
|
82
|
+
end
|
83
|
+
|
84
|
+
let (:album) { representer.prepare(Album.new("Epitaph", "Brett Gurewitz")) }
|
85
|
+
|
86
|
+
it "renders nested Album-properties in separate section" do
|
87
|
+
render(album).must_equal_document output
|
88
|
+
end
|
89
|
+
|
90
|
+
it "parses nested properties to Album instance" do
|
91
|
+
album = parse(representer.prepare(Album.new), output)
|
92
|
+
album.label.must_equal "Epitaph"
|
93
|
+
album.owner.must_equal "Brett Gurewitz"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/test/representable_test.rb
CHANGED
@@ -457,86 +457,6 @@ class RepresentableTest < MiniTest::Spec
|
|
457
457
|
end
|
458
458
|
end
|
459
459
|
|
460
|
-
describe "inline representers" do
|
461
|
-
let (:song) { Song.new("Alive") }
|
462
|
-
let (:request) { representer.prepare(OpenStruct.new(:song => song)) }
|
463
|
-
|
464
|
-
{
|
465
|
-
:hash => [Representable::Hash, {"song"=>{"name"=>"Alive"}}, {"song"=>{"name"=>"You've Taken Everything"}}],
|
466
|
-
:json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"],
|
467
|
-
: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>"],
|
468
|
-
:yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"],
|
469
|
-
}.each do |format, cfg|
|
470
|
-
mod, output, input = cfg
|
471
|
-
|
472
|
-
describe "[#{format}] with :class" do
|
473
|
-
representer!(mod) do
|
474
|
-
property :song, :class => Song do
|
475
|
-
property :name
|
476
|
-
end
|
477
|
-
end
|
478
|
-
|
479
|
-
it { request.send("to_#{format}").must_equal output }
|
480
|
-
it { request.send("from_#{format}", input).song.name.must_equal "You've Taken Everything"}
|
481
|
-
end
|
482
|
-
end
|
483
|
-
|
484
|
-
{
|
485
|
-
:hash => [Representable::Hash, {"songs"=>[{"name"=>"Alive"}]}, {"songs"=>[{"name"=>"You've Taken Everything"}]}],
|
486
|
-
:json => [Representable::JSON, "{\"songs\":[{\"name\":\"Alive\"}]}", "{\"songs\":[{\"name\":\"You've Taken Everything\"}]}"],
|
487
|
-
: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 }],
|
488
|
-
:yaml => [Representable::YAML, "---\nsongs:\n- name: Alive\n", "---\nsongs:\n- name: You've Taken Everything\n"],
|
489
|
-
}.each do |format, cfg|
|
490
|
-
mod, output, input, collection_options = cfg
|
491
|
-
collection_options ||= {}
|
492
|
-
|
493
|
-
describe "[#{format}] collection with :class" do
|
494
|
-
let (:request) { representer.prepare(OpenStruct.new(:songs => [song])) }
|
495
|
-
|
496
|
-
representer!(mod) do
|
497
|
-
collection :songs, collection_options.merge(:class => Song) do
|
498
|
-
property :name
|
499
|
-
end
|
500
|
-
end
|
501
|
-
|
502
|
-
it { request.send("to_#{format}").must_equal output }
|
503
|
-
it { request.send("from_#{format}", input).songs.first.name.must_equal "You've Taken Everything"}
|
504
|
-
end
|
505
|
-
end
|
506
|
-
|
507
|
-
describe "without :class" do
|
508
|
-
representer! do
|
509
|
-
property :song do
|
510
|
-
property :name
|
511
|
-
end
|
512
|
-
end
|
513
|
-
|
514
|
-
it { request.to_hash.must_equal({"song"=>{"name"=>"Alive"}}) }
|
515
|
-
end
|
516
|
-
|
517
|
-
describe "decorator" do
|
518
|
-
let (:representer) do
|
519
|
-
Class.new(Representable::Decorator) do
|
520
|
-
include Representable::Hash
|
521
|
-
|
522
|
-
property :song, :class => Song do
|
523
|
-
property :name
|
524
|
-
end
|
525
|
-
|
526
|
-
self
|
527
|
-
end
|
528
|
-
end
|
529
|
-
|
530
|
-
it { request.to_hash.must_equal({"song"=>{"name"=>"Alive"}}) }
|
531
|
-
it { request.from_hash({"song"=>{"name"=>"You've Taken Everything"}}).song.name.must_equal "You've Taken Everything"}
|
532
|
-
|
533
|
-
it "uses an inline decorator" do
|
534
|
-
request.to_hash
|
535
|
-
song.wont_be_kind_of Representable
|
536
|
-
end
|
537
|
-
end
|
538
|
-
end
|
539
|
-
|
540
460
|
describe ":if" do
|
541
461
|
before do
|
542
462
|
@pop = Class.new(PopBand) { attr_accessor :fame }
|
data/test/test_helper.rb
CHANGED
@@ -49,20 +49,44 @@ MiniTest::Spec.class_eval do
|
|
49
49
|
include AssertJson::Assertions
|
50
50
|
include XmlHelper
|
51
51
|
|
52
|
-
def self.
|
53
|
-
|
52
|
+
def self.for_formats(formats)
|
53
|
+
formats.each do |format, cfg|
|
54
|
+
mod, output, input = cfg
|
55
|
+
yield format, mod, output, input
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def render(object)
|
60
|
+
AssertableDocument.new(object.send("to_#{format}"), format)
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse(object, input)
|
64
|
+
object.send("from_#{format}", input)
|
65
|
+
end
|
66
|
+
|
67
|
+
class AssertableDocument
|
68
|
+
attr_reader :document
|
54
69
|
|
55
|
-
|
56
|
-
|
57
|
-
format = fmt[:module] || Representable::Hash
|
70
|
+
def initialize(document, format)
|
71
|
+
@document, @format = document, format
|
58
72
|
end
|
59
73
|
|
74
|
+
def must_equal_document(*args)
|
75
|
+
return document.must_equal_xml(*args) if @format == :xml
|
76
|
+
document.must_equal(*args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.representer!(options={}, &block)
|
81
|
+
fmt = options # we need that so the 2nd call to ::let (within a ::describe) remembers the right format.
|
82
|
+
|
83
|
+
name = options[:name] || :representer
|
84
|
+
format = options[:module] || Representable::Hash
|
85
|
+
|
60
86
|
let(name) do
|
61
|
-
mod = Module.new
|
87
|
+
mod = options[:decorator] ? Class.new(Representable::Decorator) : Module.new
|
62
88
|
|
63
|
-
|
64
|
-
inject_representer(mod, fmt)
|
65
|
-
end
|
89
|
+
inject_representer(mod, fmt)
|
66
90
|
|
67
91
|
mod.module_eval do
|
68
92
|
include format
|
data/test/xml_test.rb
CHANGED
@@ -396,7 +396,7 @@ class CollectionTest < MiniTest::Spec
|
|
396
396
|
|
397
397
|
describe "XML::Collection" do
|
398
398
|
describe "with contained objects" do
|
399
|
-
representer!(Representable::XML::Collection) do
|
399
|
+
representer!(:module => Representable::XML::Collection) do
|
400
400
|
items :class => Song, :extend => SongRepresenter
|
401
401
|
self.representation_wrap= :songs
|
402
402
|
end
|
@@ -423,7 +423,7 @@ class CollectionTest < MiniTest::Spec
|
|
423
423
|
end
|
424
424
|
|
425
425
|
describe "XML::AttributeHash" do # TODO: move to HashTest.
|
426
|
-
representer!(Representable::XML::AttributeHash) do
|
426
|
+
representer!(:module => Representable::XML::AttributeHash) do
|
427
427
|
self.representation_wrap= :songs
|
428
428
|
end
|
429
429
|
|
metadata
CHANGED
@@ -1,165 +1,165 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: representable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.7.
|
4
|
+
version: 1.7.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-02-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: multi_json
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: test_xml
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: 0.1.6
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 0.1.6
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: minitest
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - ~>
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: 5.0.0
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - ~>
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 5.0.0
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: mocha
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: 0.13.0
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: 0.13.0
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: mongoid
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: virtus
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- - ~>
|
115
|
+
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: 0.5.0
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- - ~>
|
122
|
+
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: 0.5.0
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: yajl-ruby
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- -
|
129
|
+
- - ">="
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: '0'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- -
|
136
|
+
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: json
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
|
-
- - ~>
|
143
|
+
- - "~>"
|
144
144
|
- !ruby/object:Gem::Version
|
145
145
|
version: 1.7.7
|
146
146
|
type: :development
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- - ~>
|
150
|
+
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: 1.7.7
|
153
|
-
description:
|
154
|
-
|
153
|
+
description: Renders and parses JSON/XML/YAML documents from and to Ruby objects.
|
154
|
+
Includes plain properties, collections, nesting, coercion and more.
|
155
155
|
email:
|
156
156
|
- apotonick@gmail.com
|
157
157
|
executables: []
|
158
158
|
extensions: []
|
159
159
|
extra_rdoc_files: []
|
160
160
|
files:
|
161
|
-
- .gitignore
|
162
|
-
- .travis.yml
|
161
|
+
- ".gitignore"
|
162
|
+
- ".travis.yml"
|
163
163
|
- CHANGES.md
|
164
164
|
- Gemfile
|
165
165
|
- LICENSE
|
@@ -203,8 +203,10 @@ files:
|
|
203
203
|
- test/hash_bindings_test.rb
|
204
204
|
- test/hash_test.rb
|
205
205
|
- test/inheritance_test.rb
|
206
|
+
- test/inline_test.rb
|
206
207
|
- test/json_test.rb
|
207
208
|
- test/mongoid_test.rb
|
209
|
+
- test/nested_test.rb
|
208
210
|
- test/representable_test.rb
|
209
211
|
- test/test_helper.rb
|
210
212
|
- test/test_helper_test.rb
|
@@ -220,19 +222,38 @@ require_paths:
|
|
220
222
|
- lib
|
221
223
|
required_ruby_version: !ruby/object:Gem::Requirement
|
222
224
|
requirements:
|
223
|
-
- -
|
225
|
+
- - ">="
|
224
226
|
- !ruby/object:Gem::Version
|
225
227
|
version: '0'
|
226
228
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
227
229
|
requirements:
|
228
|
-
- -
|
230
|
+
- - ">="
|
229
231
|
- !ruby/object:Gem::Version
|
230
232
|
version: '0'
|
231
233
|
requirements: []
|
232
234
|
rubyforge_project:
|
233
|
-
rubygems_version: 2.
|
235
|
+
rubygems_version: 2.2.1
|
234
236
|
signing_key:
|
235
237
|
specification_version: 4
|
236
|
-
summary:
|
237
|
-
|
238
|
-
test_files:
|
238
|
+
summary: Renders and parses JSON/XML/YAML documents from and to Ruby objects. Includes
|
239
|
+
plain properties, collections, nesting, coercion and more.
|
240
|
+
test_files:
|
241
|
+
- test/coercion_test.rb
|
242
|
+
- test/config_test.rb
|
243
|
+
- test/decorator_test.rb
|
244
|
+
- test/definition_test.rb
|
245
|
+
- test/example.rb
|
246
|
+
- test/generic_test.rb
|
247
|
+
- test/hash_bindings_test.rb
|
248
|
+
- test/hash_test.rb
|
249
|
+
- test/inheritance_test.rb
|
250
|
+
- test/inline_test.rb
|
251
|
+
- test/json_test.rb
|
252
|
+
- test/mongoid_test.rb
|
253
|
+
- test/nested_test.rb
|
254
|
+
- test/representable_test.rb
|
255
|
+
- test/test_helper.rb
|
256
|
+
- test/test_helper_test.rb
|
257
|
+
- test/xml_bindings_test.rb
|
258
|
+
- test/xml_test.rb
|
259
|
+
- test/yaml_test.rb
|