representable 1.5.1 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.textile +7 -0
- data/README.md +25 -13
- data/TODO +1 -2
- data/lib/representable/binding.rb +11 -9
- data/lib/representable/coercion.rb +1 -1
- data/lib/representable/decorator.rb +1 -1
- data/lib/representable/decorator/coercion.rb +45 -0
- data/lib/representable/json.rb +7 -7
- data/lib/representable/version.rb +1 -1
- data/representable.gemspec +1 -0
- data/test/coercion_test.rb +26 -8
- data/test/representable_test.rb +23 -5
- metadata +19 -3
- data/test/Gemfile +0 -3
data/CHANGES.textile
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
h2. 1.5.2
|
2
|
+
|
3
|
+
* Rename `:representer_exec` to `:decorator_scope` and make it a documented (!) feature.
|
4
|
+
* Accessors for properties defined with `decorator_scope: true` will now be invoked on the decorator, not on the represented instance anymore. This allows having decorators with helper methods.
|
5
|
+
* Use `MultiJson` instead of `JSON` when parsing and rendering.
|
6
|
+
* Make `Representable::Decorator::Coercion` work.
|
7
|
+
|
1
8
|
h2. 1.5.1
|
2
9
|
|
3
10
|
* Make lonely collections and hashes work with decorators.
|
data/README.md
CHANGED
@@ -218,9 +218,21 @@ module SongRepresenter
|
|
218
218
|
end
|
219
219
|
```
|
220
220
|
|
221
|
-
That works as the method is mixed into the represented object.
|
221
|
+
That works as the method is mixed into the represented object. When adding a helper method to a decorator, representable will still invoke accessors on the represented instance - unless you tell it the scope.
|
222
222
|
|
223
|
-
|
223
|
+
```ruby
|
224
|
+
class SongRepresenter < Representable::Decorator
|
225
|
+
property :title, decorator_scope: true
|
226
|
+
|
227
|
+
def title
|
228
|
+
represented.name
|
229
|
+
end
|
230
|
+
end
|
231
|
+
```
|
232
|
+
|
233
|
+
This will call `title` getter and setter on the decorator instance, not on the represented object. You can still access the represented object in the decorator method using `represented`. BTW, in a module representer this option is ignored.
|
234
|
+
|
235
|
+
Or use `:getter` or `:setter` to dynamically add a method for the represented object.
|
224
236
|
|
225
237
|
```ruby
|
226
238
|
class SongRepresenter < Representable::Decorator
|
@@ -667,6 +679,17 @@ module SongRepresenter
|
|
667
679
|
end
|
668
680
|
```
|
669
681
|
|
682
|
+
When using a decorator representer, use the `Representable::Decorator::Coercion` module.
|
683
|
+
|
684
|
+
```ruby
|
685
|
+
module SongRepresenter < Representable::Decorator
|
686
|
+
include Representable::JSON
|
687
|
+
include Representable::Decorator::Coercion
|
688
|
+
|
689
|
+
property :recorded_at, :type => DateTime
|
690
|
+
end
|
691
|
+
```
|
692
|
+
|
670
693
|
## Undocumented Features
|
671
694
|
|
672
695
|
(Please don't read this section!)
|
@@ -678,17 +701,6 @@ end
|
|
678
701
|
property :title, :binding => lambda { |*args| JSON::TitleBinding.new(*args) }
|
679
702
|
```
|
680
703
|
|
681
|
-
* Lambdas are usually executed in the represented object's context. If your writing a `Decorator` representer and you need to execute lambdas in its context use the `:representer_exec` option.
|
682
|
-
|
683
|
-
<!-- and some more in a beautiful cuddle -->
|
684
|
-
```ruby
|
685
|
-
class SongRepresenter < Representable::Decorator
|
686
|
-
property :title, :representer_exec => true, :getter => lambda {..}
|
687
|
-
end
|
688
|
-
```
|
689
|
-
|
690
|
-
You can still access the represented object in the lambda using `represented`. In a module representer this option is ignored.
|
691
|
-
|
692
704
|
## Copyright
|
693
705
|
|
694
706
|
Representable started as a heavily simplified fork of the ROXML gem. Big thanks to Ben Woosley for his inspiring work.
|
data/TODO
CHANGED
@@ -12,14 +12,14 @@ module Representable
|
|
12
12
|
build_for(definition, *args)
|
13
13
|
end
|
14
14
|
|
15
|
-
def initialize(definition, represented, user_options={},
|
15
|
+
def initialize(definition, represented, user_options={}, exec_context=represented) # TODO: remove default arg for user options. # DISCUSS: make exec_context an options hash?
|
16
16
|
super(definition)
|
17
|
-
@represented
|
18
|
-
@user_options
|
19
|
-
@
|
17
|
+
@represented = represented
|
18
|
+
@user_options = user_options
|
19
|
+
@exec_context = exec_context
|
20
20
|
end
|
21
21
|
|
22
|
-
attr_reader :user_options, :represented
|
22
|
+
attr_reader :user_options, :represented # TODO: make private/remove.
|
23
23
|
|
24
24
|
# Main entry point for rendering/parsing a property object.
|
25
25
|
def serialize(value)
|
@@ -74,17 +74,19 @@ module Representable
|
|
74
74
|
|
75
75
|
def get
|
76
76
|
represented_exec_for(:getter) do
|
77
|
-
|
77
|
+
exec_context.send(getter)
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
81
|
def set(value)
|
82
82
|
represented_exec_for(:setter, value) do
|
83
|
-
|
83
|
+
exec_context.send(setter, value)
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
87
|
private
|
88
|
+
attr_reader :exec_context
|
89
|
+
|
88
90
|
# Execute the block for +option_name+ on the represented object.
|
89
91
|
# Executes passed block when there's no lambda for option.
|
90
92
|
def represented_exec_for(option_name, *args)
|
@@ -92,12 +94,12 @@ module Representable
|
|
92
94
|
call_proc_for(options[option_name], *args)
|
93
95
|
end
|
94
96
|
|
95
|
-
# All lambdas are executed on
|
97
|
+
# All lambdas are executed on exec_context which is either represented or the decorator instance.
|
96
98
|
def call_proc_for(proc, *args)
|
97
99
|
return proc unless proc.is_a?(Proc)
|
98
100
|
# TODO: call method when proc is sympbol.
|
99
101
|
args << user_options # DISCUSS: we assume user_options is a Hash!
|
100
|
-
|
102
|
+
exec_context.instance_exec(*args, &proc)
|
101
103
|
end
|
102
104
|
|
103
105
|
|
@@ -7,7 +7,7 @@ module Representable::Coercion
|
|
7
7
|
extend ClassMethods
|
8
8
|
end
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
module ClassMethods
|
12
12
|
def property(name, args={})
|
13
13
|
attribute(name, args[:type]) if args[:type] # FIXME (in virtus): undefined method `superclass' for VirtusCoercionTest::SongRepresenter:Module
|
@@ -14,7 +14,7 @@ module Representable
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def representable_binding_for(attr, format, options)
|
17
|
-
context = attr.options[:
|
17
|
+
context = attr.options[:decorator_scope] ? self : represented # DISCUSS: should Decorator know this kinda stuff?
|
18
18
|
|
19
19
|
format.build(attr, represented, options, context)
|
20
20
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Representable::Decorator
|
2
|
+
module Coercion
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
include Virtus
|
6
|
+
extend Representable::Coercion::ClassMethods
|
7
|
+
extend ClassMethods
|
8
|
+
|
9
|
+
def initialize(represented) # override Virtus' #initialize.
|
10
|
+
@represented = represented
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def property(name, options={})
|
17
|
+
if options[:type]
|
18
|
+
options[:decorator_scope] = true # call setter on decorator so coercion kicks in.
|
19
|
+
create_writer(name)
|
20
|
+
create_reader(name)
|
21
|
+
end
|
22
|
+
|
23
|
+
super # Representable::Coercion.
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
# FIXME: dear @solnic, please make this better!
|
28
|
+
def create_writer(name)
|
29
|
+
# the call to super makes the actual coercion, which is then delegated to the represented instance.
|
30
|
+
define_method "#{name}=" do |v|
|
31
|
+
coerced_value = super(v).get(self)
|
32
|
+
represented.send("#{name}=", coerced_value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_reader(name)
|
37
|
+
# the call to super makes the actual coercion, which is then delegated to the represented instance.
|
38
|
+
define_method "#{name}" do
|
39
|
+
send("#{name}=", represented.send(name))
|
40
|
+
super()
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/representable/json.rb
CHANGED
@@ -14,25 +14,25 @@ module Representable
|
|
14
14
|
extend Representable::Hash::ClassMethods # DISCUSS: this is only for .from_hash, remove in 2.3?
|
15
15
|
end
|
16
16
|
end
|
17
|
-
|
18
|
-
|
17
|
+
|
18
|
+
|
19
19
|
module ClassMethods
|
20
20
|
# Creates a new object from the passed JSON document.
|
21
21
|
def from_json(*args, &block)
|
22
22
|
create_represented(*args, &block).from_json(*args)
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
26
|
-
|
25
|
+
|
26
|
+
|
27
27
|
# Parses the body as JSON and delegates to #from_hash.
|
28
28
|
def from_json(data, *args)
|
29
|
-
data =
|
29
|
+
data = MultiJson.load(data)
|
30
30
|
from_hash(data, *args)
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
# Returns a JSON string representing this object.
|
34
34
|
def to_json(*args)
|
35
|
-
to_hash(*args)
|
35
|
+
MultiJson.dump to_hash(*args)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
data/representable.gemspec
CHANGED
data/test/coercion_test.rb
CHANGED
@@ -4,7 +4,7 @@ require 'representable/coercion'
|
|
4
4
|
class VirtusCoercionTest < MiniTest::Spec
|
5
5
|
class Song # note that we don't define accessors for the properties here.
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
describe "Coercion with Virtus" do
|
9
9
|
describe "on object level" do
|
10
10
|
module SongRepresenter
|
@@ -13,7 +13,7 @@ class VirtusCoercionTest < MiniTest::Spec
|
|
13
13
|
property :composed_at, :type => DateTime
|
14
14
|
property :track, :type => Integer
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
it "coerces properties in #from_json" do
|
18
18
|
song = Song.new.extend(SongRepresenter).from_json("{\"composed_at\":\"November 18th, 1983\",\"track\":\"18\"}")
|
19
19
|
assert_kind_of DateTime, song.composed_at
|
@@ -21,29 +21,47 @@ class VirtusCoercionTest < MiniTest::Spec
|
|
21
21
|
assert_equal DateTime.parse("Fri, 18 Nov 1983 00:00:00 +0000"), song.composed_at
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
25
|
-
|
24
|
+
|
25
|
+
|
26
26
|
describe "on class level" do
|
27
27
|
class ImmigrantSong
|
28
28
|
include Representable::JSON
|
29
29
|
include Representable::Coercion
|
30
|
-
|
30
|
+
|
31
31
|
property :composed_at, :type => DateTime, :default => "May 12th, 2012"
|
32
32
|
property :track, :type => Integer
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
it "coerces into the provided type" do
|
36
36
|
song = ImmigrantSong.new.from_json("{\"composed_at\":\"November 18th, 1983\",\"track\":\"18\"}")
|
37
37
|
assert_equal DateTime.parse("Fri, 18 Nov 1983 00:00:00 +0000"), song.composed_at
|
38
38
|
assert_equal 18, song.track
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
it "respects the :default options" do
|
42
42
|
song = ImmigrantSong.new.from_json("{}")
|
43
43
|
assert_kind_of DateTime, song.composed_at
|
44
44
|
assert_equal DateTime.parse("Mon, 12 May 2012 00:00:00 +0000"), song.composed_at
|
45
45
|
end
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
|
+
require 'representable/decorator/coercion'
|
49
|
+
describe "on decorator" do
|
50
|
+
class SongRepresentation < Representable::Decorator
|
51
|
+
include Representable::JSON
|
52
|
+
include Representable::Decorator::Coercion
|
53
|
+
|
54
|
+
property :composed_at, :type => DateTime
|
55
|
+
end
|
56
|
+
|
57
|
+
it "coerces when parsing" do
|
58
|
+
song = SongRepresentation.new(OpenStruct.new).from_json("{\"composed_at\":\"November 18th, 1983\"}")
|
59
|
+
song.composed_at.must_equal DateTime.parse("Fri, 18 Nov 1983")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "coerces when rendering" do
|
63
|
+
SongRepresentation.new(OpenStruct.new(:composed_at => "November 18th, 1983")).to_json.must_equal "{\"composed_at\":\"1983-11-18T00:00:00+00:00\"}"
|
64
|
+
end
|
65
|
+
end
|
48
66
|
end
|
49
67
|
end
|
data/test/representable_test.rb
CHANGED
@@ -683,11 +683,19 @@ class RepresentableTest < MiniTest::Spec
|
|
683
683
|
end
|
684
684
|
end
|
685
685
|
|
686
|
-
describe ":
|
686
|
+
describe ":decorator_scope" do
|
687
687
|
representer! do
|
688
|
-
property :title, :getter => lambda { |*| title_from_representer }, :
|
688
|
+
property :title, :getter => lambda { |*| title_from_representer }, :decorator_scope => true
|
689
689
|
end
|
690
690
|
|
691
|
+
let (:representer_with_method) {
|
692
|
+
Module.new do
|
693
|
+
include Representable::Hash
|
694
|
+
property :title, :decorator_scope => true
|
695
|
+
def title; "Crystal Planet"; end
|
696
|
+
end
|
697
|
+
}
|
698
|
+
|
691
699
|
it "executes lambdas in represented context" do
|
692
700
|
Class.new do
|
693
701
|
def title_from_representer
|
@@ -696,8 +704,11 @@ class RepresentableTest < MiniTest::Spec
|
|
696
704
|
end.new.extend(representer).to_hash.must_equal({"title"=>"Sounds Of Silence"})
|
697
705
|
end
|
698
706
|
|
699
|
-
|
707
|
+
it "executes method in represented context" do
|
708
|
+
Object.new.extend(representer_with_method).to_hash.must_equal({"title"=>"Crystal Planet"})
|
709
|
+
end
|
700
710
|
|
711
|
+
describe "with decorator" do
|
701
712
|
it "executes lambdas in representer context" do
|
702
713
|
rpr = representer
|
703
714
|
Class.new(Representable::Decorator) do
|
@@ -706,13 +717,20 @@ class RepresentableTest < MiniTest::Spec
|
|
706
717
|
def title_from_representer
|
707
718
|
"Sounds Of Silence"
|
708
719
|
end
|
709
|
-
end.new(Object.new).to_hash
|
720
|
+
end.new(Object.new).to_hash.must_equal({"title"=>"Sounds Of Silence"})
|
721
|
+
end
|
722
|
+
|
723
|
+
it "executes method in representer context" do
|
724
|
+
rpr = representer_with_method
|
725
|
+
Class.new(Representable::Decorator) do
|
726
|
+
include rpr # mixes in #title.
|
727
|
+
end.new(Object.new).to_hash.must_equal({"title"=>"Crystal Planet"})
|
710
728
|
end
|
711
729
|
|
712
730
|
it "still allows accessing the represented object" do
|
713
731
|
Class.new(Representable::Decorator) do
|
714
732
|
include Representable::Hash
|
715
|
-
property :title, :getter => lambda { |*| represented.title }, :
|
733
|
+
property :title, :getter => lambda { |*| represented.title }, :decorator_scope => true
|
716
734
|
|
717
735
|
def title
|
718
736
|
"Sounds Of Silence"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: representable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
@@ -155,6 +155,22 @@ dependencies:
|
|
155
155
|
- - ! '>='
|
156
156
|
- !ruby/object:Gem::Version
|
157
157
|
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: json
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ~>
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: 1.7.7
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ~>
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 1.7.7
|
158
174
|
description: Maps representation documents from and to Ruby objects. Includes XML
|
159
175
|
and JSON support, plain properties, collections and compositions.
|
160
176
|
email:
|
@@ -178,6 +194,7 @@ files:
|
|
178
194
|
- lib/representable/bindings/yaml_bindings.rb
|
179
195
|
- lib/representable/coercion.rb
|
180
196
|
- lib/representable/decorator.rb
|
197
|
+
- lib/representable/decorator/coercion.rb
|
181
198
|
- lib/representable/definition.rb
|
182
199
|
- lib/representable/deprecations.rb
|
183
200
|
- lib/representable/feature/readable_writeable.rb
|
@@ -192,7 +209,6 @@ files:
|
|
192
209
|
- lib/representable/xml/hash.rb
|
193
210
|
- lib/representable/yaml.rb
|
194
211
|
- representable.gemspec
|
195
|
-
- test/Gemfile
|
196
212
|
- test/coercion_test.rb
|
197
213
|
- test/definition_test.rb
|
198
214
|
- test/example.rb
|
data/test/Gemfile
DELETED