representable 1.6.1 → 1.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b78042035b57cdc5c26ba4f5e5e9ad7ecc218256
4
- data.tar.gz: 602635412709132eb114614ee69e53b9b73e31b2
3
+ metadata.gz: 8702c711c2230d338b5390e3e10cd7af901d41cf
4
+ data.tar.gz: fb5fd9498c58b6047a3ec221950f76c39d633c42
5
5
  SHA512:
6
- metadata.gz: 75a8db26513def9bf34f95e43ddc9edda28a8d8eda41d271b4e8b55bbad6b9dc4244d7d8d7a390996cf12907f68e71721cf940e9cbf6f4d6b6a53fd9095a4b56
7
- data.tar.gz: 33a3d4c76452a0400897c3dc26c2ea66a9f5f15757ce9c87d35ddca65b4baa1ccdc740244dc4a70f39384b426075baa47d70af08fb9d835f4da94cb7ca51d68f
6
+ metadata.gz: 1a66be6a8a40fa284c674ae0ab63aa77e48cf9257be1de100a474cc0a14bffe2271172104b190f7fc9987eb6927402db5e9f24953d670a3733b619fef3ac2ae9
7
+ data.tar.gz: 7b89c5a30d057d6e47ebd34f2881acc7d94df6e25e4a42692ce38b92e457aff7a96b1be12bb25312d997fdf6c58cbad46af2906487b7badf78e58e99925a47b5
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ h2. 1.7.0
2
+
3
+ * The actual serialization and deserialization (that is, calling `to_hash` etc on the object) now happens in dedicated classes: `ObjectDeserializer` and friends. If you used to override stuff in `Binding`, I'm sorry.
4
+ * A new option `parse_strategy: :sync`. Instead of creating a new object using the `:class` option when parsing, it uses the original object found in the represented instance. This works for property and collections.
5
+ * `Config` is now a hash. You may find a particular definition by using `Config#[]`.
6
+ * Properties are now overridden: when calling `property(:title)` multiple times with the same name, this will override the former `Definition`. While this slightly changes the API, it allows overriding properties cleanly in sub-representers and saves you from manually finding and fiddling with the definitions.
7
+
1
8
  h2. 1.6.1
2
9
 
3
10
  * 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.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Representable maps ruby objects to documents and back.
4
4
 
5
- In other words: Take an object and extend it with a representer module. This will allow you to render a JSON, XML or YAML document from that object. But that's only half of it! You can also use representers to parse a document and create an object.
5
+ In other words: Take an object and decorate it with a representer module. This will allow you to render a JSON, XML or YAML document from that object. But that's only half of it! You can also use representers to parse a document and create or populate an object.
6
6
 
