representable 1.6.0 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
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