representable 2.0.4 → 2.1.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.
@@ -1,5 +1,5 @@
1
1
  require 'representable'
2
- require 'representable/bindings/hash_bindings'
2
+ require 'representable/hash/binding'
3
3
 
4
4
  module Representable
5
5
  # The generic representer. Brings #to_hash and #from_hash to your object.
@@ -25,13 +25,13 @@ module Representable
25
25
  # Note: `#from_hash` still does _not_ stringify incoming hashes. This is per design: Representable is not made for hashes, only,
26
26
  # but for any arbitrary data structure. A generic `key.to_s` with non-hash data would result in weird issues.
27
27
  # I decided it's more predictable to require the user to provide stringified keys.
28
- def from_hash(data, options={}, binding_builder=PropertyBinding)
28
+ def from_hash(data, options={}, binding_builder=Binding)
29
29
  data = filter_wrap(data, options)
30
30
 
31
31
  update_properties_from(data, options, binding_builder)
32
32
  end
33
33
 
34
- def to_hash(options={}, binding_builder=PropertyBinding)
34
+ def to_hash(options={}, binding_builder=Binding)
35
35
  hash = create_representation_with({}, options, binding_builder)
36
36
 
37
37
  return hash unless wrap = options[:wrap] || representation_wrap(options)
@@ -0,0 +1,40 @@
1
+ require 'representable/binding'
2
+
3
+ module Representable
4
+ module Hash
5
+ class Binding < Representable::Binding
6
+ def self.build_for(definition, *args) # TODO: remove default arg.
7
+ return Collection.new(definition, *args) if definition.array?
8
+ return Hash.new(definition, *args) if definition.hash?
9
+ new(definition, *args)
10
+ end
11
+
12
+ def read(hash)
13
+ return FragmentNotFound unless hash.has_key?(as) # DISCUSS: put it all in #read for performance. not really sure if i like returning that special thing.
14
+
15
+ hash[as] # fragment
16
+ end
17
+
18
+ def write(hash, fragment)
19
+ hash[as] = fragment
20
+ end
21
+
22
+ def serialize_method
23
+ :to_hash
24
+ end
25
+
26
+ def deserialize_method
27
+ :from_hash
28
+ end
29
+
30
+ class Collection < self
31
+ include Representable::Binding::Collection
32
+ end
33
+
34
+
35
+ class Hash < self
36
+ include Representable::Binding::Hash
37
+ end
38
+ end
39
+ end
40
+ end
@@ -20,12 +20,13 @@ module Representable::Hash
20
20
 
21
21
  def create_representation_with(doc, options, format)
22
22
  bin = representable_mapper(format, options).bindings.first
23
- bin.write(doc, represented)
23
+ bin.render_fragment(represented, doc)
24
24
  end
25
25
 
26
26
  def update_properties_from(doc, options, format)
27
27
  bin = representable_mapper(format, options).bindings.first
28
- value = bin.deserialize_from(doc)
28
+ #value = bin.deserialize_from(doc)
29
+ value = Deserializer::Collection.new(bin).call(doc)
29
30
  represented.replace(value)
30
31
  end
31
32
  end
@@ -3,13 +3,15 @@ module Representable
3
3
  def create_representation_with(doc, options, format)
4
4
  bin = representable_mapper(format, options).bindings.first
5
5
  hash = filter_keys_for(represented, options)
6
- bin.write(doc, hash)
6
+ bin.render_fragment(hash, doc) # TODO: Use something along Populator, which does
7
7
  end
8
8
 
9
9
  def update_properties_from(doc, options, format)
10
10
  bin = representable_mapper(format, options).bindings.first
11
11
  hash = filter_keys_for(doc, options)
12
- value = bin.deserialize_from(hash)
12
+
13
+ value = Deserializer::Hash.new(bin).call(hash)
14
+ # value = bin.deserialize_from(hash)
13
15
  represented.replace(value)
14
16
  end
15
17
 
@@ -52,7 +52,7 @@ module Representable
52
52
  def skip_conditional_property?(binding)
53
53
  return unless condition = binding[:if]
54
54
 
55
- not binding.send(:evaluate_option, :if)
55
+ not binding.evaluate_option(:if)
56
56
  end
57
57
 
58
58
  # DISCUSS: this could be just another :if option in a Pipeline?
