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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f465aa1b0aa43a6e5d2bacba9a09352679d2e9c0
4
- data.tar.gz: 63ad7fd426a19a8efdc2ee5b06590a29507011fd
3
+ metadata.gz: b78042035b57cdc5c26ba4f5e5e9ad7ecc218256
4
+ data.tar.gz: 602635412709132eb114614ee69e53b9b73e31b2
5
5
  SHA512:
6
- metadata.gz: 3d7a5b5b2f92d3fba72b01acace5bf14e40c80f8c55e82da3429b8870a67203eb681d2d777598848eec4d0515c9133a235ce5de36ea8365ecdc0d1d7597d7031
7
- data.tar.gz: 0a76832f4640f6287abee2668ab58a867b65e573adfbbf71b1be44208215d068f5953dd88a7468b822208a20f3b78050f1f2f64ffdda87dc86b9bb9501386bfd
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.
@@ -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
- def inline_representer(base_module, &block) # DISCUSS: separate module?
155
- Module.new do
156
- include base_module
157
- instance_exec &block
158
- end
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'
@@ -162,7 +162,7 @@ module Representable
162
162
 
163
163
  def instance_for(fragment, *args)
164
164
  return unless options[:instance]
165
- call_proc_for(options[:instance], fragment)
165
+ call_proc_for(options[:instance], fragment) or get
166
166
  end
167
167
  end
168
168
  end
@@ -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 { |obj| serialize(obj) }
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) }
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.6.0"
2
+ VERSION = "1.6.1"
3
3
  end
@@ -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
@@ -1,7 +1,7 @@
1
1
  require 'test_helper'
2
2
  require 'representable/hash'
3
3
 
4
- class YamlTest < MiniTest::Spec
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)
@@ -398,7 +398,7 @@ end
398
398
  end
399
399
 
400
400
  it "creates emtpy array from default if configured" do
401
- cd = Compilation.from_json({}.to_json)
401
+ cd = Compilation.from_json("{}")
402
402
  assert_equal [], cd.bands
403
403
  end
404
404
  end
@@ -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.0
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-07-15 00:00:00.000000000 Z
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