representable 1.2.9 → 1.3.0

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.
data/TODO CHANGED
@@ -4,4 +4,14 @@ class: |key, hsh|
4
4
  * Allow passing options to Binding#serialize.
5
5
  serialize(.., options{:exclude => ..})
6
6
 
7
- * Allow :writer and :reader / :getter/:setter
7
+ * Allow :writer and :reader / :getter/:setter
8
+
9
+ document `XML::AttributeHash` etc
10
+
11
+ * deprecate :from, make :a(lia)s authorative.
12
+ * cleanup ReadableWriteable
13
+ * deprecate Representable::*::ClassMethods (::from_hash and friends)
14
+
15
+ * Song < OpenStruct in test_helper
16
+
17
+ * have representable-options (:include, :exclude) and user-options
@@ -42,7 +42,7 @@ module Representable
42
42
 
43
43
  # Reads values from +doc+ and sets properties accordingly.
44
44
  def update_properties_from(doc, options, format)
45
- representable_bindings_for(format).each do |bin|
45
+ representable_bindings_for(format, options).each do |bin|
46
46
  deserialize_property(bin, doc, options)
47
47
  end
48
48
  self
@@ -51,7 +51,7 @@ module Representable
51
51
  private
52
52
  # Compiles the document going through all properties.
53
53
  def create_representation_with(doc, options, format)
54
- representable_bindings_for(format).each do |bin|
54
+ representable_bindings_for(format, options).each do |bin|
55
55
  serialize_property(bin, doc, options)
56
56
  end
57
57
  doc
@@ -88,44 +88,35 @@ private
88
88
  # Retrieve value and write fragment to the doc.
89
89
  def compile_fragment(bin, doc)
90
90
  value = send(bin.getter)
91
- value = bin.default_for(value)
92
91
 
93
- write_fragment_for(bin, value, doc)
92
+ bin.write_fragment(doc, value)
94
93
  end
95
94
 
96
95
  # Parse value from doc and update the model property.
97
96
  def uncompile_fragment(bin, doc)
98
- value = read_fragment_for(bin, doc)
99
-
100
- if value == Binding::FragmentNotFound
101
- return unless bin.has_default?
102
- value = bin.default
97
+ bin.read_fragment(doc) do |value|
98
+ send(bin.setter, value)
103
99
  end
104
-
105
- send(bin.setter, value)
106
- end
107
-
108
- def write_fragment_for(bin, value, doc) # DISCUSS: move to Binding?
109
- return if bin.skipable_nil_value?(value)
110
- bin.write(doc, value)
111
- end
112
-
113
- def read_fragment_for(bin, doc) # DISCUSS: move to Binding?
114
- bin.read(doc)
115
100
  end
116
101
 
117
102
  def representable_attrs
118
103
  @representable_attrs ||= self.class.representable_attrs # DISCUSS: copy, or better not?
119
104
  end
120
105
 
121
- def representable_bindings_for(format)
122
- representable_attrs.map {|attr| format.build_for(attr, self) }
106
+ def representable_bindings_for(format, options)
107
+ options = cleanup_options(options) # FIXME: make representable-options and user-options two different hashes.
108
+ representable_attrs.map {|attr| format.build_for(attr, self, options) }
123
109
  end
124
110
 
125
111
  # Returns the wrapper for the representation. Mostly used in XML.
126
112
  def representation_wrap
127
113
  representable_attrs.wrap_for(self.class.name)
128
114
  end
115
+
116
+ private
117
+ def cleanup_options(options) # TODO: remove me.
118
+ options.reject { |k,v| [:include, :exclude].include?(k) }
119
+ end
129
120
 
130
121
  module ClassInclusions
131
122
  def included(base)
@@ -10,9 +10,10 @@ module Representable
10
10
  raise "Binding#definition is no longer supported as all Definition methods are now delegated automatically."
11
11
  end
12
12
 