@@ -0,0 +1,59 @@
1
+ module Representable
2
+ #
3
+ # populator
4
+ # skip_parse? --> return
5
+ # deserialize (this is where additional logic can happen, e.g. Object-HAL's collection semantics).
6
+ # parse_filter
7
+ # set
8
+ class Populator # rename to Deserializer?
9
+ def initialize(binding)
10
+ @binding = binding
11
+ end
12
+
13
+ # goal of this is to have this workflow apply-able to collections AND to items per collection, or for items in hashes.
14
+ def call(fragment, doc)
15
+ # the rest should be applied per item (collection) or per fragment (collection and property)
16
+ if fragment == Binding::FragmentNotFound
17
+ return unless @binding.has_default?
18
+ value = @binding[:default]
19
+ else
20
+ value = deserialize(fragment) { return } # stop here if skip_parse?
21
+ end
22
+
23
+ value = @binding.parse_filter(value, doc)
24
+ # parse_filter
25
+ # set
26
+ @binding.set(value)
27
+ end
28
+
29
+ private
30
+ def deserialize(fragment)
31
+ return yield if @binding.evaluate_option(:skip_parse, fragment) # TODO: move this into Deserializer.
32
+
33
+ # use a Deserializer to transform fragment to/into object.
34
+ deserializer_class.new(@binding).call(fragment) # CollectionDeserializer/HashDeserializer/etc.
35
+ end
36
+
37
+ def deserializer_class
38
+ Deserializer
39
+ end
40
+
41
+
42
+ # A separated collection deserializer/populator allows us better dealing with populating/modifying
43
+ # collections of models. (e.g. replace, update, push, etc.).
44
+ # That also gives us a place to apply options like :parse_filter, etc. per item.
45
+ class Collection < self
46
+ private
47
+ def deserialize(fragment)
48
+ return Deserializer::Collection.new(@binding).call(fragment)
49
+ end
50
+ end
51
+
52
+ class Hash < self
53
+ private
54
+ def deserializer_class
55
+ Deserializer::Hash
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,28 +1,39 @@
1
1
  require "representable/deserializer"
2
2
 
3
3
  module Representable
4
- class ObjectSerializer < ObjectDeserializer
5
- def initialize(binding, object)
6
- super(binding)
7
- @object = object
8
- end
9
-
10
- def call
11
- # return unless @binding.typed? # FIXME: fix that in XML/YAML.
12
- return @object if @object.nil? # DISCUSS: move to Object#serialize ?
13
-
14
- representable = prepare(@object)
4
+ class Serializer < Deserializer
5
+ def call(object)
6
+ return object if object.nil? # DISCUSS: move to Object#serialize ?
15
7
 
16
- serialize(representable, @binding.user_options)
8
+ serialize(object, @binding.user_options)
17
9
  end
18
10
 
19
11
  private
12
+ # Serialize one object by calling to_json etc. on it.
20
13
  def serialize(object, user_options)
14
+ object = prepare(object)
15
+
21
16
  return object unless @binding.representable?
22
17
 
23
- @binding.send(:evaluate_option, :serialize, object) do
18
+ @binding.evaluate_option(:serialize, object) do
24
19
  object.send(@binding.serialize_method, user_options.merge!({:wrap => false}))
25
20
  end
26
21
  end
22
+
23
+
24
+ class Collection < self
25
+ def serialize(array, *args)
26
+ array.collect { |item| super(item, *args) } # TODO: i don't want Array but Forms here - what now?
27
+ end
28
+ end
29
+
30
+
31
+ class Hash < self
32
+ def serialize(hash, *args)
33
+ {}.tap do |hsh|
34
+ hash.each { |key, obj| hsh[key] = super(obj, *args) }
35
+ end
36
+ end
37
+ end
27
38
  end
28
39
  end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "2.0.4"
2
+ VERSION = "2.1.0"
3
3
  end
@@ -1,5 +1,5 @@
1
1
  require 'representable'
2
- require 'representable/bindings/xml_bindings'
2
+ require 'representable/xml/binding'
3
3
  require 'representable/xml/collection'
4
4
  require 'nokogiri'
5
5
 
@@ -32,7 +32,7 @@ module Representable
32
32
  end
33
33
 
34
34
  def from_node(node, options={})
35
- update_properties_from(node, options, PropertyBinding)
35
+ update_properties_from(node, options, Binding)
36
36
  end
37
37
 
38
38
  # Returns a Nokogiri::XML object representing this object.
@@ -40,7 +40,7 @@ module Representable
40
40
  options[:doc] ||= Nokogiri::XML::Document.new
41
41
  root_tag = options[:wrap] || representation_wrap(options)
42
42
 
43
- create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, options[:doc]), options, PropertyBinding)
43
+ create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, options[:doc]), options, Binding)
44
44
  end
45
45
 
46
46
  def to_xml(*args)