7
7
  Representable is helpful for all kind of rendering and parsing workflows. However, it is mostly useful in API code. Are you planning to write a real REST API with representable? Then check out the [roar](http://github.com/apotonick/roar) gem first, save work and time and make the world a better place instead.
8
8
 
@@ -117,7 +117,7 @@ private
117
117
  # property :name, :readable => false
118
118
  # property :name, :writeable => false
119
119
  def property(name, options={}, &block)
120
- (representable_attrs << definition_class.new(name, options)).last
120
+ representable_attrs << definition_class.new(name, options)
121
121
  end
122
122
 
123
123
  # Declares a represented document node collection.
@@ -0,0 +1,5 @@
1
+ * all property objects should be extended/wrapped so we don't need the switch.
2
+
3
+ # Deprecations
4
+
5
+ * deprecate instance: { nil } which is superseded by parse_strategy: :sync
@@ -1,4 +1,6 @@
1
- require 'delegate'
1
+ require "delegate"
2
+ require "representable/deserializer"
3
+ require "representable/serializer"
2
4
 
3
5
  module Representable
4
6
  # The Binding wraps the Definition instance for this property and provides methods to read/write fragments.
@@ -26,7 +28,7 @@ module Representable
26
28
  value
27
29
  end
28
30
 
29
- def deserialize(fragment)
31
+ def deserialize(fragment, *args)
30
32
  fragment
31
33
  end
32
34
 
@@ -84,6 +86,8 @@ module Representable
84
86
  end
85
87
  end
86
88
 
89
+ # the remaining methods in this class are format-independent and should be in Definition.
90
+
87
91
  private
88
92
  attr_reader :exec_context
89
93
 
@@ -103,47 +107,23 @@ module Representable
103
107
  end
104
108
 
105
109
 
106
- # Hooks into #serialize and #deserialize to setup (extend/decorate) typed properties
107
- # at runtime.
108
110
  module Prepare
109
- # Extends the object with its representer before serialization.
110
- def serialize(*)
111
- prepare(super)
112
- end
113
-
114
- def deserialize(*)
115
- prepare(super)
116
- end
117
-
118
- def prepare(object)
119
- return object unless mod = representer_module_for(object) # :extend.
120
-
121
- mod = mod.first if mod.is_a?(Array) # TODO: deprecate :extend => [..]
122
- mod.prepare(object)
123
- end
124
-
125
- private
126
111
  def representer_module_for(object, *args)
127
112
  call_proc_for(representer_module, object) # TODO: how to pass additional data to the computing block?`
128
113
  end
129
114
  end
115
+ include Prepare
130
116
 
131
- # Overrides #serialize/#deserialize to call #to_*/from_*.
132
- # Computes :class in #deserialize. # TODO: shouldn't this be in a separate module? ObjectSerialize/ObjectDeserialize?
133
- module Object
134
- include Binding::Prepare
135
117
 
118
+ # Delegates to call #to_*/from_*.
119
+ module Object
136
120
  def serialize(object)
137
- return object if object.nil?
138
-
139
- super.send(serialize_method, @user_options.merge!({:wrap => false})) # TODO: pass :binding => self
121
+ ObjectSerializer.new(self, object).call
140
122
  end
141
123
 
142
- def deserialize(data)
124
+ def deserialize(data, object=lambda { get })
143
125
  # DISCUSS: does it make sense to skip deserialization of nil-values here?
144
- create_object(data).tap do |obj|
145
- super(obj).send(deserialize_method, data, @user_options)
146
- end
126
+ ObjectDeserializer.new(self, object).call(data)
147
127
  end
148
128
 
149
129
  def create_object(fragment)
@@ -152,7 +132,7 @@ module Representable
152
132
 
153
133
  private
154
134
  def class_for(fragment, *args)
155
- item_class = class_from(fragment) or return fragment # DISCUSS: is it legal to return the very fragment here?
135
+ item_class = class_from(fragment) or return fragment
156
136
  item_class.new
157
137
  end
158
138
 
@@ -47,15 +47,14 @@ module Representable
47
47
  end
48
48
  end
49
49
 
50
-
51
50
  class CollectionBinding < PropertyBinding
52
51
  def serialize_for(value)
53
52
  # 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 { |item| serialize(item) }
53
+ value.collect { |item| serialize(item) } # TODO: i don't want Array but Forms here - what now?
55
54
  end
56
55
 
57
56
  def deserialize_from(fragment)
58
- fragment.collect { |item_fragment| deserialize(item_fragment) }
57
+ CollectionDeserializer.new(self).deserialize(fragment)
59
58
  end
60
59
  end
61
60
 
@@ -1,28 +1,29 @@
1
1
  require 'representable/binding'
2
+ require 'representable/bindings/hash_bindings.rb'
2
3
 
3
4
  module Representable
4
5
  module XML
5
6
  module ObjectBinding
6
7
  include Binding::Object
7
-
8
+
8
9
  def serialize_method
9
10
  :to_node
10
11
  end
11
-
12
+
12
13
  def deserialize_method
13
14
  :from_node
14
15
  end
15
-
16
- def deserialize_node(node)
17
- deserialize(node)
16
+
17
+ def content_for(node) # TODO: move to ScalarDecorator.
18
+ node
18
19
  end
19
-
20
+
20
21
  def serialize_node(node, value)
21
22
  serialize(value)
22
23
  end
23
24
  end
24
-
25
-
25
+
26
+
26
27
  class PropertyBinding < Binding
27
28
  def self.build_for(definition, *args)
28
29
  return CollectionBinding.new(definition, *args) if definition.array?
@@ -36,45 +37,41 @@ module Representable
36
37
  super
37
38
  extend ObjectBinding if typed? # FIXME.
38
39
  end
39
-
40
+
40
41
  def write(parent, value)
41
42
  wrap_node = parent
42
-
43
+
43
44
  if wrap = options[:wrap]
44
45
  parent << wrap_node = node_for(parent, wrap)
45
46
  end
46
47
 
47
48
  wrap_node << serialize_for(value, parent)
48
49
  end
49
-
50
+
50
51
  def read(node)
51
52
  nodes = find_nodes(node)
52
53
  return FragmentNotFound if nodes.size == 0 # TODO: write dedicated test!
53
-
54
+
54
55
  deserialize_from(nodes)
55
56
  end
56
-
57
+
57
58
  # Creates wrapped node for the property.
58
59
  def serialize_for(value, parent)
59
60
  #def serialize_for(value, parent, tag_name=definition.from)
60
61
  node = node_for(parent, from)
61
62
  serialize_node(node, value)
62
63
  end
63
-
64
+
64
65
  def serialize_node(node, value)
65
66
  node.content = serialize(value)
66
67
  node
67
68
  end
68
-
69
+
69
70
  def deserialize_from(nodes)
70
- deserialize_node(nodes.first)
71
- end
72
-
73
- # DISCUSS: rename to #read_from ?
74
- def deserialize_node(node)
75
- deserialize(node.content)
71
+ content_for deserialize(nodes.first)
72
+ #deserialize(nodes.first)
76
73
  end
77
-
74
+
78
75
  private
79
76
  def xpath
80
77
  from
@@ -89,18 +86,25 @@ module Representable
89
86
  def node_for(parent, name)
90
87
  Nokogiri::XML::Node.new(name.to_s, parent.document)
91
88
  end
89
+
90
+ def content_for(node) # TODO: move this into a ScalarDecorator.
91
+ node.content
92
+ end
92
93
  end
93
-
94
+
94
95
  class CollectionBinding < PropertyBinding
95
96
  def serialize_for(value, parent)
96
97
  # return NodeSet so << works.
97
98
  set_for(parent, value.collect { |item| super(item, parent) })
98
99
  end
99
-
100
+
100
101
  def deserialize_from(nodes)
101
- nodes.collect do |item|
102
- deserialize_node(item)
102
+ content_nodes = nodes.collect do |item| # TODO: move this to Node?
103
+ content_for(item)
103
104
  end
105
+
106
+ # *Deserializer doesn't want anything format specific!
107
+ CollectionDeserializer.new(self).deserialize(content_nodes)
104
108
  end
105
109
 
106
110
  private
@@ -108,8 +112,8 @@ module Representable
108
112
  Nokogiri::XML::NodeSet.new(parent.document, nodes)
109
113
  end
110
114
  end
111
-
112
-
115
+
116
+
113
117
  class HashBinding < CollectionBinding
114
118
  def serialize_for(value, parent)
115
119
  set_for(parent, value.collect do |k, v|
@@ -117,16 +121,16 @@ module Representable
117
121
  serialize_node(node, v)
118
122
  end)
119
123
  end
120
-
124
+
121
125
  def deserialize_from(nodes)
122
126
  {}.tap do |hash|
123
127
  nodes.children.each do |node|
124
- hash[node.name] = deserialize_node(node)
128
+ hash[node.name] = deserialize(content_for node)
125
129
  end
126
130
  end
127
131
  end
128
132
  end
129
-
133
+
130
134
  class AttributeHashBinding < CollectionBinding
131
135
  # DISCUSS: use AttributeBinding here?
132
136
  def write(parent, value) # DISCUSS: is it correct overriding #write here?
@@ -135,7 +139,7 @@ module Representable
135
139
  end
136
140
  parent
137
141
  end
138
-
142
+
139
143
  def deserialize_from(node)
140
144
  {}.tap do |hash|
141
145
  node.each do |k,v|
@@ -144,18 +148,18 @@ module Representable
144
148
  end
145
149
  end
146
150
  end
147
-
148
-
151
+
152
+
149
153
  # Represents a tag attribute. Currently this only works on the top-level tag.
150
154
  class AttributeBinding < PropertyBinding
151
155
  def read(node)
152
156
  deserialize(node[from])
153
157
  end
154
-
158
+
155
159
  def serialize_for(value, parent)
156
160
  parent[from] = serialize(value.to_s)
157
161
  end
158
-
162
+
159
163
  def write(parent, value)
160
164
  serialize_for(value, parent)
161
165
  end
@@ -4,11 +4,11 @@ module Representable
4
4
  module YAML
5
5
  module ObjectBinding
6
6
  include Binding::Object
7
-
7
+
8
8
  def serialize_method
9
9
  :to_ast
10
10
  end
11
-
11
+
12
12
  def deserialize_method
13
13
  :from_hash
14
14
  end
@@ -28,7 +28,7 @@ module Representable
28
28
  super
29
29
  extend ObjectBinding if typed?
30
30
  end
31
-
31
+
32
32
  def write(map, value)
33
33
  map.children << Psych::Nodes::Scalar.new(from)
34
34
  map.children << serialize_for(value) # FIXME: should be serialize.
@@ -42,8 +42,8 @@ module Representable
42
42
  Psych::Nodes::Scalar.new(value.to_s)
43
43
  end
44
44
  end
45
-
46
-
45
+
46
+
47
47
  class CollectionBinding < PropertyBinding
48
48
  def serialize_for(value)
49
49
  Psych::Nodes::Sequence.new.tap do |seq|
@@ -51,9 +51,9 @@ module Representable
51
51
  value.each { |obj| seq.children << super(obj) }
52
52
  end
53
53
  end
54
-
54
+
55
55
  def deserialize_from(fragment) # FIXME: redundant from Hash::Bindings
56
- fragment.collect { |item_fragment| deserialize(item_fragment) }
56
+ CollectionDeserializer.new(self).deserialize(fragment)
57
57
  end
58
58
  end
59
59
  end
@@ -1,6 +1,6 @@
1
1
  module Representable
2
2
  # NOTE: the API of Config is subject to change so don't rely too much on this private object.
3
- class Config < Array
3
+ class Config < Hash
4
4
  # DISCUSS: experimental. this will soon be moved to a separate gem
5
5
  module InheritableArray
6
6
  def inheritable_array(name)
@@ -19,6 +19,17 @@ module Representable
19
19
  end
20
20
  end
21
21
 
22
+ def <<(definition)
23
+ self[definition.name] = definition
24
+ end
25
+
26
+ def [](name)
27
+ fetch(name.to_s, nil)
28
+ end
29
+
30
+ def each(*args, &block)
31
+ values.each(*args, &block)
32
+ end
22
33
 
23
34
  attr_accessor :wrap
24
35
 
@@ -30,18 +41,22 @@ module Representable
30
41
  end
31
42
 
32
43
  module InheritMethods
33
- def clone
34
- self.class.new(collect { |d| d.clone })
44
+ def cloned
45
+ collect { |d| d.clone }
35
46
  end
36
47
 
37
48
  def inherit(parent)
38
- push(*parent.clone)
49
+ push(parent.cloned)
39
50
  end
40
51
  end
41
52
  include InheritMethods
42
53
  include InheritableArray # overrides #inherit.
43
54
 
44
55
  private
56
+ def push(defs)
57
+ defs.each { |d| self << d }
58
+ end
59
+
45
60
  def infer_name_for(name)
46
61
  name.to_s.split('::').last.
47
62
  gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
@@ -72,5 +72,9 @@ module Representable
72
72
  def create_binding(*args)
73
73
  binding.call(self, *args)
74
74
  end
75
+
76
+ def sync?
77
+ options[:parse_strategy] == :sync
78
+ end
75
79
  end
76
80
  end
@@ -0,0 +1,63 @@
1
+ module Representable
2
+ class CollectionDeserializer < Array # always is the targeted collection, already.
3
+ def initialize(binding) # TODO: get rid of binding dependency
4
+ # next step: use #get always.
5
+ @binding = binding
6
+ collection = []
7
+ # should be call to #default:
8
+ collection = binding.get if binding.sync?
9
+
10
+ super collection
11
+ end
12
+
13
+ def deserialize(fragment)
14
+ # next step: get rid of collect.
15
+ fragment.enum_for(:each_with_index).collect { |item_fragment, i|
16
+ @deserializer = ObjectDeserializer.new(@binding, lambda { self[i] })
17
+
18
+ @deserializer.call(item_fragment) # FIXME: what if obj nil?
19
+ }
20
+ end
21
+ end
22
+
23
+
24
+ class ObjectDeserializer
25
+ # dependencies: Def#options, Def#create_object, Def#get
26
+ def initialize(binding, object)
27
+ @binding = binding
28
+ @object = object
29
+ end
30
+
31
+ def call(fragment)
32
+ # TODO: this used to be handled in #serialize where Object added it's behaviour. treat scalars as objects to remove this switch:
33
+ return fragment unless @binding.typed?
34
+
35
+ if @binding.sync?
36
+ # TODO: this is also done when instance: { nil }
37
+ @object = @object.call # call Binding#get or Binding#get[i]
38
+ else
39
+ @object = @binding.create_object(fragment)
40
+ end
41
+
42
+ # DISCUSS: what parts should be in this class, what in Binding?
43
+ representable = prepare(@object)
44
+ deserialize(representable, fragment, @binding.user_options)
45
+ #yield @object
46
+ end
47
+
48
+ private
49
+ def deserialize(object, fragment, options)
50
+ object.send(@binding.deserialize_method, fragment, options)
51
+ end
52
+
53
+ def prepare(object)
54
+ mod = @binding.representer_module_for(object)
55
+
56
+ return object unless mod
57
+
58
+ mod = mod.first if mod.is_a?(Array) # TODO: deprecate :extend => [..]
59
+ mod.prepare(object)
60
+ end
61
+ # in deserialize, we should get the original object?
62
+ end
63
+ end