13
- def initialize(definition, represented)
13
+ def initialize(definition, represented, user_options={}) # TODO: remove default arg.
14
14
  super(definition)
15
- @represented = represented
15
+ @represented = represented
16
+ @user_options = user_options
16
17
  end
17
18
 
18
19
  # Main entry point for rendering/parsing a property object.
@@ -24,6 +25,33 @@ module Representable
24
25
  fragment
25
26
  end
26
27
 
28
+
29
+ def write_fragment(doc, value)
30
+ value = default_for(value)
31
+
32
+ write_fragment_for(value, doc)
33
+ end
34
+
35
+ def write_fragment_for(value, doc)
36
+ return if skipable_nil_value?(value)
37
+ write(doc, value)
38
+ end
39
+
40
+ def read_fragment(doc)
41
+ value = read_fragment_for(doc)
42
+
43
+ if value == FragmentNotFound
44
+ return unless has_default?
45
+ value = default
46
+ end
47
+
48
+ yield value
49
+ end
50
+
51
+ def read_fragment_for(doc)
52
+ read(doc)
53
+ end
54
+
27
55
 
28
56
  # Hooks into #serialize and #deserialize to extend typed properties
29
57
  # at runtime.
@@ -62,12 +90,12 @@ module Representable
62
90
  def serialize(object)
63
91
  return object if object.nil?
64
92
 
65
- super.send(serialize_method, :wrap => false) # TODO: pass :binding => self
93
+ super.send(serialize_method, @user_options.merge!({:wrap => false})) # TODO: pass :binding => self
66
94
  end
67
95
 
68
96
  def deserialize(data)
69
97
  # DISCUSS: does it make sense to skip deserialization of nil-values here?
70
- super(create_object(data)).send(deserialize_method, data)
98
+ super(create_object(data)).send(deserialize_method, data, @user_options)
71
99
  end
72
100
 
73
101
  def create_object(fragment)
@@ -16,10 +16,10 @@ module Representable
16
16
 
17
17
 
18
18
  class PropertyBinding < Representable::Binding
19
- def self.build_for(definition, represented)
20
- return CollectionBinding.new(definition, represented) if definition.array?
21
- return HashBinding.new(definition, represented) if definition.hash?
22
- new(definition, represented)
19
+ def self.build_for(definition, *args) # TODO: remove default arg.
20
+ return CollectionBinding.new(definition, *args) if definition.array?
21
+ return HashBinding.new(definition, *args) if definition.hash?
22
+ new(definition, *args)
23
23
  end
24
24
 
25
25
  def initialize(*args) # FIXME. make generic.
@@ -24,12 +24,12 @@ module Representable
24
24
 
25
25
 
26
26
  class PropertyBinding < Binding
27
- def self.build_for(definition, represented)
28
- return CollectionBinding.new(definition, represented) if definition.array?
29
- return HashBinding.new(definition, represented) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
30
- return AttributeHashBinding.new(definition, represented) if definition.hash? and definition.options[:use_attributes]
31
- return AttributeBinding.new(definition, represented) if definition.attribute
32
- new(definition, represented)
27
+ def self.build_for(definition, *args)
28
+ return CollectionBinding.new(definition, *args) if definition.array?
29
+ return HashBinding.new(definition, *args) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
30
+ return AttributeHashBinding.new(definition, *args) if definition.hash? and definition.options[:use_attributes]
31
+ return AttributeBinding.new(definition, *args) if definition.attribute
32
+ new(definition, *args)
33
33
  end
34
34
 
35
35
  def initialize(*args)
@@ -48,10 +48,7 @@ module Representable
48
48
  end
49
49
 
50
50
  def read(node)
51
- selector = "./#{xpath}"
52
- selector = "./#{options[:wrap]}/#{xpath}" if options[:wrap]
53
- nodes = node.search(selector)
54
-
51
+ nodes = find_nodes(node)
55
52
  return FragmentNotFound if nodes.size == 0 # TODO: write dedicated test!