@@ -0,0 +1,171 @@
1
+ require 'representable/binding'
2
+ require 'representable/hash/binding.rb'
3
+
4
+ module Representable
5
+ module XML
6
+ class Binding < Representable::Binding
7
+ def self.build_for(definition, *args)
8
+ return Collection.new(definition, *args) if definition.array?
9
+ return Hash.new(definition, *args) if definition.hash? and not definition[:use_attributes] # FIXME: hate this.
10
+ return AttributeHash.new(definition, *args) if definition.hash? and definition[:use_attributes]
11
+ return Attribute.new(definition, *args) if definition[:attribute]
12
+ return Content.new(definition, *args) if definition[:content]
13
+ new(definition, *args)
14
+ end
15
+
16
+ def write(parent, fragments)
17
+ wrap_node = parent
18
+
19
+ if wrap = self[:wrap]
20
+ parent << wrap_node = node_for(parent, wrap)
21
+ end
22
+
23
+ wrap_node << serialize_for(fragments, parent)
24
+ end
25
+
26
+ def read(node)
27
+ nodes = find_nodes(node)
28
+ return FragmentNotFound if nodes.size == 0 # TODO: write dedicated test!
29
+
30
+ deserialize_from(nodes)
31
+ end
32
+
33
+ # Creates wrapped node for the property.
34
+ def serialize_for(value, parent)
35
+ node = node_for(parent, as)
36
+ serialize_node(node, value)
37
+ end
38
+
39
+ def serialize_node(node, value)
40
+ return value if typed?
41
+
42
+ node.content = value
43
+ node
44
+ end
45
+
46
+ def deserialize_from(nodes)
47
+ content_for(nodes.first)
48
+ end
49
+
50
+ # DISCUSS: why is this public?
51
+ def serialize_method
52
+ :to_node
53
+ end
54
+
55
+ def deserialize_method
56
+ :from_node
57
+ end
58
+
59
+ private
60
+ def xpath
61
+ as
62
+ end
63
+
64
+ def find_nodes(doc)
65
+ selector = xpath
66
+ selector = "#{self[:wrap]}/#{xpath}" if self[:wrap]
67
+ nodes = doc.xpath(selector)
68
+ end
69
+
70
+ def node_for(parent, name)
71
+ Nokogiri::XML::Node.new(name.to_s, parent.document)
72
+ end
73
+
74
+ def content_for(node) # TODO: move this into a ScalarDecorator.
75
+ return node if typed?
76
+
77
+ node.content
78
+ end
79
+
80
+
81
+ class Collection < self
82
+ include Representable::Binding::Collection
83
+
84
+ def serialize_for(value, parent)
85
+ # return NodeSet so << works.
86
+ set_for(parent, value.collect { |item| super(item, parent) })
87
+ end
88
+
89
+ def deserialize_from(nodes)
90
+ content_nodes = nodes.collect do |item| # TODO: move this to Node?
91
+ content_for(item)
92
+ end
93
+
94
+ content_nodes
95
+ end
96
+
97
+ private
98
+ def set_for(parent, nodes)
99
+ Nokogiri::XML::NodeSet.new(parent.document, nodes)
100
+ end
101
+ end
102
+
103
+
104
+ class Hash < Collection
105
+ include Representable::Binding::Hash
106
+
107
+ def serialize_for(value, parent)
108
+ set_for(parent, value.collect do |k, v|
109
+ node = node_for(parent, k)
110
+ serialize_node(node, v)
111
+ end)
112
+ end
113
+
114
+ def deserialize_from(nodes)
115
+ hash = {}
116
+ nodes.children.each do |node|
117
+ hash[node.name] = content_for node
118
+ end
119
+
120
+ hash
121
+ end
122
+ end
123
+
124
+ class AttributeHash < Collection
125
+ # DISCUSS: use AttributeBinding here?
126
+ def write(parent, value) # DISCUSS: is it correct overriding #write here?
127
+ value.collect do |k, v|
128
+ parent[k] = v.to_s
129
+ end
130
+ parent
131
+ end
132
+
133
+ # FIXME: this is not tested!
134
+ def deserialize_from(node)
135
+ HashDeserializer.new(self).deserialize(node)
136
+ end
137
+ end
138
+
139
+
140
+ # Represents a tag attribute. Currently this only works on the top-level tag.
141
+ class Attribute < self
142
+ def read(node)
143
+ node[as]
144
+ end
145
+
146
+ def serialize_for(value, parent)
147
+ parent[as] = value.to_s
148
+ end
149
+
150
+ def write(parent, value)
151
+ serialize_for(value, parent)
152
+ end
153
+ end
154
+
155
+ # Represents tag content.
156
+ class Content < self
157
+ def read(node)
158
+ node.content
159
+ end
160
+
161
+ def serialize_for(value, parent)
162
+ parent.content = value.to_s
163
+ end
164
+
165
+ def write(parent, value)
166
+ serialize_for(value, parent)
167
+ end
168
+ end
169
+ end # Binding
170
+ end
171
+ end
@@ -1,4 +1,4 @@
1
- require 'representable/bindings/yaml_bindings'
1
+ require 'representable/yaml/binding'
2
2
 
3
3
  module Representable
4
4
  module YAML
@@ -15,7 +15,7 @@ module Representable
15
15
 
16
16
  def from_yaml(doc, options={})
17
17
  hash = Psych.load(doc)
18
- from_hash(hash, options, PropertyBinding)
18
+ from_hash(hash, options, Binding)
19
19
  end
20
20
 
21
21
  # Returns a Nokogiri::XML object representing this object.
@@ -23,7 +23,7 @@ module Representable
23
23
  #root_tag = options[:wrap] || representation_wrap
24
24
 
25
25
  Psych::Nodes::Mapping.new.tap do |map|
26
- create_representation_with(map, options, PropertyBinding)
26
+ create_representation_with(map, options, Binding)
27
27
  end
28
28
  end
29
29