representable 1.6.1 → 1.7.0

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: 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