representable 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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. Of course, this doesn't work with decorators.
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
- Use `:getter` or `:setter` to dynamically add a method for the represented object.
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
@@ -27,5 +27,4 @@ module ReaderWriter
27
27
 
28
28
  * make lambda options optional (arity == 0)
29
29
 
30
- * use `representer_exec` not only for lambdas, but also for methods
31
- property :title # => calls decorator.title
30
+ * pass args to methods when arity matches
@@ -12,14 +12,14 @@ module Representable
12
12
  build_for(definition, *args)
13
13
  end
14
14
 
15
- def initialize(definition, represented, user_options={}, lambda_context=represented) # TODO: remove default arg for user options. # DISCUSS: make lambda_context an options hash?
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 = represented
18
- @user_options = user_options
19
- @lambda_context = lambda_context
17
+ @represented = represented
18
+ @user_options = user_options
19
+ @exec_context = exec_context
20
20
  end
21
21
 
22
- attr_reader :user_options, :represented, :lambda_context # TODO: make private/remove.
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
- represented.send(getter)
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
- represented.send(setter, value)
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 lambda_context which is either represented or the decorator instance.
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
- lambda_context.instance_exec(*args, &proc)
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[:representer_exec] ? self : represented # DISCUSS: should Decorator know this kinda stuff?
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
@@ -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 = ::JSON[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).to_json
35
+ MultiJson.dump to_hash(*args)
36
36
  end
37
37
  end
38
38
  end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.5.1"
2
+ VERSION = "1.5.2"
3
3
  end
@@ -29,4 +29,5 @@ Gem::Specification.new do |s|
29
29
  s.add_development_dependency "mongoid"
30
30
  s.add_development_dependency "virtus", "~> 0.5.0"
31
31
  s.add_development_dependency "yajl-ruby"
32
+ s.add_development_dependency "json", '~> 1.7.7'
32
33
  end
@@ -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
@@ -683,11 +683,19 @@ class RepresentableTest < MiniTest::Spec
683
683
  end
684
684
  end
685
685
 
686
- describe ":representer_exec" do
686
+ describe ":decorator_scope" do
687
687
  representer! do
688
- property :title, :getter => lambda { |*| title_from_representer }, :representer_exec => true
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
- describe "with decorator" do
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().must_equal({"title"=>"Sounds Of Silence"})
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 }, :representer_exec => true
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.1
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-23 00:00:00.000000000 Z
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
@@ -1,3 +0,0 @@
1
- source :rubygems
2
-
3
- gem 'representable', :path => "../"