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,58 @@
|
|
1
|
+
class Object
|
2
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
3
|
+
# For example, "", " ", +nil+, [], and {} are blank.
|
4
|
+
#
|
5
|
+
# This simplifies
|
6
|
+
#
|
7
|
+
# if !address.nil? && !address.empty?
|
8
|
+
#
|
9
|
+
# to
|
10
|
+
#
|
11
|
+
# if !address.blank?
|
12
|
+
def blank?
|
13
|
+
respond_to?(:empty?) ? empty? : !self
|
14
|
+
end
|
15
|
+
|
16
|
+
# An object is present if it's not blank.
|
17
|
+
def present?
|
18
|
+
!blank?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class NilClass #:nodoc:
|
23
|
+
def blank?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class FalseClass #:nodoc:
|
29
|
+
def blank?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class TrueClass #:nodoc:
|
35
|
+
def blank?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Array #:nodoc:
|
41
|
+
alias_method :blank?, :empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
class Hash #:nodoc:
|
45
|
+
alias_method :blank?, :empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
class String #:nodoc:
|
49
|
+
def blank?
|
50
|
+
self !~ /\S/
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Numeric #:nodoc:
|
55
|
+
def blank?
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Extends the class object with class and instance accessors for class attributes,
|
2
|
+
# just like the native attr* accessors for instance attributes.
|
3
|
+
#
|
4
|
+
# class Person
|
5
|
+
# cattr_accessor :hair_colors
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
# Person.hair_colors = [:brown, :black, :blonde, :red]
|
9
|
+
class Class
|
10
|
+
def cattr_reader(*syms)
|
11
|
+
syms.flatten.each do |sym|
|
12
|
+
next if sym.is_a?(Hash)
|
13
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
14
|
+
unless defined? @@#{sym}
|
15
|
+
@@#{sym} = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.#{sym}
|
19
|
+
@@#{sym}
|
20
|
+
end
|
21
|
+
|
22
|
+
def #{sym}
|
23
|
+
@@#{sym}
|
24
|
+
end
|
25
|
+
EOS
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def cattr_writer(*syms)
|
30
|
+
options = syms.extract_options!
|
31
|
+
syms.flatten.each do |sym|
|
32
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
33
|
+
unless defined? @@#{sym}
|
34
|
+
@@#{sym} = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.#{sym}=(obj)
|
38
|
+
@@#{sym} = obj
|
39
|
+
end
|
40
|
+
|
41
|
+
#{"
|
42
|
+
def #{sym}=(obj)
|
43
|
+
@@#{sym} = obj
|
44
|
+
end
|
45
|
+
" unless options[:instance_writer] == false }
|
46
|
+
EOS
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def cattr_accessor(*syms)
|
51
|
+
cattr_reader(*syms)
|
52
|
+
cattr_writer(*syms)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# Retain for backward compatibility. Methods are now included in Class.
|
2
|
+
module ClassInheritableAttributes # :nodoc:
|
3
|
+
end
|
4
|
+
|
5
|
+
# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
|
6
|
+
# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
|
7
|
+
# to, for example, an array without those additions being shared with either their parent, siblings, or
|
8
|
+
# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
|
9
|
+
class Class # :nodoc:
|
10
|
+
def class_inheritable_reader(*syms)
|
11
|
+
syms.each do |sym|
|
12
|
+
next if sym.is_a?(Hash)
|
13
|
+
class_eval <<-EOS
|
14
|
+
def self.#{sym}
|
15
|
+
read_inheritable_attribute(:#{sym})
|
16
|
+
end
|
17
|
+
|
18
|
+
def #{sym}
|
19
|
+
self.class.#{sym}
|
20
|
+
end
|
21
|
+
EOS
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def class_inheritable_writer(*syms)
|
26
|
+
options = syms.extract_options!
|
27
|
+
syms.each do |sym|
|
28
|
+
class_eval <<-EOS
|
29
|
+
def self.#{sym}=(obj)
|
30
|
+
write_inheritable_attribute(:#{sym}, obj)
|
31
|
+
end
|
32
|
+
|
33
|
+
#{"
|
34
|
+
def #{sym}=(obj)
|
35
|
+
self.class.#{sym} = obj
|
36
|
+
end
|
37
|
+
" unless options[:instance_writer] == false }
|
38
|
+
EOS
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def class_inheritable_array_writer(*syms)
|
43
|
+
options = syms.extract_options!
|
44
|
+
syms.each do |sym|
|
45
|
+
class_eval <<-EOS
|
46
|
+
def self.#{sym}=(obj)
|
47
|
+
write_inheritable_array(:#{sym}, obj)
|
48
|
+
end
|
49
|
+
|
50
|
+
#{"
|
51
|
+
def #{sym}=(obj)
|
52
|
+
self.class.#{sym} = obj
|
53
|
+
end
|
54
|
+
" unless options[:instance_writer] == false }
|
55
|
+
EOS
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def class_inheritable_hash_writer(*syms)
|
60
|
+
options = syms.extract_options!
|
61
|
+
syms.each do |sym|
|
62
|
+
class_eval <<-EOS
|
63
|
+
def self.#{sym}=(obj)
|
64
|
+
write_inheritable_hash(:#{sym}, obj)
|
65
|
+
end
|
66
|
+
|
67
|
+
#{"
|
68
|
+
def #{sym}=(obj)
|
69
|
+
self.class.#{sym} = obj
|
70
|
+
end
|
71
|
+
" unless options[:instance_writer] == false }
|
72
|
+
EOS
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def class_inheritable_accessor(*syms)
|
77
|
+
class_inheritable_reader(*syms)
|
78
|
+
class_inheritable_writer(*syms)
|
79
|
+
end
|
80
|
+
|
81
|
+
def class_inheritable_array(*syms)
|
82
|
+
class_inheritable_reader(*syms)
|
83
|
+
class_inheritable_array_writer(*syms)
|
84
|
+
end
|
85
|
+
|
86
|
+
def class_inheritable_hash(*syms)
|
87
|
+
class_inheritable_reader(*syms)
|
88
|
+
class_inheritable_hash_writer(*syms)
|
89
|
+
end
|
90
|
+
|
91
|
+
def inheritable_attributes
|
92
|
+
@inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
|
93
|
+
end
|
94
|
+
|
95
|
+
def write_inheritable_attribute(key, value)
|
96
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
97
|
+
@inheritable_attributes = {}
|
98
|
+
end
|
99
|
+
inheritable_attributes[key] = value
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_inheritable_array(key, elements)
|
103
|
+
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
|
104
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
|
105
|
+
end
|
106
|
+
|
107
|
+
def write_inheritable_hash(key, hash)
|
108
|
+
write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
|
109
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
|
110
|
+
end
|
111
|
+
|
112
|
+
def read_inheritable_attribute(key)
|
113
|
+
inheritable_attributes[key]
|
114
|
+
end
|
115
|
+
|
116
|
+
def reset_inheritable_attributes
|
117
|
+
@inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
# Prevent this constant from being created multiple times
|
122
|
+
EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
|
123
|
+
|
124
|
+
def inherited_with_inheritable_attributes(child)
|
125
|
+
inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
|
126
|
+
|
127
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
128
|
+
new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
129
|
+
else
|
130
|
+
new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
|
131
|
+
memo.update(key => value.duplicable? ? value.dup : value)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
|
136
|
+
end
|
137
|
+
|
138
|
+
alias inherited_without_inheritable_attributes inherited
|
139
|
+
alias inherited inherited_with_inheritable_attributes
|
140
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Class #:nodoc:
|
2
|
+
|
3
|
+
# Unassociates the class with its subclasses and removes the subclasses
|
4
|
+
# themselves.
|
5
|
+
#
|
6
|
+
# Integer.remove_subclasses # => [Bignum, Fixnum]
|
7
|
+
# Fixnum # => NameError: uninitialized constant Fixnum
|
8
|
+
def remove_subclasses
|
9
|
+
Object.remove_subclasses_of(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns an array with the names of the subclasses of +self+ as strings.
|
13
|
+
#
|
14
|
+
# Integer.subclasses # => ["Bignum", "Fixnum"]
|
15
|
+
def subclasses
|
16
|
+
Object.subclasses_of(self).map { |o| o.to_s }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Removes the classes in +klasses+ from their parent module.
|
20
|
+
#
|
21
|
+
# Ordinary classes belong to some module via a constant. This method computes
|
22
|
+
# that constant name from the class name and removes it from the module it
|
23
|
+
# belongs to.
|
24
|
+
#
|
25
|
+
# Object.remove_class(Integer) # => [Integer]
|
26
|
+
# Integer # => NameError: uninitialized constant Integer
|
27
|
+
#
|
28
|
+
# Take into account that in general the class object could be still stored
|
29
|
+
# somewhere else.
|
30
|
+
#
|
31
|
+
# i = Integer # => Integer
|
32
|
+
# Object.remove_class(Integer) # => [Integer]
|
33
|
+
# Integer # => NameError: uninitialized constant Integer
|
34
|
+
# i.subclasses # => ["Bignum", "Fixnum"]
|
35
|
+
# Fixnum.superclass # => Integer
|
36
|
+
def remove_class(*klasses)
|
37
|
+
klasses.flatten.each do |klass|
|
38
|
+
# Skip this class if there is nothing bound to this name
|
39
|
+
next unless defined?(klass.name)
|
40
|
+
|
41
|
+
basename = klass.to_s.split("::").last
|
42
|
+
parent = klass.parent
|
43
|
+
|
44
|
+
# Skip this class if it does not match the current one bound to this name
|
45
|
+
next unless parent.const_defined?(basename) && klass = parent.const_get(basename)
|
46
|
+
|
47
|
+
parent.instance_eval { remove_const basename } unless parent == klass
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Object
|
2
|
+
# Can you safely .dup this object?
|
3
|
+
# False for nil, false, true, symbols, and numbers; true otherwise.
|
4
|
+
def duplicable?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class NilClass #:nodoc:
|
10
|
+
def duplicable?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class FalseClass #:nodoc:
|
16
|
+
def duplicable?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class TrueClass #:nodoc:
|
22
|
+
def duplicable?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Symbol #:nodoc:
|
28
|
+
def duplicable?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Numeric #:nodoc:
|
34
|
+
def duplicable?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Class #:nodoc:
|
40
|
+
def duplicable?
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Enumerable
|
2
|
+
|
3
|
+
# Calculates a sum from the elements. Examples:
|
4
|
+
#
|
5
|
+
# payments.sum { |p| p.price * p.tax_rate }
|
6
|
+
# payments.sum(&:price)
|
7
|
+
#
|
8
|
+
# The latter is a shortcut for:
|
9
|
+
#
|
10
|
+
# payments.inject { |sum, p| sum + p.price }
|
11
|
+
#
|
12
|
+
# It can also calculate the sum without the use of a block.
|
13
|
+
#
|
14
|
+
# [5, 15, 10].sum # => 30
|
15
|
+
# ["foo", "bar"].sum # => "foobar"
|
16
|
+
# [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5]
|
17
|
+
#
|
18
|
+
# The default sum of an empty list is zero. You can override this default:
|
19
|
+
#
|
20
|
+
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
|
21
|
+
#
|
22
|
+
def sum(identity = 0, &block)
|
23
|
+
return identity unless size > 0
|
24
|
+
|
25
|
+
if block_given?
|
26
|
+
map(&block).sum
|
27
|
+
else
|
28
|
+
inject { |sum, element| sum + element }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Iterates over a collection, passing the current element *and* the
|
33
|
+
# +memo+ to the block. Handy for building up hashes or
|
34
|
+
# reducing collections down to one object. Examples:
|
35
|
+
#
|
36
|
+
# %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } #=> {'foo' => 'FOO', 'bar' => 'BAR'}
|
37
|
+
#
|
38
|
+
# *Note* that you can't use immutable objects like numbers, true or false as
|
39
|
+
# the memo. You would think the following returns 120, but since the memo is
|
40
|
+
# never changed, it does not.
|
41
|
+
#
|
42
|
+
# (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1
|
43
|
+
#
|
44
|
+
def each_with_object(memo, &block)
|
45
|
+
returning memo do |m|
|
46
|
+
each do |element|
|
47
|
+
block.call(element, m)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end unless [].respond_to?(:each_with_object)
|
51
|
+
|
52
|
+
# Convert an enumerable to a hash. Examples:
|
53
|
+
#
|
54
|
+
# people.index_by(&:login)
|
55
|
+
# => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
|
56
|
+
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
|
57
|
+
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
|
58
|
+
#
|
59
|
+
def index_by
|
60
|
+
inject({}) do |accum, elem|
|
61
|
+
accum[yield(elem)] = elem
|
62
|
+
accum
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns true if the collection has more than 1 element. Functionally equivalent to collection.size > 1.
|
67
|
+
# Works with a block too ala any?, so people.many? { |p| p.age > 26 } # => returns true if more than 1 person is over 26.
|
68
|
+
def many?(&block)
|
69
|
+
size = block_given? ? select(&block).size : self.size
|
70
|
+
size > 1
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'cgi'
|
3
|
+
require 'builder'
|
4
|
+
require 'xmlsimple'
|
5
|
+
|
6
|
+
# Locked down XmlSimple#xml_in_string
|
7
|
+
class XmlSimple
|
8
|
+
# Same as xml_in but doesn't try to smartly shoot itself in the foot.
|
9
|
+
def xml_in_string(string, options = nil)
|
10
|
+
handle_options('in', options)
|
11
|
+
|
12
|
+
@doc = parse(string)
|
13
|
+
result = collapse(@doc.root)
|
14
|
+
|
15
|
+
if @options['keeproot']
|
16
|
+
merge({}, @doc.root.name, result)
|
17
|
+
else
|
18
|
+
result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.xml_in_string(string, options = nil)
|
23
|
+
new.xml_in_string(string, options)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# This module exists to decorate files deserialized using Hash.from_xml with
|
28
|
+
# the <tt>original_filename</tt> and <tt>content_type</tt> methods.
|
29
|
+
module FileLike #:nodoc:
|
30
|
+
attr_writer :original_filename, :content_type
|
31
|
+
|
32
|
+
def original_filename
|
33
|
+
@original_filename || 'untitled'
|
34
|
+
end
|
35
|
+
|
36
|
+
def content_type
|
37
|
+
@content_type || 'application/octet-stream'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module ActiveSupport #:nodoc:
|
42
|
+
module CoreExtensions #:nodoc:
|
43
|
+
module Hash #:nodoc:
|
44
|
+
module Conversions
|
45
|
+
|
46
|
+
XML_TYPE_NAMES = {
|
47
|
+
"Symbol" => "symbol",
|
48
|
+
"Fixnum" => "integer",
|
49
|
+
"Bignum" => "integer",
|
50
|
+
"BigDecimal" => "decimal",
|
51
|
+
"Float" => "float",
|
52
|
+
"Date" => "date",
|
53
|
+
"DateTime" => "datetime",
|
54
|
+
"Time" => "datetime",
|
55
|
+
"TrueClass" => "boolean",
|
56
|
+
"FalseClass" => "boolean"
|
57
|
+
} unless defined?(XML_TYPE_NAMES)
|
58
|
+
|
59
|
+
XML_FORMATTING = {
|
60
|
+
"symbol" => Proc.new { |symbol| symbol.to_s },
|
61
|
+
"date" => Proc.new { |date| date.to_s(:db) },
|
62
|
+
"datetime" => Proc.new { |time| time.xmlschema },
|
63
|
+
"binary" => Proc.new { |binary| ActiveSupport::Base64.encode64(binary) },
|
64
|
+
"yaml" => Proc.new { |yaml| yaml.to_yaml }
|
65
|
+
} unless defined?(XML_FORMATTING)
|
66
|
+
|
67
|
+
# TODO: use Time.xmlschema instead of Time.parse;
|
68
|
+
# use regexp instead of Date.parse
|
69
|
+
unless defined?(XML_PARSING)
|
70
|
+
XML_PARSING = {
|
71
|
+
"symbol" => Proc.new { |symbol| symbol.to_sym },
|
72
|
+
"date" => Proc.new { |date| ::Date.parse(date) },
|
73
|
+
"datetime" => Proc.new { |time| ::Time.parse(time).utc rescue ::DateTime.parse(time).utc },
|
74
|
+
"integer" => Proc.new { |integer| integer.to_i },
|
75
|
+
"float" => Proc.new { |float| float.to_f },
|
76
|
+
"decimal" => Proc.new { |number| BigDecimal(number) },
|
77
|
+
"boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) },
|
78
|
+
"string" => Proc.new { |string| string.to_s },
|
79
|
+
"yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml },
|
80
|
+
"base64Binary" => Proc.new { |bin| ActiveSupport::Base64.decode64(bin) },
|
81
|
+
"file" => Proc.new do |file, entity|
|
82
|
+
f = StringIO.new(ActiveSupport::Base64.decode64(file))
|
83
|
+
f.extend(FileLike)
|
84
|
+
f.original_filename = entity['name']
|
85
|
+
f.content_type = entity['content_type']
|
86
|
+
f
|
87
|
+
end
|
88
|
+
}
|
89
|
+
|
90
|
+
XML_PARSING.update(
|
91
|
+
"double" => XML_PARSING["float"],
|
92
|
+
"dateTime" => XML_PARSING["datetime"]
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.included(klass)
|
97
|
+
klass.extend(ClassMethods)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Converts a hash into a string suitable for use as a URL query string. An optional <tt>namespace</tt> can be
|
101
|
+
# passed to enclose the param names (see example below).
|
102
|
+
#
|
103
|
+
# ==== Example:
|
104
|
+
# { :name => 'David', :nationality => 'Danish' }.to_query # => "name=David&nationality=Danish"
|
105
|
+
#
|
106
|
+
# { :name => 'David', :nationality => 'Danish' }.to_query('user') # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
|
107
|
+
def to_query(namespace = nil)
|
108
|
+
collect do |key, value|
|
109
|
+
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
|
110
|
+
end.sort * '&'
|
111
|
+
end
|
112
|
+
|
113
|
+
alias_method :to_param, :to_query
|
114
|
+
|
115
|
+
def to_xml(options = {})
|
116
|
+
options[:indent] ||= 2
|
117
|
+
options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]),
|
118
|
+
:root => "hash" })
|
119
|
+
options[:builder].instruct! unless options.delete(:skip_instruct)
|
120
|
+
dasherize = !options.has_key?(:dasherize) || options[:dasherize]
|
121
|
+
root = dasherize ? options[:root].to_s.dasherize : options[:root].to_s
|
122
|
+
|
123
|
+
options[:builder].__send__(:method_missing, root) do
|
124
|
+
each do |key, value|
|
125
|
+
case value
|
126
|
+
when ::Hash
|
127
|
+
value.to_xml(options.merge({ :root => key, :skip_instruct => true }))
|
128
|
+
when ::Array
|
129
|
+
value.to_xml(options.merge({ :root => key, :children => key.to_s.singularize, :skip_instruct => true}))
|
130
|
+
when ::Method, ::Proc
|
131
|
+
# If the Method or Proc takes two arguments, then
|
132
|
+
# pass the suggested child element name. This is
|
133
|
+
# used if the Method or Proc will be operating over
|
134
|
+
# multiple records and needs to create an containing
|
135
|
+
# element that will contain the objects being
|
136
|
+
# serialized.
|
137
|
+
if 1 == value.arity
|
138
|
+
value.call(options.merge({ :root => key, :skip_instruct => true }))
|
139
|
+
else
|
140
|
+
value.call(options.merge({ :root => key, :skip_instruct => true }), key.to_s.singularize)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
if value.respond_to?(:to_xml)
|
144
|
+
value.to_xml(options.merge({ :root => key, :skip_instruct => true }))
|
145
|
+
else
|
146
|
+
type_name = XML_TYPE_NAMES[value.class.name]
|
147
|
+
|
148
|
+
key = dasherize ? key.to_s.dasherize : key.to_s
|
149
|
+
|
150
|
+
attributes = options[:skip_types] || value.nil? || type_name.nil? ? { } : { :type => type_name }
|
151
|
+
if value.nil?
|
152
|
+
attributes[:nil] = true
|
153
|
+
end
|
154
|
+
|
155
|
+
options[:builder].tag!(key,
|
156
|
+
XML_FORMATTING[type_name] ? XML_FORMATTING[type_name].call(value) : value,
|
157
|
+
attributes
|
158
|
+
)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
yield options[:builder] if block_given?
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
module ClassMethods
|
169
|
+
def from_xml(xml)
|
170
|
+
# TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple
|
171
|
+
typecast_xml_value(undasherize_keys(XmlSimple.xml_in_string(xml,
|
172
|
+
'forcearray' => false,
|
173
|
+
'forcecontent' => true,
|
174
|
+
'keeproot' => true,
|
175
|
+
'contentkey' => '__content__')
|
176
|
+
))
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
def typecast_xml_value(value)
|
181
|
+
case value.class.to_s
|
182
|
+
when 'Hash'
|
183
|
+
if value['type'] == 'array'
|
184
|
+
child_key, entries = value.detect { |k,v| k != 'type' } # child_key is throwaway
|
185
|
+
if entries.nil? || (c = value['__content__'] && c.blank?)
|
186
|
+
[]
|
187
|
+
else
|
188
|
+
case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a?
|
189
|
+
when "Array"
|
190
|
+
entries.collect { |v| typecast_xml_value(v) }
|
191
|
+
when "Hash"
|
192
|
+
[typecast_xml_value(entries)]
|
193
|
+
else
|
194
|
+
raise "can't typecast #{entries.inspect}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
elsif value.has_key?("__content__")
|
198
|
+
content = value["__content__"]
|
199
|
+
if parser = XML_PARSING[value["type"]]
|
200
|
+
if parser.arity == 2
|
201
|
+
XML_PARSING[value["type"]].call(content, value)
|
202
|
+
else
|
203
|
+
XML_PARSING[value["type"]].call(content)
|
204
|
+
end
|
205
|
+
else
|
206
|
+
content
|
207
|
+
end
|
208
|
+
elsif value['type'] == 'string' && value['nil'] != 'true'
|
209
|
+
""
|
210
|
+
# blank or nil parsed values are represented by nil
|
211
|
+
elsif value.blank? || value['nil'] == 'true'
|
212
|
+
nil
|
213
|
+
# If the type is the only element which makes it then
|
214
|
+
# this still makes the value nil, except if type is
|
215
|
+
# a XML node(where type['value'] is a Hash)
|
216
|
+
elsif value['type'] && value.size == 1 && !value['type'].is_a?(::Hash)
|
217
|
+
nil
|
218
|
+
else
|
219
|
+
xml_value = value.inject({}) do |h,(k,v)|
|
220
|
+
h[k] = typecast_xml_value(v)
|
221
|
+
h
|
222
|
+
end
|
223
|
+
|
224
|
+
# Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with
|
225
|
+
# how multipart uploaded files from HTML appear
|
226
|
+
xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value
|
227
|
+
end
|
228
|
+
when 'Array'
|
229
|
+
value.map! { |i| typecast_xml_value(i) }
|
230
|
+
case value.length
|
231
|
+
when 0 then nil
|
232
|
+
when 1 then value.first
|
233
|
+
else value
|
234
|
+
end
|
235
|
+
when 'String'
|
236
|
+
value
|
237
|
+
else
|
238
|
+
raise "can't typecast #{value.class.name} - #{value.inspect}"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def undasherize_keys(params)
|
243
|
+
case params.class.to_s
|
244
|
+
when "Hash"
|
245
|
+
params.inject({}) do |h,(k,v)|
|
246
|
+
h[k.to_s.tr("-", "_")] = undasherize_keys(v)
|
247
|
+
h
|
248
|
+
end
|
249
|
+
when "Array"
|
250
|
+
params.map { |v| undasherize_keys(v) }
|
251
|
+
else
|
252
|
+
params
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|