activeobject 0.0.3
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.
- data/CHANGE +10 -0
- data/Interface_desc +21 -0
- data/MIT-LICENSE +20 -0
- data/README +72 -0
- data/Rakefile.rb +9 -0
- data/active-object.gemspec +50 -0
- data/examples/account.rb +69 -0
- data/examples/data.tch +0 -0
- data/examples/light_cloud.yml +18 -0
- data/examples/test.rb +3 -0
- data/examples/user.rb +112 -0
- data/init.rb +4 -0
- data/lib/active-object.rb +23 -0
- data/lib/active_object/adapters/light_cloud.rb +40 -0
- data/lib/active_object/adapters/tokyo_cabinet.rb +48 -0
- data/lib/active_object/adapters/tokyo_tyrant.rb +14 -0
- data/lib/active_object/associations.rb +200 -0
- data/lib/active_object/base.rb +415 -0
- data/lib/active_object/callbacks.rb +180 -0
- data/lib/active_object/observer.rb +180 -0
- data/lib/active_object/serialization.rb +99 -0
- data/lib/active_object/serializers/json_serializer.rb +75 -0
- data/lib/active_object/serializers/xml_serializer.rb +325 -0
- data/lib/active_object/validations.rb +687 -0
- data/lib/active_support/callbacks.rb +303 -0
- data/lib/active_support/core_ext/array/access.rb +53 -0
- data/lib/active_support/core_ext/array/conversions.rb +183 -0
- data/lib/active_support/core_ext/array/extract_options.rb +20 -0
- data/lib/active_support/core_ext/array/grouping.rb +106 -0
- data/lib/active_support/core_ext/array/random_access.rb +12 -0
- data/lib/active_support/core_ext/array.rb +13 -0
- data/lib/active_support/core_ext/blank.rb +58 -0
- data/lib/active_support/core_ext/class/attribute_accessors.rb +54 -0
- data/lib/active_support/core_ext/class/inheritable_attributes.rb +140 -0
- data/lib/active_support/core_ext/class/removal.rb +50 -0
- data/lib/active_support/core_ext/class.rb +3 -0
- data/lib/active_support/core_ext/duplicable.rb +43 -0
- data/lib/active_support/core_ext/enumerable.rb +72 -0
- data/lib/active_support/core_ext/hash/conversions.rb +259 -0
- data/lib/active_support/core_ext/hash/keys.rb +52 -0
- data/lib/active_support/core_ext/hash.rb +8 -0
- data/lib/active_support/core_ext/module/aliasing.rb +74 -0
- data/lib/active_support/core_ext/module/attr_accessor_with_default.rb +31 -0
- data/lib/active_support/core_ext/module/attribute_accessors.rb +58 -0
- data/lib/active_support/core_ext/module.rb +16 -0
- data/lib/active_support/core_ext/object/conversions.rb +14 -0
- data/lib/active_support/core_ext/object/extending.rb +80 -0
- data/lib/active_support/core_ext/object/instance_variables.rb +74 -0
- data/lib/active_support/core_ext/object/metaclass.rb +13 -0
- data/lib/active_support/core_ext/object/misc.rb +43 -0
- data/lib/active_support/core_ext/object.rb +5 -0
- data/lib/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/active_support/core_ext/string.rb +7 -0
- data/lib/active_support/core_ext.rb +4 -0
- data/lib/active_support/inflections.rb +55 -0
- data/lib/active_support/inflector.rb +348 -0
- data/lib/active_support/vendor/builder-2.1.2/blankslate.rb +113 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/blankslate.rb +20 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/css.rb +250 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xchar.rb +115 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xmlbase.rb +139 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xmlevents.rb +63 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xmlmarkup.rb +328 -0
- data/lib/active_support/vendor/builder-2.1.2/builder.rb +13 -0
- data/lib/active_support/vendor/xml-simple-1.0.11/xmlsimple.rb +1021 -0
- data/lib/active_support/vendor.rb +14 -0
- data/lib/active_support.rb +6 -0
- data/spec/case/association_test.rb +97 -0
- data/spec/case/base_test.rb +74 -0
- data/spec/case/callbacks_observers_test.rb +38 -0
- data/spec/case/callbacks_test.rb +424 -0
- data/spec/case/serialization_test.rb +87 -0
- data/spec/case/validations_test.rb +1482 -0
- data/spec/data.tch +0 -0
- data/spec/helper.rb +15 -0
- data/spec/light_cloud.yml +18 -0
- data/spec/model/account.rb +4 -0
- data/spec/model/topic.rb +26 -0
- data/spec/model/user.rb +8 -0
- metadata +173 -0
@@ -0,0 +1,325 @@
|
|
1
|
+
module ActiveObject #:nodoc:
|
2
|
+
module Serialization
|
3
|
+
# Builds an XML document to represent the model. Some configuration is
|
4
|
+
# available through +options+. However more complicated cases should
|
5
|
+
# override ActiveObject::Base#to_xml.
|
6
|
+
#
|
7
|
+
# By default the generated XML document will include the processing
|
8
|
+
# instruction and all the object's attributes. For example:
|
9
|
+
#
|
10
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
11
|
+
# <topic>
|
12
|
+
# <title>The First Topic</title>
|
13
|
+
# <author-name>David</author-name>
|
14
|
+
# <id type="integer">1</id>
|
15
|
+
# <approved type="boolean">false</approved>
|
16
|
+
# <replies-count type="integer">0</replies-count>
|
17
|
+
# <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
|
18
|
+
# <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
|
19
|
+
# <content>Have a nice day</content>
|
20
|
+
# <author-email-address>david@loudthinking.com</author-email-address>
|
21
|
+
# <parent-id></parent-id>
|
22
|
+
# <last-read type="date">2004-04-15</last-read>
|
23
|
+
# </topic>
|
24
|
+
#
|
25
|
+
# This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
|
26
|
+
# <tt>:skip_instruct</tt>, <tt>:skip_types</tt> and <tt>:dasherize</tt>.
|
27
|
+
# The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
|
28
|
+
# +attributes+ method. The default is to dasherize all column names, but you
|
29
|
+
# can disable this setting <tt>:dasherize</tt> to +false+. To not have the
|
30
|
+
# column type included in the XML output set <tt>:skip_types</tt> to +true+.
|
31
|
+
#
|
32
|
+
# For instance:
|
33
|
+
#
|
34
|
+
# topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
|
35
|
+
#
|
36
|
+
# <topic>
|
37
|
+
# <title>The First Topic</title>
|
38
|
+
# <author-name>David</author-name>
|
39
|
+
# <approved type="boolean">false</approved>
|
40
|
+
# <content>Have a nice day</content>
|
41
|
+
# <author-email-address>david@loudthinking.com</author-email-address>
|
42
|
+
# <parent-id></parent-id>
|
43
|
+
# <last-read type="date">2004-04-15</last-read>
|
44
|
+
# </topic>
|
45
|
+
#
|
46
|
+
# To include first level associations use <tt>:include</tt>:
|
47
|
+
#
|
48
|
+
# firm.to_xml :include => [ :account, :clients ]
|
49
|
+
#
|
50
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
51
|
+
# <firm>
|
52
|
+
# <id type="integer">1</id>
|
53
|
+
# <rating type="integer">1</rating>
|
54
|
+
# <name>37signals</name>
|
55
|
+
# <clients type="array">
|
56
|
+
# <client>
|
57
|
+
# <rating type="integer">1</rating>
|
58
|
+
# <name>Summit</name>
|
59
|
+
# </client>
|
60
|
+
# <client>
|
61
|
+
# <rating type="integer">1</rating>
|
62
|
+
# <name>Microsoft</name>
|
63
|
+
# </client>
|
64
|
+
# </clients>
|
65
|
+
# <account>
|
66
|
+
# <id type="integer">1</id>
|
67
|
+
# <credit-limit type="integer">50</credit-limit>
|
68
|
+
# </account>
|
69
|
+
# </firm>
|
70
|
+
#
|
71
|
+
# To include deeper levels of associations pass a hash like this:
|
72
|
+
#
|
73
|
+
# firm.to_xml :include => {:account => {}, :clients => {:include => :address}}
|
74
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
75
|
+
# <firm>
|
76
|
+
# <id type="integer">1</id>
|
77
|
+
# <rating type="integer">1</rating>
|
78
|
+
# <name>37signals</name>
|
79
|
+
# <clients type="array">
|
80
|
+
# <client>
|
81
|
+
# <rating type="integer">1</rating>
|
82
|
+
# <name>Summit</name>
|
83
|
+
# <address>
|
84
|
+
# ...
|
85
|
+
# </address>
|
86
|
+
# </client>
|
87
|
+
# <client>
|
88
|
+
# <rating type="integer">1</rating>
|
89
|
+
# <name>Microsoft</name>
|
90
|
+
# <address>
|
91
|
+
# ...
|
92
|
+
# </address>
|
93
|
+
# </client>
|
94
|
+
# </clients>
|
95
|
+
# <account>
|
96
|
+
# <id type="integer">1</id>
|
97
|
+
# <credit-limit type="integer">50</credit-limit>
|
98
|
+
# </account>
|
99
|
+
# </firm>
|
100
|
+
#
|
101
|
+
# To include any methods on the model being called use <tt>:methods</tt>:
|
102
|
+
#
|
103
|
+
# firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
|
104
|
+
#
|
105
|
+
# <firm>
|
106
|
+
# # ... normal attributes as shown above ...
|
107
|
+
# <calculated-earnings>100000000000000000</calculated-earnings>
|
108
|
+
# <real-earnings>5</real-earnings>
|
109
|
+
# </firm>
|
110
|
+
#
|
111
|
+
# To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
|
112
|
+
# modified version of the options hash that was given to +to_xml+:
|
113
|
+
#
|
114
|
+
# proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
|
115
|
+
# firm.to_xml :procs => [ proc ]
|
116
|
+
#
|
117
|
+
# <firm>
|
118
|
+
# # ... normal attributes as shown above ...
|
119
|
+
# <abc>def</abc>
|
120
|
+
# </firm>
|
121
|
+
#
|
122
|
+
# Alternatively, you can yield the builder object as part of the +to_xml+ call:
|
123
|
+
#
|
124
|
+
# firm.to_xml do |xml|
|
125
|
+
# xml.creator do
|
126
|
+
# xml.first_name "David"
|
127
|
+
# xml.last_name "Heinemeier Hansson"
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# <firm>
|
132
|
+
# # ... normal attributes as shown above ...
|
133
|
+
# <creator>
|
134
|
+
# <first_name>David</first_name>
|
135
|
+
# <last_name>Heinemeier Hansson</last_name>
|
136
|
+
# </creator>
|
137
|
+
# </firm>
|
138
|
+
#
|
139
|
+
# As noted above, you may override +to_xml+ in your ActiveObject::Base
|
140
|
+
# subclasses to have complete control about what's generated. The general
|
141
|
+
# form of doing this is:
|
142
|
+
#
|
143
|
+
# class IHaveMyOwnXML < ActiveObject::Base
|
144
|
+
# def to_xml(options = {})
|
145
|
+
# options[:indent] ||= 2
|
146
|
+
# xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
147
|
+
# xml.instruct! unless options[:skip_instruct]
|
148
|
+
# xml.level_one do
|
149
|
+
# xml.tag!(:second_level, 'content')
|
150
|
+
# end
|
151
|
+
# end
|
152
|
+
# end
|
153
|
+
def to_xml(options = {}, &block)
|
154
|
+
serializer = XmlSerializer.new(self, options)
|
155
|
+
block_given? ? serializer.to_s(&block) : serializer.to_s
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
class XmlSerializer < ActiveObject::Serialization::Serializer #:nodoc:
|
161
|
+
def builder
|
162
|
+
@builder ||= begin
|
163
|
+
options[:indent] ||= 2
|
164
|
+
builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
165
|
+
|
166
|
+
unless options[:skip_instruct]
|
167
|
+
builder.instruct!
|
168
|
+
options[:skip_instruct] = true
|
169
|
+
end
|
170
|
+
|
171
|
+
builder
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def root
|
176
|
+
root = (options[:root] || @object.class.to_s.underscore).to_s
|
177
|
+
dasherize? ? root.dasherize : root
|
178
|
+
end
|
179
|
+
|
180
|
+
def dasherize?
|
181
|
+
!options.has_key?(:dasherize) || options[:dasherize]
|
182
|
+
end
|
183
|
+
|
184
|
+
def serializable_attributes
|
185
|
+
serializable_attribute_names.collect { |name| Attribute.new(name.to_s, @object) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def serializable_method_attributes
|
189
|
+
Array(options[:methods]).inject([]) do |method_attributes, name|
|
190
|
+
method_attributes << MethodAttribute.new(name.to_s, @object) if @object.respond_to?(name.to_s)
|
191
|
+
method_attributes
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def add_attributes
|
196
|
+
(serializable_attributes + serializable_method_attributes).each do |attribute|
|
197
|
+
add_tag(attribute)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def add_procs
|
202
|
+
if procs = options.delete(:procs)
|
203
|
+
[ *procs ].each do |proc|
|
204
|
+
proc.call(options)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def add_tag(attribute)
|
210
|
+
builder.tag!(
|
211
|
+
dasherize? ? attribute.name.dasherize : attribute.name,
|
212
|
+
attribute.value.to_s,
|
213
|
+
attribute.decorations(!options[:skip_types])
|
214
|
+
)
|
215
|
+
end
|
216
|
+
|
217
|
+
def add_associations(association, objects, opts)
|
218
|
+
if objects.is_a?(Enumerable)
|
219
|
+
tag = association.to_s
|
220
|
+
tag = tag.dasherize if dasherize?
|
221
|
+
if objects.empty?
|
222
|
+
builder.tag!(tag, :type => :array)
|
223
|
+
else
|
224
|
+
builder.tag!(tag, :type => :array) do
|
225
|
+
association_name = association.to_s.singularize
|
226
|
+
objects.each do |object|
|
227
|
+
object.to_xml opts.merge(
|
228
|
+
:root => association_name,
|
229
|
+
:type => (object.class.to_s.underscore == association_name ? nil : object.class.name)
|
230
|
+
)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
else
|
235
|
+
if object = @object.send(association)
|
236
|
+
object.to_xml(opts.merge(:root => association))
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def serialize
|
242
|
+
args = [root]
|
243
|
+
if options[:namespace]
|
244
|
+
args << {:xmlns=>options[:namespace]}
|
245
|
+
end
|
246
|
+
|
247
|
+
if options[:type]
|
248
|
+
args << {:type=>options[:type]}
|
249
|
+
end
|
250
|
+
|
251
|
+
builder.tag!(*args) do
|
252
|
+
add_attributes
|
253
|
+
procs = options.delete(:procs)
|
254
|
+
add_includes { |association, objects, opts| add_associations(association, objects, opts) }
|
255
|
+
options[:procs] = procs
|
256
|
+
add_procs
|
257
|
+
yield builder if block_given?
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class Attribute #:nodoc:
|
262
|
+
attr_reader :name, :value, :type
|
263
|
+
|
264
|
+
def initialize(name, object)
|
265
|
+
@name, @object = name, object
|
266
|
+
|
267
|
+
@type = compute_type
|
268
|
+
@value = compute_value
|
269
|
+
end
|
270
|
+
|
271
|
+
# There is a significant speed improvement if the value
|
272
|
+
# does not need to be escaped, as <tt>tag!</tt> escapes all values
|
273
|
+
# to ensure that valid XML is generated. For known binary
|
274
|
+
# values, it is at least an order of magnitude faster to
|
275
|
+
# Base64 encode binary values and directly put them in the
|
276
|
+
# output XML than to pass the original value or the Base64
|
277
|
+
# encoded value to the <tt>tag!</tt> method. It definitely makes
|
278
|
+
# no sense to Base64 encode the value and then give it to
|
279
|
+
# <tt>tag!</tt>, since that just adds additional overhead.
|
280
|
+
def needs_encoding?
|
281
|
+
![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
|
282
|
+
end
|
283
|
+
|
284
|
+
def decorations(include_types = true)
|
285
|
+
decorations = {}
|
286
|
+
|
287
|
+
if type == :binary
|
288
|
+
decorations[:encoding] = 'base64'
|
289
|
+
end
|
290
|
+
|
291
|
+
if include_types && type != :string
|
292
|
+
decorations[:type] = type
|
293
|
+
end
|
294
|
+
|
295
|
+
if value.nil?
|
296
|
+
decorations[:nil] = true
|
297
|
+
end
|
298
|
+
|
299
|
+
decorations
|
300
|
+
end
|
301
|
+
|
302
|
+
protected
|
303
|
+
def compute_type
|
304
|
+
type = @object.send(name).class
|
305
|
+
end
|
306
|
+
|
307
|
+
def compute_value
|
308
|
+
value = @object.send(name)
|
309
|
+
|
310
|
+
if formatter = Hash::XML_FORMATTING[type.to_s]
|
311
|
+
value ? formatter.call(value) : nil
|
312
|
+
else
|
313
|
+
value
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
class MethodAttribute < Attribute #:nodoc:
|
319
|
+
protected
|
320
|
+
def compute_type
|
321
|
+
Hash::XML_TYPE_NAMES[@object.send(name).class.name] || :string
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|