ant-mapper 0.0.2
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 +6 -0
- data/MIT-LICENSE +20 -0
- data/README +25 -0
- data/ant.gemspec +48 -0
- data/data.tch +0 -0
- data/examples/account.rb +71 -0
- data/examples/data.tch +0 -0
- data/examples/light_cloud.yml +18 -0
- data/examples/user.rb +84 -0
- data/init.rb +4 -0
- data/lib/ant.rb +7 -0
- data/lib/ant_mapper.rb +10 -0
- data/lib/ant_mapper/adapters/light_cloud.rb +59 -0
- data/lib/ant_mapper/adapters/tokyo_cabinet.rb +42 -0
- data/lib/ant_mapper/adapters/tokyo_tyrant.rb +14 -0
- data/lib/ant_mapper/base.rb +367 -0
- data/lib/ant_mapper/callbacks.rb +180 -0
- data/lib/ant_mapper/observer.rb +180 -0
- data/lib/ant_mapper/validations.rb +687 -0
- data/lib/ant_support.rb +4 -0
- data/lib/ant_support/callbacks.rb +303 -0
- data/lib/ant_support/core_ext.rb +4 -0
- data/lib/ant_support/core_ext/array.rb +5 -0
- data/lib/ant_support/core_ext/array/extract_options.rb +20 -0
- data/lib/ant_support/core_ext/blank.rb +58 -0
- data/lib/ant_support/core_ext/class.rb +3 -0
- data/lib/ant_support/core_ext/class/attribute_accessors.rb +54 -0
- data/lib/ant_support/core_ext/class/inheritable_attributes.rb +140 -0
- data/lib/ant_support/core_ext/class/removal.rb +50 -0
- data/lib/ant_support/core_ext/duplicable.rb +43 -0
- data/lib/ant_support/core_ext/enumerable.rb +72 -0
- data/lib/ant_support/core_ext/hash.rb +6 -0
- data/lib/ant_support/core_ext/hash/keys.rb +52 -0
- data/lib/ant_support/core_ext/module.rb +16 -0
- data/lib/ant_support/core_ext/module/aliasing.rb +74 -0
- data/lib/ant_support/core_ext/module/attr_accessor_with_default.rb +31 -0
- data/lib/ant_support/core_ext/module/attribute_accessors.rb +58 -0
- data/lib/ant_support/core_ext/object.rb +1 -0
- data/lib/ant_support/core_ext/object/extending.rb +80 -0
- data/lib/ant_support/core_ext/string.rb +7 -0
- data/lib/ant_support/core_ext/string/inflections.rb +51 -0
- data/spec/case/callbacks_observers_test.rb +38 -0
- data/spec/case/callbacks_test.rb +417 -0
- data/spec/case/create_object_test.rb +56 -0
- data/spec/case/set_class_name_test.rb +17 -0
- data/spec/case/validations_test.rb +1482 -0
- data/spec/helper.rb +15 -0
- data/spec/light_cloud.yml +18 -0
- data/spec/model/account.rb +3 -0
- data/spec/model/topic.rb +28 -0
- data/spec/model/user.rb +4 -0
- metadata +125 -0
@@ -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,52 @@
|
|
1
|
+
module AntSupport #:nodoc:
|
2
|
+
module CoreExtensions #:nodoc:
|
3
|
+
module Hash #:nodoc:
|
4
|
+
module Keys
|
5
|
+
# Return a new hash with all keys converted to strings.
|
6
|
+
def stringify_keys
|
7
|
+
inject({}) do |options, (key, value)|
|
8
|
+
options[key.to_s] = value
|
9
|
+
options
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Destructively convert all keys to strings.
|
14
|
+
def stringify_keys!
|
15
|
+
keys.each do |key|
|
16
|
+
self[key.to_s] = delete(key)
|
17
|
+
end
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return a new hash with all keys converted to symbols.
|
22
|
+
def symbolize_keys
|
23
|
+
inject({}) do |options, (key, value)|
|
24
|
+
options[(key.to_sym rescue key) || key] = value
|
25
|
+
options
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Destructively convert all keys to symbols.
|
30
|
+
def symbolize_keys!
|
31
|
+
self.replace(self.symbolize_keys)
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :to_options, :symbolize_keys
|
35
|
+
alias_method :to_options!, :symbolize_keys!
|
36
|
+
|
37
|
+
# Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
|
38
|
+
# Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
|
39
|
+
# as keys, this will fail.
|
40
|
+
#
|
41
|
+
# ==== Examples:
|
42
|
+
# { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
|
43
|
+
# { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age"
|
44
|
+
# { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
45
|
+
def assert_valid_keys(*valid_keys)
|
46
|
+
unknown_keys = keys - [valid_keys].flatten
|
47
|
+
raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'ant_support/core_ext/module/attribute_accessors'
|
2
|
+
require 'ant_support/core_ext/module/attr_accessor_with_default'
|
3
|
+
require 'ant_support/core_ext/module/aliasing'
|
4
|
+
|
5
|
+
module AntSupport
|
6
|
+
module CoreExtensions
|
7
|
+
# Various extensions for the Ruby core Module class.
|
8
|
+
module Module
|
9
|
+
# Nothing here. Only defined for API documentation purposes.
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Module
|
15
|
+
include AntSupport::CoreExtensions::Module
|
16
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module AntSupport
|
2
|
+
module CoreExtensions
|
3
|
+
module Module
|
4
|
+
# Encapsulates the common pattern of:
|
5
|
+
#
|
6
|
+
# alias_method :foo_without_feature, :foo
|
7
|
+
# alias_method :foo, :foo_with_feature
|
8
|
+
#
|
9
|
+
# With this, you simply do:
|
10
|
+
#
|
11
|
+
# alias_method_chain :foo, :feature
|
12
|
+
#
|
13
|
+
# And both aliases are set up for you.
|
14
|
+
#
|
15
|
+
# Query and bang methods (foo?, foo!) keep the same punctuation:
|
16
|
+
#
|
17
|
+
# alias_method_chain :foo?, :feature
|
18
|
+
#
|
19
|
+
# is equivalent to
|
20
|
+
#
|
21
|
+
# alias_method :foo_without_feature?, :foo?
|
22
|
+
# alias_method :foo?, :foo_with_feature?
|
23
|
+
#
|
24
|
+
# so you can safely chain foo, foo?, and foo! with the same feature.
|
25
|
+
def alias_method_chain(target, feature)
|
26
|
+
# Strip out punctuation on predicates or bang methods since
|
27
|
+
# e.g. target?_without_feature is not a valid method name.
|
28
|
+
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
|
29
|
+
yield(aliased_target, punctuation) if block_given?
|
30
|
+
|
31
|
+
with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
|
32
|
+
|
33
|
+
alias_method without_method, target
|
34
|
+
alias_method target, with_method
|
35
|
+
|
36
|
+
case
|
37
|
+
when public_method_defined?(without_method)
|
38
|
+
public target
|
39
|
+
when protected_method_defined?(without_method)
|
40
|
+
protected target
|
41
|
+
when private_method_defined?(without_method)
|
42
|
+
private target
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Allows you to make aliases for attributes, which includes
|
47
|
+
# getter, setter, and query methods.
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
#
|
51
|
+
# class Content < AntMapper::Base
|
52
|
+
# # has a title attribute
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# class Email < Content
|
56
|
+
# alias_attribute :subject, :title
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# e = Email.find(1)
|
60
|
+
# e.title # => "Superstars"
|
61
|
+
# e.subject # => "Superstars"
|
62
|
+
# e.subject? # => true
|
63
|
+
# e.subject = "Megastars"
|
64
|
+
# e.title # => "Megastars"
|
65
|
+
def alias_attribute(new_name, old_name)
|
66
|
+
module_eval <<-STR, __FILE__, __LINE__+1
|
67
|
+
def #{new_name}; self.#{old_name}; end
|
68
|
+
def #{new_name}?; self.#{old_name}?; end
|
69
|
+
def #{new_name}=(v); self.#{old_name} = v; end
|
70
|
+
STR
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|