representable 1.7.5 → 1.7.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|