nokogiri-happymapper 0.5.6 → 0.5.7
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|