56
53
 
57
54
  deserialize_from(nodes)
@@ -83,6 +80,12 @@ module Representable
83
80
  from
84
81
  end
85
82
 
83
+ def find_nodes(doc)
84
+ selector = xpath
85
+ selector = "#{options[:wrap]}/#{xpath}" if options[:wrap]
86
+ nodes = doc.xpath(selector)
87
+ end
88
+
86
89
  def node_for(parent, name)
87
90
  Nokogiri::XML::Node.new(name.to_s, parent.document)
88
91
  end
@@ -19,12 +19,9 @@ module Representable
19
19
  end
20
20
 
21
21
  class PropertyBinding < Representable::Hash::PropertyBinding
22
- def self.build_for(definition, represented)
23
- return CollectionBinding.new(definition, represented) if definition.array?
24
- #return HashBinding.new(definition) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
25
- #return AttributeHashBinding.new(definition) if definition.hash? and definition.options[:use_attributes]
26
- #return AttributeBinding.new(definition) if definition.attribute
27
- new(definition, represented)
22
+ def self.build_for(definition, *args)
23
+ return CollectionBinding.new(definition, *args) if definition.array?
24
+ new(definition, *args)
28
25
  end
29
26
 
30
27
  def initialize(*args) # FIXME. make generic.
@@ -1,11 +1,4 @@
1
1
  module Representable
2
2
  module Deprecations
3
- def skip_excluded_property?(binding, options) # TODO: remove with 1.3.
4
- if options[:except]
5
- options[:exclude] = options[:except]
6
- warn "The :except option is deprecated and will be removed in 1.3. Please use :exclude."
7
- end # i wanted a one-liner but failed :)
8
- super
9
- end
10
3
  end
11
4
  end
@@ -8,13 +8,13 @@ module Representable
8
8
  end
9
9
 
10
10
  def create_representation_with(doc, options, format)
11
- bin = representable_bindings_for(format).first
11
+ bin = representable_bindings_for(format, options).first
12
12
  hash = filter_keys_for(self, options)
13
13
  bin.write(doc, hash)
14
14
  end
15
15
 
16
16
  def update_properties_from(doc, options, format)
17
- bin = representable_bindings_for(format).first
17
+ bin = representable_bindings_for(format, options).first
18
18
  hash = filter_keys_for(doc, options)
19
19
  value = bin.deserialize_from(hash)
20
20
  replace(value)
@@ -18,12 +18,12 @@ module Representable::JSON
18
18
 
19
19
 
20
20
  def create_representation_with(doc, options, format)
21
- bin = representable_bindings_for(format).first
21
+ bin = representable_bindings_for(format, options).first
22
22
  bin.serialize_for(self)
23
23
  end
24
24
 
25
25
  def update_properties_from(doc, options, format)
26
- bin = representable_bindings_for(format).first
26
+ bin = representable_bindings_for(format, options).first
27
27
  value = bin.deserialize_from(doc)
28
28
  replace(value)
29
29
  end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.2.9"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -18,13 +18,13 @@ module Representable::XML
18
18
 
19
19
 
20
20
  def create_representation_with(doc, options, format)
21
- bin = representable_bindings_for(format).first
22
- bin.serialize_for(self)
21
+ bin = representable_bindings_for(format, options).first
22
+ bin.write(doc, self)
23
23
  end
24
24
 
25
25
  def update_properties_from(doc, options, format)
26
- bin = representable_bindings_for(format).first
27
- value = bin.deserialize_from(doc)
26
+ bin = representable_bindings_for(format, options).first
27
+ value = bin.deserialize_from(doc.search("./*")) # FIXME: use Binding#read.
28
28
  replace(value)
29
29
  end
30
30
 
@@ -4,6 +4,14 @@ Bundler.setup
4
4
  require 'representable/yaml'
5
5
  require 'ostruct'
6
6
 
