representable 2.0.4 → 2.1.0

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