representable 1.5.1 → 1.5.2

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.
@@ -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 => "../"