7
+ def reset_representer(*module_name)
8
+ module_name.each do |mod|
9
+ mod.module_eval do
10
+ @representable_attrs = nil
11
+ end
12
+ end
13
+ end
14
+
7
15
  class Song < OpenStruct
8
16
  end
9
17
 
@@ -37,11 +45,7 @@ puts song.extend(SongRepresenter).to_json
37
45
 
38
46
  ######### collections
39
47
 
40
- module SongRepresenter
41
- include Representable::JSON
42
-
43
- self.representation_wrap= false
44
- end
48
+ reset_representer(SongRepresenter)
45
49
 
46
50
  module SongRepresenter
47
51
  include Representable::JSON
@@ -76,6 +80,7 @@ end
76
80
  album = Album.new(:name => "The Police", :song => song)
77
81
  puts album.extend(AlbumRepresenter).to_json
78
82
 
83
+ reset_representer(AlbumRepresenter)
79
84
 
80
85
  module AlbumRepresenter
81
86
  include Representable::JSON
@@ -88,23 +93,25 @@ album = Album.new(:name => "The Police", :songs => [song, Song.new(:title => "Sy
88
93
  puts album.extend(AlbumRepresenter).to_json
89
94
 
90
95
 
91
- SongRepresenter.module_eval do
92
- @representable_attrs = nil
93
- end
96
+ album = Album.new.extend(AlbumRepresenter).from_json(%{{"name":"Offspring","songs":[{"title":"Genocide"},{"title":"Nitro","composers":["Offspring"]}]}
97
+ })
98
+ puts album.inspect
94
99
 
100
+ reset_representer(SongRepresenter)
95
101
 
96
102
  ######### using helpers (customizing the rendering/parsing)
97
103
  module AlbumRepresenter
104
+ include Representable::JSON
105
+
98
106
  def name
99
- super.upper
107
+ super.upcase
100
108
  end
101
109
  end
102
- album = Album.new(:name => "The Police", :songs => [song, Song.new(:title => "Synchronicity")])
103
- puts album.extend(AlbumRepresenter).to_json
104
110
 
105
- SongRepresenter.module_eval do
106
- @representable_attrs = nil
107
- end
111
+ puts Album.new(:name => "The Police").
112
+ extend(AlbumRepresenter).to_json
113
+
114
+ reset_representer(SongRepresenter)
108
115
 
109
116
 
110
117
  ######### inheritance
@@ -139,10 +146,7 @@ end
139
146
  song = Song.new(:title => "Fallout", :composers => ["Steward Copeland", "Sting"])
140
147
  puts song.extend(SongRepresenter).to_xml
141
148
 
142
-
143
- SongRepresenter.module_eval do
144
- @representable_attrs = nil
145
- end
149
+ reset_representer(SongRepresenter)
146
150
 
147
151
 
148
152
  ### YAML
@@ -172,8 +176,135 @@ module SongRepresenter
172
176
  end
173
177
  puts song.extend(SongRepresenter).to_yaml
174
178
 
179
+ SongRepresenter.module_eval do
180
+ @representable_attrs = nil
181
+ end
182
+
183
+ # R/W support
184
+ song = Song.new(:title => "You're Wrong", :track => 4)
185
+ module SongRepresenter
186
+ include Representable::Hash
187
+
188
+ property :title, :readable => false
189
+ property :track
190
+ end
191
+ puts song.extend(SongRepresenter).to_hash
192
+
193
+ SongRepresenter.module_eval do
194
+ @representable_attrs = nil
195
+ end
196
+
197
+ module SongRepresenter
198
+ include Representable::Hash
199
+
200
+ property :title, :writeable => false
201
+ property :track
202
+ end
203
+ song = Song.new.extend(SongRepresenter)
204
+ song.from_hash({:title => "Fallout", :track => 1})
205
+ puts song
175
206
 
176
207
  ######### custom methods in representer (using helpers)
177
- ######### r/w, conditions
178
- #########
179
- ######### polymorphic :extend and :class, instance context!, :instance
208
+ ######### conditions
209
+ ######### :as
210
+ ######### XML::Collection
211
+ ######### polymorphic :extend and :class, instance context!, :instance
212
+ class CoverSong < Song
213
+ end
214
+
215
+ songs = [Song.new(title: "Weirdo", track: 5), CoverSong.new(title: "Truth Hits Everybody", track: 6, copyright: "The Police")]
216
+ album = Album.new(name: "Incognito", songs: songs)
217
+
218
+
219
+ reset_representer(SongRepresenter, AlbumRepresenter)
220
+
221
+ module SongRepresenter
222
+ include Representable::Hash
223
+
224
+ property :title
225
+ property :track
226
+ end
227
+
228
+ module CoverSongRepresenter
229
+ include Representable::Hash
230
+ include SongRepresenter
231
+
232
+ property :copyright
233
+ end
234
+
235
+ module AlbumRepresenter
236
+ include Representable::Hash
237
+
238
+ property :name
239
+ collection :songs, :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter }
240
+ end
241
+
242
+ puts album.extend(AlbumRepresenter).to_hash
243
+
244
+ reset_representer(AlbumRepresenter)
245
+
246
+ module AlbumRepresenter
247
+ include Representable::Hash
248
+
249
+ property :name
250
+ collection :songs,
251
+ :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter },
252
+ :class => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong : Song } #=> {"title"=>"Weirdo", "track"=>5}
253
+ end
254
+
255
+ album = Album.new.extend(AlbumRepresenter).from_hash({"name"=>"Incognito", "songs"=>[{"title"=>"Weirdo", "track"=>5}, {"title"=>"Truth Hits Everybody", "track"=>6, "copyright"=>"The Police"}]})
256
+ puts album.inspect
257
+
258
+ reset_representer(AlbumRepresenter)
259
+
260
+ module AlbumRepresenter
261
+ include Representable::Hash
262
+
263
+ property :name
264
+ collection :songs,
265
+ :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter },
266
+ :instance => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong.new : Song.new(original: true) }
267
+ end
268
+
269
+ album = Album.new.extend(AlbumRepresenter).from_hash({"name"=>"Incognito", "songs"=>[{"title"=>"Weirdo", "track"=>5}, {"title"=>"Truth Hits Everybody", "track"=>6, "copyright"=>"The Police"}]})
270
+ puts album.inspect
271
+
272
+ ######### #hash
273
+
274
+ reset_representer(SongRepresenter)
275
+
276
+ module SongRepresenter
277
+ include Representable::JSON
278
+
279
+ property :title
280
+ hash :ratings
281
+ end
282
+
283
+ puts Song.new(title: "Bliss", ratings: {"Rolling Stone" => 4.9, "FryZine" => 4.5}).extend(SongRepresenter).to_json
284
+
285
+ ######### JSON::Hash
286
+
287
+ module FavoriteSongsRepresenter
288
+ include Representable::JSON::Hash
289
+ end
290
+
291
+ puts( {"Nick" => "Hyper Music", "El" => "Blown In The Wind"}.extend(FavoriteSongsRepresenter).to_json)
292
+
293
+ require 'representable/json/hash'
294
+ module FavoriteSongsRepresenter
295
+ include Representable::JSON::Hash
296
+
297
+ values extend: SongRepresenter, class: Song
298
+ end
299
+
300
+ puts( {"Nick" => Song.new(title: "Hyper Music")}.extend(FavoriteSongsRepresenter).to_json)
301
+
302
+
303
+ require 'representable/json/collection'
304
+ module SongsRepresenter
305
+ include Representable::JSON::Collection
306
+
307
+ items extend: SongRepresenter, class: Song
308
+ end
309
+
310
+ puts [Song.new(title: "Hyper Music"), Song.new(title: "Screenager")].extend(SongsRepresenter).to_json