representable 1.6.0 → 1.6.1
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/CHANGES.md +6 -0
- data/README.md +43 -0
- data/lib/representable.rb +24 -13
- data/lib/representable/binding.rb +1 -1
- data/lib/representable/bindings/hash_bindings.rb +16 -16
- data/lib/representable/version.rb +1 -1
- data/test/generic_test.rb +91 -0
- data/test/hash_test.rb +36 -3
- data/test/json_test.rb +1 -1
- data/test/representable_test.rb +23 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b78042035b57cdc5c26ba4f5e5e9ad7ecc218256
|
4
|
+
data.tar.gz: 602635412709132eb114614ee69e53b9b73e31b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75a8db26513def9bf34f95e43ddc9edda28a8d8eda41d271b4e8b55bbad6b9dc4244d7d8d7a390996cf12907f68e71721cf940e9cbf6f4d6b6a53fd9095a4b56
|
7
|
+
data.tar.gz: 33a3d4c76452a0400897c3dc26c2ea66a9f5f15757ce9c87d35ddca65b4baa1ccdc740244dc4a70f39384b426075baa47d70af08fb9d835f4da94cb7ca51d68f
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
h2. 1.6.1
|
2
|
+
|
3
|
+
* Using `instance: lambda { nil }` will now treat the property as a representable object without trying to extend it. It simply calls `to_*/from_*` on the property.
|
4
|
+
* You can use an inline representer and still have a `:extend` which will be automatically included in the inline representer. This is handy if you want to "extend" a base representer with an inline block. Thanks to @pixelvitamina for requesting that.
|
5
|
+
* Allow inline representers with `collection`. Thanks to @rsutphin!
|
6
|
+
|
1
7
|
h2. 1.6.0
|
2
8
|
|
3
9
|
* You can define inline representers now if you don't wanna use multiple modules and files.
|
data/README.md
CHANGED
@@ -193,6 +193,17 @@ module AlbumRepresenter
|
|
193
193
|
|
194
194
|
This works both for representer modules and decorators.
|
195
195
|
|
196
|
+
You can use an inline representer along with `:extend`. The latter will automatically be included in the inline representer. This is handy if you want to inline-extend a base decorator.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
module AlbumRepresenter
|
200
|
+
include Representable::JSON
|
201
|
+
|
202
|
+
property :hit, extend: SongRepresenter do
|
203
|
+
property :numbers_sold
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
196
207
|
## Decorator vs. Extend
|
197
208
|
|
198
209
|
People who dislike `:extend` go use the `Decorator` strategy!
|
@@ -722,6 +733,38 @@ Coercing values only happens when rendering or parsing a document. Representable
|
|
722
733
|
property :title, :binding => lambda { |*args| JSON::TitleBinding.new(*args) }
|
723
734
|
```
|
724
735
|
|
736
|
+
* You can use the parsed document fragment directly as a representable instance by returning `nil` in `:class`.
|
737
|
+
|
738
|
+
<!-- here comes some code -->
|
739
|
+
```ruby
|
740
|
+
property :song, :class => lambda { |*| nil }
|
741
|
+
```
|
742
|
+
|
743
|
+
This makes sense when your parsing looks like this.
|
744
|
+
|
745
|
+
<!-- here comes some code -->
|
746
|
+
```ruby
|
747
|
+
hit.from_hash(song: <#Song ..>)
|
748
|
+
```
|
749
|
+
|
750
|
+
Representable will not attempt to create a `Song` instance for you but use the provided from the document.
|
751
|
+
|
752
|
+
* The same goes the other way when rendering. Just provide an empty `:instance` block.
|
753
|
+
|
754
|
+
<!-- here comes some code -->
|
755
|
+
```ruby
|
756
|
+
property :song, :instance => lambda { |*| nil }
|
757
|
+
```
|
758
|
+
|
759
|
+
This will treat the `song` property instance as a representable object.
|
760
|
+
|
761
|
+
<!-- here comes some code -->
|
762
|
+
```ruby
|
763
|
+
hit.to_json # this will call hit.song.to_json
|
764
|
+
```
|
765
|
+
|
766
|
+
Rendering `collection`s works the same. Parsing doesn't work out-of-the-box, currently, as we're still unsure how to map items to fragments.
|
767
|
+
|
725
768
|
## Copyright
|
726
769
|
|
727
770
|
Representable started as a heavily simplified fork of the ROXML gem. Big thanks to Ben Woosley for his inspiring work.
|
data/lib/representable.rb
CHANGED
@@ -11,6 +11,7 @@ module Representable
|
|
11
11
|
extend ClassInclusions, ModuleExtensions
|
12
12
|
extend ClassMethods
|
13
13
|
extend ClassMethods::Declarations
|
14
|
+
extend DSLAdditions
|
14
15
|
|
15
16
|
include Deprecations
|
16
17
|
end
|
@@ -77,7 +78,7 @@ private
|
|
77
78
|
# Copies the representable_attrs to the extended object.
|
78
79
|
def extended(object)
|
79
80
|
super
|
80
|
-
object.representable_attrs=(representable_attrs)
|
81
|
+
object.representable_attrs=(representable_attrs) # yes, we want a hard overwrite here and no inheritance.
|
81
82
|
end
|
82
83
|
end
|
83
84
|
|
@@ -116,10 +117,6 @@ private
|
|
116
117
|
# property :name, :readable => false
|
117
118
|
# property :name, :writeable => false
|
118
119
|
def property(name, options={}, &block)
|
119
|
-
if block_given? # DISCUSS: separate module?
|
120
|
-
options[:extend] = inline_representer(representer_engine, &block)
|
121
|
-
end
|
122
|
-
|
123
120
|
(representable_attrs << definition_class.new(name, options)).last
|
124
121
|
end
|
125
122
|
|
@@ -130,9 +127,9 @@ private
|
|
130
127
|
# collection :products
|
131
128
|
# collection :products, :from => :item
|
132
129
|
# collection :products, :class => Product
|
133
|
-
def collection(name, options={})
|
130
|
+
def collection(name, options={}, &block)
|
134
131
|
options[:collection] = true
|
135
|
-
property(name, options)
|
132
|
+
property(name, options, &block)
|
136
133
|
end
|
137
134
|
|
138
135
|
def hash(name=nil, options={})
|
@@ -150,15 +147,29 @@ private
|
|
150
147
|
def build_config
|
151
148
|
Config.new
|
152
149
|
end
|
150
|
+
end # Declarations
|
151
|
+
end
|
152
|
+
|
153
|
+
# Internal module for DSL sugar that should not go into the core library.
|
154
|
+
module DSLAdditions
|
155
|
+
def property(name, options={}, &block)
|
156
|
+
return super unless block_given?
|
157
|
+
|
158
|
+
inline = inline_representer(representer_engine, &block)
|
159
|
+
inline.module_eval { include options[:extend] } if options[:extend]
|
153
160
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
161
|
+
options[:extend] = inline
|
162
|
+
super
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
def inline_representer(base_module, &block) # DISCUSS: separate module?
|
167
|
+
Module.new do
|
168
|
+
include base_module
|
169
|
+
instance_exec &block
|
159
170
|
end
|
160
171
|
end
|
161
|
-
end
|
172
|
+
end # DSLAdditions
|
162
173
|
end
|
163
174
|
|
164
175
|
require 'representable/decorator'
|
@@ -4,17 +4,17 @@ module Representable
|
|
4
4
|
module Hash
|
5
5
|
module ObjectBinding
|
6
6
|
include Binding::Object
|
7
|
-
|
7
|
+
|
8
8
|
def serialize_method
|
9
9
|
:to_hash
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def deserialize_method
|
13
13
|
:from_hash
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
|
16
|
+
|
17
|
+
|
18
18
|
class PropertyBinding < Representable::Binding
|
19
19
|
def self.build_for(definition, *args) # TODO: remove default arg.
|
20
20
|
return CollectionBinding.new(definition, *args) if definition.array?
|
@@ -26,40 +26,40 @@ module Representable
|
|
26
26
|
super
|
27
27
|
extend ObjectBinding if typed?
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def read(hash)
|
31
31
|
return FragmentNotFound unless hash.has_key?(from) # DISCUSS: put it all in #read for performance. not really sure if i like returning that special thing.
|
32
|
-
|
32
|
+
|
33
33
|
fragment = hash[from]
|
34
34
|
deserialize_from(fragment)
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
def write(hash, value)
|
38
38
|
hash[from] = serialize_for(value)
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
def serialize_for(value)
|
42
42
|
serialize(value)
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
def deserialize_from(fragment)
|
46
46
|
deserialize(fragment)
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
50
|
-
|
49
|
+
|
50
|
+
|
51
51
|
class CollectionBinding < PropertyBinding
|
52
52
|
def serialize_for(value)
|
53
53
|
# value.enum_for(:each_with_index).collect { |obj, i| serialize(obj, i) } # DISCUSS: provide ary index/hash key for representer_module_for?
|
54
|
-
value.collect { |
|
54
|
+
value.collect { |item| serialize(item) }
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
def deserialize_from(fragment)
|
58
58
|
fragment.collect { |item_fragment| deserialize(item_fragment) }
|
59
59
|
end
|
60
60
|
end
|
61
|
-
|
62
|
-
|
61
|
+
|
62
|
+
|
63
63
|
class HashBinding < PropertyBinding
|
64
64
|
def serialize_for(value)
|
65
65
|
# requires value to respond to #each with two block parameters.
|
@@ -67,7 +67,7 @@ module Representable
|
|
67
67
|
value.each { |key, obj| hsh[key] = serialize(obj) }
|
68
68
|
end
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
def deserialize_from(fragment)
|
72
72
|
{}.tap do |hsh|
|
73
73
|
fragment.each { |key, item_fragment| hsh[key] = deserialize(item_fragment) }
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class GenericTest < MiniTest::Spec
|
4
|
+
# one day, this file will contain all engine-independent test cases. one day...
|
5
|
+
let (:new_album) { OpenStruct.new.extend(representer) }
|
6
|
+
let (:album) { OpenStruct.new(:songs => ["Fuck Armageddon"]).extend(representer) }
|
7
|
+
let (:song) { OpenStruct.new(:title => "Resist Stance").extend(song_representer) }
|
8
|
+
let (:song_representer) { Module.new do include Representable::Hash; property :title end }
|
9
|
+
|
10
|
+
|
11
|
+
describe "::collection" do
|
12
|
+
representer! do
|
13
|
+
collection :songs
|
14
|
+
end
|
15
|
+
|
16
|
+
it "initializes property with empty array" do
|
17
|
+
new_album.from_hash({})
|
18
|
+
new_album.songs.must_equal [] # DISCUSS: do we really want this?
|
19
|
+
end
|
20
|
+
|
21
|
+
it "overrides property with empty array" do
|
22
|
+
album.from_hash({})
|
23
|
+
album.songs.must_equal [] # DISCUSS: do we really want this?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
describe ":representable with property" do # TODO: introduce :representable option?
|
29
|
+
representer! do
|
30
|
+
property :song, :instance => lambda { |*| nil }
|
31
|
+
end
|
32
|
+
|
33
|
+
let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) } # note that song is already representable.
|
34
|
+
|
35
|
+
it "calls #to_hash on song instance, nothing else" do
|
36
|
+
hit.to_hash.must_equal("song"=>{"title"=>"Resist Stance"})
|
37
|
+
end
|
38
|
+
|
39
|
+
it "calls #from_hash on the existing song instance, nothing else" do
|
40
|
+
song_id = hit.song.object_id
|
41
|
+
hit.from_hash("song"=>{"title"=>"Suffer"})
|
42
|
+
hit.song.title.must_equal "Suffer"
|
43
|
+
hit.song.object_id.must_equal song_id
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
describe ":representable with collection" do # TODO: introduce :representable option?
|
49
|
+
representer! do
|
50
|
+
collection :songs, :instance => lambda { |*| nil }
|
51
|
+
end
|
52
|
+
let (:album) { OpenStruct.new(:songs => [song]).extend(representer) }
|
53
|
+
|
54
|
+
it "calls #to_hash on song instances, nothing else" do
|
55
|
+
album.to_hash.must_equal("songs"=>[{"title"=>"Resist Stance"}])
|
56
|
+
end
|
57
|
+
|
58
|
+
it "calls #from_hash on the existing song instance, nothing else" do
|
59
|
+
album.songs.instance_eval do
|
60
|
+
def from_hash(items, *args)
|
61
|
+
#puts items #=> {"title"=>"Suffer"}
|
62
|
+
first.from_hash(items) # example how you can use this.
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
song = album.songs.first
|
67
|
+
song_id = song.object_id
|
68
|
+
album.from_hash("songs"=>[{"title"=>"Suffer"}])
|
69
|
+
song.title.must_equal "Suffer"
|
70
|
+
song.object_id.must_equal song_id
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
describe "mix :extend and inline representers" do
|
76
|
+
representer! do
|
77
|
+
rpr_module = Module.new do
|
78
|
+
include Representable::Hash
|
79
|
+
property :title
|
80
|
+
end
|
81
|
+
property :song, :extend => rpr_module do
|
82
|
+
property :artist
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it do OpenStruct.new(:song => OpenStruct.new(:title => "The Fever And The Sound", :artist => "Strung Out")).extend(representer).
|
87
|
+
to_hash.
|
88
|
+
must_equal({"song"=>{"artist"=>"Strung Out", "title"=>"The Fever And The Sound"}})
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/test/hash_test.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
require 'representable/hash'
|
3
3
|
|
4
|
-
class
|
4
|
+
class HashTest < MiniTest::Spec
|
5
5
|
def self.hash_representer(&block)
|
6
6
|
Module.new do
|
7
7
|
include Representable::Hash
|
@@ -59,6 +59,39 @@ class YamlTest < MiniTest::Spec
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
end
|
62
|
+
|
63
|
+
|
64
|
+
describe "with :extend and :as" do
|
65
|
+
hash_song = hash_representer do property :name end
|
66
|
+
|
67
|
+
let (:hash_album) { Module.new do
|
68
|
+
include Representable::Hash
|
69
|
+
property :song, :extend => hash_song, :class => Song, :as => :hit
|
70
|
+
end }
|
71
|
+
|
72
|
+
let (:album) { OpenStruct.new(:song => Song.new("Liar")).extend(hash_album) }
|
73
|
+
|
74
|
+
it { album.to_hash.must_equal("hit" => {"name" => "Liar"}) }
|
75
|
+
it { album.from_hash("hit" => {"name" => "Go With Me"}).must_equal OpenStruct.new(:song => Song.new("Go With Me")) }
|
76
|
+
end
|
77
|
+
# describe "FIXME COMBINE WITH ABOVE with :extend and :as" do
|
78
|
+
# hash_song = Module.new do
|
79
|
+
# include Representable::XML
|
80
|
+
# self.representation_wrap = :song
|
81
|
+
# property :name
|
82
|
+
# end
|
83
|
+
|
84
|
+
# let (:hash_album) { Module.new do
|
85
|
+
# include Representable::XML
|
86
|
+
# self.representation_wrap = :album
|
87
|
+
# property :song, :extend => hash_song, :class => Song, :as => :hit
|
88
|
+
# end }
|
89
|
+
|
90
|
+
# let (:album) { OpenStruct.new(:song => Song.new("Liar")).extend(hash_album) }
|
91
|
+
|
92
|
+
# it { album.to_xml.must_equal_xml("<album><hit><name>Liar</name></hit></album>") }
|
93
|
+
# #it { album.from_hash("hit" => {"name" => "Go With Me"}).must_equal OpenStruct.new(:song => Song.new("Go With Me")) }
|
94
|
+
# end
|
62
95
|
end
|
63
96
|
|
64
97
|
|
@@ -86,7 +119,7 @@ class YamlTest < MiniTest::Spec
|
|
86
119
|
|
87
120
|
|
88
121
|
describe "with :class and :extend" do
|
89
|
-
hash_song = hash_representer do
|
122
|
+
hash_song = hash_representer do
|
90
123
|
property :name
|
91
124
|
property :track
|
92
125
|
end
|
@@ -119,7 +152,7 @@ class YamlTest < MiniTest::Spec
|
|
119
152
|
class DefinitionTest < MiniTest::Spec
|
120
153
|
it "what" do
|
121
154
|
class Representable::Hash::Binding < SimpleDelegator
|
122
|
-
|
155
|
+
|
123
156
|
end
|
124
157
|
|
125
158
|
definition = Representable::Definition.new(:name)
|
data/test/json_test.rb
CHANGED
data/test/representable_test.rb
CHANGED
@@ -452,6 +452,29 @@ class RepresentableTest < MiniTest::Spec
|
|
452
452
|
end
|
453
453
|
end
|
454
454
|
|
455
|
+
{
|
456
|
+
:hash => [Representable::Hash, {"songs"=>[{"name"=>"Alive"}]}, {"songs"=>[{"name"=>"You've Taken Everything"}]}],
|
457
|
+
:json => [Representable::JSON, "{\"songs\":[{\"name\":\"Alive\"}]}", "{\"songs\":[{\"name\":\"You've Taken Everything\"}]}"],
|
458
|
+
: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 }],
|
459
|
+
:yaml => [Representable::YAML, "---\nsongs:\n- name: Alive\n", "---\nsongs:\n- name: You've Taken Everything\n"],
|
460
|
+
}.each do |format, cfg|
|
461
|
+
mod, output, input, collection_options = cfg
|
462
|
+
collection_options ||= {}
|
463
|
+
|
464
|
+
describe "[#{format}] collection with :class" do
|
465
|
+
let (:request) { representer.prepare(OpenStruct.new(:songs => [song])) }
|
466
|
+
|
467
|
+
representer!(mod) do
|
468
|
+
collection :songs, collection_options.merge(:class => Song) do
|
469
|
+
property :name
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
it { request.send("to_#{format}").must_equal output }
|
474
|
+
it { request.send("from_#{format}", input).songs.first.name.must_equal "You've Taken Everything"}
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
455
478
|
describe "without :class" do
|
456
479
|
representer! do
|
457
480
|
property :song do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: representable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.6.
|
4
|
+
version: 1.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -195,6 +195,7 @@ files:
|
|
195
195
|
- test/decorator_test.rb
|
196
196
|
- test/definition_test.rb
|
197
197
|
- test/example.rb
|
198
|
+
- test/generic_test.rb
|
198
199
|
- test/hash_bindings_test.rb
|
199
200
|
- test/hash_test.rb
|
200
201
|
- test/inheritance_test.rb
|