nokogiri-happymapper 0.5.6 → 0.5.7
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 +15 -0
- data/CHANGELOG.md +5 -1
- data/README.md +74 -53
- data/lib/happymapper/anonymous_mapper.rb +114 -0
- data/lib/happymapper/attribute.rb +20 -2
- data/lib/happymapper/element.rb +52 -2
- data/lib/happymapper/item.rb +89 -182
- data/lib/happymapper/supported_types.rb +140 -0
- data/lib/happymapper/text_node.rb +6 -1
- data/lib/happymapper/version.rb +3 -0
- data/lib/happymapper.rb +42 -22
- data/spec/attribute_default_value_spec.rb +50 -0
- data/spec/fixtures/default_namespace_combi.xml +2 -1
- data/spec/happymapper/attribute_spec.rb +12 -0
- data/spec/happymapper/element_spec.rb +9 -0
- data/spec/{happymapper_item_spec.rb → happymapper/item_spec.rb} +5 -5
- data/spec/happymapper/text_node_spec.rb +9 -0
- data/spec/happymapper_parse_spec.rb +87 -0
- data/spec/happymapper_spec.rb +9 -3
- data/spec/ignay_spec.rb +22 -22
- data/spec/inheritance_spec.rb +61 -0
- data/spec/parse_with_object_to_update_spec.rb +111 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/to_xml_spec.rb +200 -0
- data/spec/to_xml_with_namespaces_spec.rb +196 -0
- data/spec/wilcard_tag_name_spec.rb +96 -0
- data/spec/wrap_spec.rb +82 -0
- data/spec/xpath_spec.rb +60 -59
- metadata +34 -33
- data/TODO +0 -0
- data/spec/happymapper_attribute_spec.rb +0 -21
- data/spec/happymapper_element_spec.rb +0 -21
- data/spec/happymapper_generic_base_spec.rb +0 -92
- data/spec/happymapper_text_node_spec.rb +0 -21
- data/spec/happymapper_to_xml_namespaces_spec.rb +0 -196
- data/spec/happymapper_to_xml_spec.rb +0 -203
- data/spec/happymapper_wrap_spec.rb +0 -69
- data/spec/parse_instance_spec.rb +0 -129
data/lib/happymapper/item.rb
CHANGED
@@ -2,8 +2,6 @@ module HappyMapper
|
|
2
2
|
class Item
|
3
3
|
attr_accessor :name, :type, :tag, :options, :namespace
|
4
4
|
|
5
|
-
Types = [String, Float, Time, Date, DateTime, Integer, Boolean]
|
6
|
-
|
7
5
|
# options:
|
8
6
|
# :deep => Boolean False to only parse element's children, True to include
|
9
7
|
# grandchildren and all others down the chain (// in xpath)
|
@@ -26,56 +24,24 @@ module HappyMapper
|
|
26
24
|
def constant
|
27
25
|
@constant ||= constantize(type)
|
28
26
|
end
|
29
|
-
|
27
|
+
|
30
28
|
#
|
31
29
|
# @param [XMLNode] node the xml node that is being parsed
|
32
30
|
# @param [String] namespace the name of the namespace
|
33
31
|
# @param [Hash] xpath_options additional xpath options
|
34
|
-
#
|
32
|
+
#
|
35
33
|
def from_xml_node(node, namespace, xpath_options)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# else the type, specified, needs to handle the parsing.
|
40
|
-
#
|
41
|
-
|
42
|
-
if primitive?
|
43
|
-
find(node, namespace, xpath_options) do |n|
|
44
|
-
if n.respond_to?(:content)
|
45
|
-
typecast(n.content)
|
46
|
-
else
|
47
|
-
typecast(n)
|
48
|
-
end
|
49
|
-
end
|
34
|
+
|
35
|
+
if suported_type_registered?
|
36
|
+
find(node, namespace, xpath_options) { |n| process_node_as_supported_type(n) }
|
50
37
|
elsif constant == XmlContent
|
51
|
-
find(node, namespace, xpath_options)
|
52
|
-
|
53
|
-
|
54
|
-
end
|
38
|
+
find(node, namespace, xpath_options) { |n| process_node_as_xml_content(n) }
|
39
|
+
elsif custom_parser_defined?
|
40
|
+
find(node, namespace, xpath_options) { |n| process_node_with_custom_parser(n) }
|
55
41
|
else
|
56
|
-
|
57
|
-
# When not a primitive type or XMLContent then default to using the
|
58
|
-
# class method #parse of the type class. If the option 'parser' has been
|
59
|
-
# defined then call that method on the type class instead of #parse
|
60
|
-
|
61
|
-
if options[:parser]
|
62
|
-
find(node, namespace, xpath_options) do |n|
|
63
|
-
if n.respond_to?(:content) && !options[:raw]
|
64
|
-
value = n.content
|
65
|
-
else
|
66
|
-
value = n.to_s
|
67
|
-
end
|
68
|
-
|
69
|
-
begin
|
70
|
-
constant.send(options[:parser].to_sym, value)
|
71
|
-
rescue
|
72
|
-
nil
|
73
|
-
end
|
74
|
-
end
|
75
|
-
else
|
76
|
-
constant.parse(node, options.merge(:namespaces => xpath_options))
|
77
|
-
end
|
42
|
+
process_node_with_default_parser(node,:namespaces => xpath_options)
|
78
43
|
end
|
44
|
+
|
79
45
|
end
|
80
46
|
|
81
47
|
def xpath(namespace = self.namespace)
|
@@ -86,166 +52,107 @@ module HappyMapper
|
|
86
52
|
#puts "xpath: #{xpath}"
|
87
53
|
xpath
|
88
54
|
end
|
89
|
-
|
90
|
-
# @return [Boolean] true if the type defined for the item is defined in the
|
91
|
-
# list of primite types {Types}.
|
92
|
-
def primitive?
|
93
|
-
Types.include?(constant)
|
94
|
-
end
|
95
|
-
|
96
|
-
def element?
|
97
|
-
@xml_type == 'element'
|
98
|
-
end
|
99
|
-
|
100
|
-
def attribute?
|
101
|
-
@xml_type == 'attribute'
|
102
|
-
end
|
103
|
-
|
104
|
-
def text_node?
|
105
|
-
@xml_type == 'textnode'
|
106
|
-
end
|
107
55
|
|
108
56
|
def method_name
|
109
57
|
@method_name ||= name.tr('-', '_')
|
110
58
|
end
|
111
59
|
|
112
60
|
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
# return the original String value.
|
116
|
-
#
|
61
|
+
# Convert the value into the correct type.
|
62
|
+
#
|
117
63
|
# @param [String] value the string value parsed from the XML value that will
|
118
64
|
# be converted to the particular primitive type.
|
119
|
-
#
|
65
|
+
#
|
120
66
|
# @return [String,Float,Time,Date,DateTime,Boolean,Integer] the converted value
|
121
67
|
# to the new type.
|
122
68
|
#
|
123
69
|
def typecast(value)
|
124
|
-
|
125
|
-
begin
|
126
|
-
if constant == String then value.to_s
|
127
|
-
elsif constant == Float then value.to_f
|
128
|
-
elsif constant == Time then Time.parse(value.to_s) rescue Time.at(value.to_i)
|
129
|
-
elsif constant == Date then Date.parse(value.to_s)
|
130
|
-
elsif constant == DateTime then DateTime.parse(value.to_s)
|
131
|
-
elsif constant == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
|
132
|
-
elsif constant == Integer
|
133
|
-
# ganked from datamapper
|
134
|
-
value_to_i = value.to_i
|
135
|
-
if value_to_i == 0 && value != '0'
|
136
|
-
value_to_s = value.to_s
|
137
|
-
begin
|
138
|
-
Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
|
139
|
-
rescue ArgumentError
|
140
|
-
nil
|
141
|
-
end
|
142
|
-
else
|
143
|
-
value_to_i
|
144
|
-
end
|
145
|
-
else
|
146
|
-
value
|
147
|
-
end
|
148
|
-
rescue
|
149
|
-
value
|
150
|
-
end
|
70
|
+
typecaster(value).apply(value)
|
151
71
|
end
|
152
72
|
|
73
|
+
|
153
74
|
private
|
154
75
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
76
|
+
# @return [Boolean] true if the type defined for the item is defined in the
|
77
|
+
# list of support types.
|
78
|
+
def suported_type_registered?
|
79
|
+
SupportedTypes.types.map {|caster| caster.type }.include?(constant)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [#apply] the typecaster object that will be able to convert
|
83
|
+
# the value into a value with the correct type.
|
84
|
+
def typecaster(value)
|
85
|
+
SupportedTypes.types.find { |caster| caster.apply?(value,constant) }
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Processes a Nokogiri::XML::Node as a supported type
|
90
|
+
#
|
91
|
+
def process_node_as_supported_type(node)
|
92
|
+
content = node.respond_to?(:content) ? node.content : node
|
93
|
+
typecast(content)
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Process a Nokogiri::XML::Node as XML Content
|
98
|
+
#
|
99
|
+
def process_node_as_xml_content(node)
|
100
|
+
node = node.children if node.respond_to?(:children)
|
101
|
+
node.respond_to?(:to_xml) ? node.to_xml : node.to_s
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# A custom parser is a custom parse method on the class. When the parser
|
106
|
+
# option has been set this value is the name of the method which will be
|
107
|
+
# used to parse the node content.
|
108
|
+
#
|
109
|
+
def custom_parser_defined?
|
110
|
+
options[:parser]
|
111
|
+
end
|
112
|
+
|
113
|
+
def process_node_with_custom_parser(node)
|
114
|
+
if node.respond_to?(:content) && !options[:raw]
|
115
|
+
value = node.content
|
116
|
+
else
|
117
|
+
value = node.to_s
|
179
118
|
end
|
180
119
|
|
120
|
+
begin
|
121
|
+
constant.send(options[:parser].to_sym, value)
|
122
|
+
rescue
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
end
|
181
126
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
value
|
206
|
-
end
|
207
|
-
else
|
208
|
-
|
209
|
-
target_path = options[:xpath] ? options[:xpath] : xpath(namespace)
|
210
|
-
|
211
|
-
results = node.xpath(target_path, xpath_options).collect do |result|
|
212
|
-
value = yield(result)
|
213
|
-
handle_attributes_option(result, value, xpath_options)
|
214
|
-
value
|
215
|
-
end
|
216
|
-
results
|
217
|
-
end
|
218
|
-
elsif attribute?
|
219
|
-
|
220
|
-
if options[:xpath]
|
221
|
-
yield(node.xpath(options[:xpath],xpath_options))
|
127
|
+
def process_node_with_default_parser(node,parse_options)
|
128
|
+
constant.parse(node,options.merge(parse_options))
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Convert any String defined types into their constant version so that
|
133
|
+
# the method #parse or the custom defined parser method would be used.
|
134
|
+
#
|
135
|
+
# @param [String,Constant] type is the name of the class or the constant
|
136
|
+
# for the class.
|
137
|
+
# @return [Constant] the constant of the type
|
138
|
+
#
|
139
|
+
def constantize(type)
|
140
|
+
type.is_a?(String) ? convert_string_to_constant(type) : type
|
141
|
+
end
|
142
|
+
|
143
|
+
def convert_string_to_constant(type)
|
144
|
+
names = type.split('::')
|
145
|
+
constant = Object
|
146
|
+
names.each do |name|
|
147
|
+
constant =
|
148
|
+
if constant.const_defined?(name)
|
149
|
+
constant.const_get(name)
|
222
150
|
else
|
223
|
-
|
151
|
+
constant.const_missing(name)
|
224
152
|
end
|
225
|
-
|
226
|
-
else # text node
|
227
|
-
yield(node.children.detect{|c| c.text?})
|
228
|
-
end
|
229
153
|
end
|
154
|
+
constant
|
155
|
+
end
|
230
156
|
|
231
|
-
def handle_attributes_option(result, value, xpath_options)
|
232
|
-
if options[:attributes].is_a?(Hash)
|
233
|
-
result = result.first unless result.respond_to?(:attribute_nodes)
|
234
|
-
|
235
|
-
result.attribute_nodes.each do |xml_attribute|
|
236
|
-
if attribute_options = options[:attributes][xml_attribute.name.to_sym]
|
237
|
-
attribute_value = Attribute.new(xml_attribute.name.to_sym, *attribute_options).from_xml_node(result, namespace, xpath_options)
|
238
|
-
|
239
|
-
result.instance_eval <<-EOV
|
240
|
-
def value.#{xml_attribute.name}
|
241
|
-
#{attribute_value.inspect}
|
242
|
-
end
|
243
|
-
EOV
|
244
|
-
end # if attributes_options
|
245
|
-
end # attribute_nodes.each
|
246
|
-
end # if options[:attributes]
|
247
|
-
end # def handle...
|
248
|
-
|
249
|
-
# end private methods
|
250
157
|
end
|
251
158
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module HappyMapper
|
2
|
+
module SupportedTypes
|
3
|
+
extend self
|
4
|
+
|
5
|
+
#
|
6
|
+
# All of the registerd supported types that can be parsed.
|
7
|
+
#
|
8
|
+
# All types defined here are set through #register.
|
9
|
+
#
|
10
|
+
def types
|
11
|
+
@types ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# Add a new converter to the list of supported types. A converter
|
16
|
+
# is an object that adheres to the protocol which is defined with two
|
17
|
+
# methods #apply?(value,convert_to_type) and #apply(value).
|
18
|
+
#
|
19
|
+
# @example Defining a class that would process `nil` or values that have
|
20
|
+
# already been converted.
|
21
|
+
#
|
22
|
+
# class NilOrAlreadyConverted
|
23
|
+
# def apply?(value,convert_to_type)
|
24
|
+
# value.kind_of?(convert_to_type) || value.nil?
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# def apply(value)
|
28
|
+
# value
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
#
|
33
|
+
def register(type_converter)
|
34
|
+
types.push type_converter
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# An additional shortcut registration method that assumes that you want
|
39
|
+
# to perform a conversion on a specific type. A block is provided which
|
40
|
+
# is the operation to perform when #apply(value) has been called.
|
41
|
+
#
|
42
|
+
# @example Registering a DateTime parser
|
43
|
+
#
|
44
|
+
# HappyMapper::SupportedTypes.register_type DateTime do |value|
|
45
|
+
# DateTime.parse(value,to_s)
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
def register_type(type,&block)
|
49
|
+
register CastWhenType.new(type,&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Many of the conversions are based on type. When the type specified
|
54
|
+
# matches then perform the action specified in the specified block.
|
55
|
+
# If no block is provided the value is simply returned.
|
56
|
+
#
|
57
|
+
class CastWhenType
|
58
|
+
attr_reader :type
|
59
|
+
|
60
|
+
def initialize(type,&block)
|
61
|
+
@type = type
|
62
|
+
@apply_block = block || no_operation
|
63
|
+
end
|
64
|
+
|
65
|
+
def no_operation
|
66
|
+
lambda {|value| value }
|
67
|
+
end
|
68
|
+
|
69
|
+
def apply?(value,convert_to_type)
|
70
|
+
convert_to_type == type
|
71
|
+
end
|
72
|
+
|
73
|
+
def apply(value)
|
74
|
+
@apply_block.call(value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# For the cases when the value is nil or is already the
|
80
|
+
# intended type then no work needs to be done and the
|
81
|
+
# value simply can be returned.
|
82
|
+
#
|
83
|
+
class NilOrAlreadyConverted
|
84
|
+
|
85
|
+
def type
|
86
|
+
NilClass
|
87
|
+
end
|
88
|
+
|
89
|
+
def apply?(value,convert_to_type)
|
90
|
+
value.kind_of?(convert_to_type) || value.nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
def apply(value)
|
94
|
+
value
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
register NilOrAlreadyConverted.new
|
99
|
+
|
100
|
+
register_type String do |value|
|
101
|
+
value.to_s
|
102
|
+
end
|
103
|
+
|
104
|
+
register_type Float do |value|
|
105
|
+
value.to_f
|
106
|
+
end
|
107
|
+
|
108
|
+
register_type Time do |value|
|
109
|
+
Time.parse(value.to_s) rescue Time.at(value.to_i)
|
110
|
+
end
|
111
|
+
|
112
|
+
register_type Date do |value|
|
113
|
+
Date.parse(value.to_s)
|
114
|
+
end
|
115
|
+
|
116
|
+
register_type DateTime do |value|
|
117
|
+
DateTime.parse(value.to_s)
|
118
|
+
end
|
119
|
+
|
120
|
+
register_type Boolean do |value|
|
121
|
+
['true', 't', '1'].include?(value.to_s.downcase)
|
122
|
+
end
|
123
|
+
|
124
|
+
register_type Integer do |value|
|
125
|
+
value_to_i = value.to_i
|
126
|
+
if value_to_i == 0 && value != '0'
|
127
|
+
value_to_s = value.to_s
|
128
|
+
begin
|
129
|
+
Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
|
130
|
+
rescue ArgumentError
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
else
|
134
|
+
value_to_i
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
data/lib/happymapper.rb
CHANGED
@@ -1,21 +1,34 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
require 'date'
|
3
3
|
require 'time'
|
4
|
-
|
5
|
-
class Boolean; end
|
6
|
-
class XmlContent; end
|
4
|
+
require 'happymapper/anonymous_mapper'
|
7
5
|
|
8
6
|
module HappyMapper
|
7
|
+
class Boolean; end
|
8
|
+
class XmlContent; end
|
9
9
|
|
10
|
-
|
10
|
+
extend AnonymousMapper
|
11
11
|
|
12
12
|
DEFAULT_NS = "happymapper"
|
13
13
|
|
14
14
|
def self.included(base)
|
15
|
-
base.
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
if !(base.superclass <= HappyMapper)
|
16
|
+
base.instance_eval do
|
17
|
+
@attributes = []
|
18
|
+
@elements = []
|
19
|
+
@registered_namespaces = {}
|
20
|
+
@wrapper_anonymous_classes = {}
|
21
|
+
end
|
22
|
+
else
|
23
|
+
base.instance_eval do
|
24
|
+
@attributes = superclass.attributes.dup
|
25
|
+
@elements = superclass.elements.dup
|
26
|
+
@registered_namespaces =
|
27
|
+
superclass.instance_variable_get(:@registered_namespaces).dup
|
28
|
+
@wrapper_anonymous_classes =
|
29
|
+
superclass.instance_variable_get(:@wrapper_anonymous_classes).dup
|
30
|
+
end
|
31
|
+
end
|
19
32
|
|
20
33
|
base.extend ClassMethods
|
21
34
|
end
|
@@ -39,8 +52,7 @@ module HappyMapper
|
|
39
52
|
#
|
40
53
|
def attribute(name, type, options={})
|
41
54
|
attribute = Attribute.new(name, type, options)
|
42
|
-
@attributes
|
43
|
-
@attributes[to_s] << attribute
|
55
|
+
@attributes << attribute
|
44
56
|
attr_accessor attribute.method_name.intern
|
45
57
|
end
|
46
58
|
|
@@ -51,7 +63,7 @@ module HappyMapper
|
|
51
63
|
# an empty array is returned when there have been no attributes defined.
|
52
64
|
#
|
53
65
|
def attributes
|
54
|
-
@attributes
|
66
|
+
@attributes
|
55
67
|
end
|
56
68
|
|
57
69
|
#
|
@@ -95,8 +107,7 @@ module HappyMapper
|
|
95
107
|
#
|
96
108
|
def element(name, type, options={})
|
97
109
|
element = Element.new(name, type, options)
|
98
|
-
@elements
|
99
|
-
@elements[to_s] << element
|
110
|
+
@elements << element
|
100
111
|
attr_accessor element.method_name.intern
|
101
112
|
end
|
102
113
|
|
@@ -108,7 +119,7 @@ module HappyMapper
|
|
108
119
|
# defined.
|
109
120
|
#
|
110
121
|
def elements
|
111
|
-
@elements
|
122
|
+
@elements
|
112
123
|
end
|
113
124
|
|
114
125
|
#
|
@@ -305,9 +316,7 @@ module HappyMapper
|
|
305
316
|
namespace = options[:namespace]
|
306
317
|
elsif namespaces.has_key?("xmlns")
|
307
318
|
namespace ||= DEFAULT_NS
|
308
|
-
|
309
|
-
namespaces[namespace] ||= default_namespace
|
310
|
-
namespaces["xmlns:#{namespaces.key(default_namespace)}"] = default_namespace
|
319
|
+
namespaces[DEFAULT_NS] = namespaces.delete("xmlns")
|
311
320
|
elsif namespaces.has_key?(DEFAULT_NS)
|
312
321
|
namespace ||= DEFAULT_NS
|
313
322
|
end
|
@@ -372,7 +381,9 @@ module HappyMapper
|
|
372
381
|
obj = options[:update] ? options[:update] : new
|
373
382
|
|
374
383
|
attributes.each do |attr|
|
375
|
-
|
384
|
+
value = attr.from_xml_node(n, namespace, namespaces)
|
385
|
+
value = attr.default if value.nil?
|
386
|
+
obj.send("#{attr.method_name}=", value)
|
376
387
|
end
|
377
388
|
|
378
389
|
elements.each do |elem|
|
@@ -431,7 +442,14 @@ module HappyMapper
|
|
431
442
|
collection
|
432
443
|
end
|
433
444
|
end
|
445
|
+
end
|
434
446
|
|
447
|
+
# Set all attributes with a default to their default values
|
448
|
+
def initialize
|
449
|
+
super
|
450
|
+
self.class.attributes.reject {|attr| attr.default.nil?}.each do |attr|
|
451
|
+
send("#{attr.method_name}=", attr.default)
|
452
|
+
end
|
435
453
|
end
|
436
454
|
|
437
455
|
#
|
@@ -481,6 +499,7 @@ module HappyMapper
|
|
481
499
|
unless attribute.options[:read_only]
|
482
500
|
|
483
501
|
value = send(attribute.method_name)
|
502
|
+
value = nil if value == attribute.default
|
484
503
|
|
485
504
|
#
|
486
505
|
# If the attribute defines an on_save lambda/proc or value that maps to
|
@@ -701,7 +720,8 @@ module HappyMapper
|
|
701
720
|
|
702
721
|
end
|
703
722
|
|
704
|
-
require
|
705
|
-
require
|
706
|
-
require
|
707
|
-
require
|
723
|
+
require 'happymapper/supported_types'
|
724
|
+
require 'happymapper/item'
|
725
|
+
require 'happymapper/attribute'
|
726
|
+
require 'happymapper/element'
|
727
|
+
require 'happymapper/text_node'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Attribute Default Value" do
|
4
|
+
|
5
|
+
context "when given a default value" do
|
6
|
+
|
7
|
+
class Meal
|
8
|
+
include HappyMapper
|
9
|
+
tag 'meal'
|
10
|
+
attribute :type, String, :default => 'omnivore'
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:subject) { Meal }
|
14
|
+
let(:default_meal_type) { 'omnivore' }
|
15
|
+
|
16
|
+
context "when no value has been specified" do
|
17
|
+
it "returns the default value" do
|
18
|
+
meal = subject.parse('<meal />')
|
19
|
+
expect(meal.type).to eq default_meal_type
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when saving to xml" do
|
24
|
+
|
25
|
+
let(:expected_xml) { %{<?xml version="1.0"?>\n<meal/>\n} }
|
26
|
+
|
27
|
+
it "the default value is not included" do
|
28
|
+
meal = subject.new
|
29
|
+
expect(meal.to_xml).to eq expected_xml
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when a new, non-nil value has been set" do
|
34
|
+
it "returns the new value" do
|
35
|
+
meal = subject.parse('<meal />')
|
36
|
+
meal.type = 'vegan'
|
37
|
+
|
38
|
+
expect(meal.type).to_not eq default_meal_type
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:expected_xml) { %{<?xml version="1.0"?>\n<meal type="kosher"/>\n} }
|
42
|
+
|
43
|
+
it "saves the new value to the xml" do
|
44
|
+
meal = subject.new
|
45
|
+
meal.type = 'kosher'
|
46
|
+
expect(meal.to_xml).to eq expected_xml
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
<?xml version="1.0"?>
|
2
|
-
<bk:book xmlns="urn:
|
2
|
+
<bk:book xmlns:bk="urn:loc.gov:books" xmlns:p="urn:loc.gov:people" xmlns="urn:ISBN:0-395-36341-6">
|
3
|
+
<p:author>Frank Gilbreth</p:author>
|
3
4
|
<bk:title>Cheaper by the Dozen</bk:title>
|
4
5
|
<number>1568491379</number>
|
5
6
|
</bk:book>
|