djsun-mongomapper 0.3.5.5 → 0.4.1.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/README.rdoc +38 -38
- data/Rakefile +87 -73
- data/VERSION +1 -1
- data/lib/mongomapper.rb +67 -71
- data/lib/mongomapper/associations.rb +86 -84
- data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +34 -34
- data/lib/mongomapper/associations/many_embedded_proxy.rb +67 -17
- data/lib/mongomapper/associations/proxy.rb +74 -73
- data/lib/mongomapper/document.rb +342 -348
- data/lib/mongomapper/embedded_document.rb +354 -274
- data/lib/mongomapper/finder_options.rb +84 -84
- data/lib/mongomapper/key.rb +32 -76
- data/lib/mongomapper/rails_compatibility/document.rb +14 -14
- data/lib/mongomapper/rails_compatibility/embedded_document.rb +26 -24
- data/lib/mongomapper/support.rb +156 -29
- data/lib/mongomapper/validations.rb +69 -47
- data/test/custom_matchers.rb +48 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +53 -56
- data/test/functional/associations/test_belongs_to_proxy.rb +48 -49
- data/test/functional/associations/test_many_documents_as_proxy.rb +208 -253
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +130 -130
- data/test/functional/associations/test_many_embedded_proxy.rb +168 -106
- data/test/functional/associations/test_many_polymorphic_proxy.rb +261 -262
- data/test/functional/test_binary.rb +21 -0
- data/test/functional/test_document.rb +946 -952
- data/test/functional/test_embedded_document.rb +98 -0
- data/test/functional/test_pagination.rb +87 -80
- data/test/functional/test_rails_compatibility.rb +29 -29
- data/test/functional/test_validations.rb +262 -172
- data/test/models.rb +169 -169
- data/test/test_helper.rb +28 -66
- data/test/unit/serializers/test_json_serializer.rb +193 -193
- data/test/unit/test_document.rb +161 -123
- data/test/unit/test_embedded_document.rb +643 -547
- data/test/unit/test_finder_options.rb +183 -183
- data/test/unit/test_key.rb +175 -247
- data/test/unit/test_rails_compatibility.rb +38 -33
- data/test/unit/test_serializations.rb +52 -52
- data/test/unit/test_support.rb +268 -0
- data/test/unit/test_time_zones.rb +40 -0
- data/test/unit/test_validations.rb +499 -258
- metadata +22 -12
- data/History +0 -76
- data/mongomapper.gemspec +0 -145
@@ -1,85 +1,85 @@
|
|
1
|
-
module MongoMapper
|
2
|
-
class FinderOptions
|
3
|
-
attr_reader :options
|
4
|
-
|
5
|
-
def self.to_mongo_criteria(conditions, parent_key=nil)
|
6
|
-
criteria = {}
|
7
|
-
conditions.each_pair do |field, value|
|
8
|
-
case value
|
9
|
-
when Array
|
10
|
-
operator_present = field.to_s =~ /^\$/
|
11
|
-
criteria[field] = if operator_present
|
12
|
-
value
|
13
|
-
else
|
14
|
-
{'$in' => value}
|
15
|
-
end
|
16
|
-
when Hash
|
17
|
-
criteria[field] = to_mongo_criteria(value, field)
|
18
|
-
else
|
19
|
-
criteria[field] = value
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
criteria
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.to_mongo_options(options)
|
27
|
-
options = options.dup
|
28
|
-
{
|
29
|
-
:fields => to_mongo_fields(options.delete(:fields) || options.delete(:select)),
|
30
|
-
:
|
31
|
-
:limit => (options.delete(:limit) || 0).to_i,
|
32
|
-
:sort => options.delete(:sort) || to_mongo_sort(options.delete(:order))
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
def initialize(options)
|
37
|
-
raise ArgumentError, "FinderOptions must be a hash" unless options.is_a?(Hash)
|
38
|
-
@options = options.symbolize_keys
|
39
|
-
@conditions = @options.delete(:conditions) || {}
|
40
|
-
end
|
41
|
-
|
42
|
-
def criteria
|
43
|
-
self.class.to_mongo_criteria(@conditions)
|
44
|
-
end
|
45
|
-
|
46
|
-
def options
|
47
|
-
self.class.to_mongo_options(@options)
|
48
|
-
end
|
49
|
-
|
50
|
-
def to_a
|
51
|
-
[criteria, options]
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
def self.to_mongo_fields(fields)
|
56
|
-
return if fields.blank?
|
57
|
-
|
58
|
-
if fields.is_a?(String)
|
59
|
-
fields.split(',').map { |field| field.strip }
|
60
|
-
else
|
61
|
-
fields.flatten.compact
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.to_mongo_sort(sort)
|
66
|
-
return if sort.blank?
|
67
|
-
pieces = sort.split(',')
|
68
|
-
pairs = pieces.map { |s| to_mongo_sort_piece(s) }
|
69
|
-
|
70
|
-
hash = OrderedHash.new
|
71
|
-
pairs.each do |pair|
|
72
|
-
field, sort_direction = pair
|
73
|
-
hash[field] = sort_direction
|
74
|
-
end
|
75
|
-
hash.symbolize_keys
|
76
|
-
end
|
77
|
-
|
78
|
-
def self.to_mongo_sort_piece(str)
|
79
|
-
field, direction = str.strip.split(' ')
|
80
|
-
direction ||= 'ASC'
|
81
|
-
direction = direction.upcase == 'ASC' ? 1 : -1
|
82
|
-
[field, direction]
|
83
|
-
end
|
84
|
-
end
|
1
|
+
module MongoMapper
|
2
|
+
class FinderOptions
|
3
|
+
attr_reader :options
|
4
|
+
|
5
|
+
def self.to_mongo_criteria(conditions, parent_key=nil)
|
6
|
+
criteria = {}
|
7
|
+
conditions.each_pair do |field, value|
|
8
|
+
case value
|
9
|
+
when Array
|
10
|
+
operator_present = field.to_s =~ /^\$/
|
11
|
+
criteria[field] = if operator_present
|
12
|
+
value
|
13
|
+
else
|
14
|
+
{'$in' => value}
|
15
|
+
end
|
16
|
+
when Hash
|
17
|
+
criteria[field] = to_mongo_criteria(value, field)
|
18
|
+
else
|
19
|
+
criteria[field] = value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
criteria
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.to_mongo_options(options)
|
27
|
+
options = options.dup
|
28
|
+
{
|
29
|
+
:fields => to_mongo_fields(options.delete(:fields) || options.delete(:select)),
|
30
|
+
:skip => (options.delete(:skip) || 0).to_i,
|
31
|
+
:limit => (options.delete(:limit) || 0).to_i,
|
32
|
+
:sort => options.delete(:sort) || to_mongo_sort(options.delete(:order))
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(options)
|
37
|
+
raise ArgumentError, "FinderOptions must be a hash" unless options.is_a?(Hash)
|
38
|
+
@options = options.symbolize_keys
|
39
|
+
@conditions = @options.delete(:conditions) || {}
|
40
|
+
end
|
41
|
+
|
42
|
+
def criteria
|
43
|
+
self.class.to_mongo_criteria(@conditions)
|
44
|
+
end
|
45
|
+
|
46
|
+
def options
|
47
|
+
self.class.to_mongo_options(@options)
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_a
|
51
|
+
[criteria, options]
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def self.to_mongo_fields(fields)
|
56
|
+
return if fields.blank?
|
57
|
+
|
58
|
+
if fields.is_a?(String)
|
59
|
+
fields.split(',').map { |field| field.strip }
|
60
|
+
else
|
61
|
+
fields.flatten.compact
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.to_mongo_sort(sort)
|
66
|
+
return if sort.blank?
|
67
|
+
pieces = sort.split(',')
|
68
|
+
pairs = pieces.map { |s| to_mongo_sort_piece(s) }
|
69
|
+
|
70
|
+
hash = OrderedHash.new
|
71
|
+
pairs.each do |pair|
|
72
|
+
field, sort_direction = pair
|
73
|
+
hash[field] = sort_direction
|
74
|
+
end
|
75
|
+
hash.symbolize_keys
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.to_mongo_sort_piece(str)
|
79
|
+
field, direction = str.strip.split(' ')
|
80
|
+
direction ||= 'ASC'
|
81
|
+
direction = direction.upcase == 'ASC' ? 1 : -1
|
82
|
+
[field, direction]
|
83
|
+
end
|
84
|
+
end
|
85
85
|
end
|
data/lib/mongomapper/key.rb
CHANGED
@@ -1,76 +1,32 @@
|
|
1
|
-
module MongoMapper
|
2
|
-
class Key
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
if type == Array
|
34
|
-
value || []
|
35
|
-
elsif type == Hash
|
36
|
-
HashWithIndifferentAccess.new(value || {})
|
37
|
-
else
|
38
|
-
value
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
def typecast(value)
|
44
|
-
return value if type.nil?
|
45
|
-
return HashWithIndifferentAccess.new(value) if value.is_a?(Hash) && type == Hash
|
46
|
-
return value.utc if type == Time && value.kind_of?(type)
|
47
|
-
return value if value.kind_of?(type) || value.nil?
|
48
|
-
begin
|
49
|
-
if type == String then value.to_s
|
50
|
-
elsif type == Float then value.to_f
|
51
|
-
elsif type == Array then value.to_a
|
52
|
-
elsif type == Time then Time.parse(value.to_s).utc
|
53
|
-
elsif type == Boolean then Boolean.mm_typecast(value)
|
54
|
-
elsif type == Integer
|
55
|
-
# ganked from datamapper
|
56
|
-
value_to_i = value.to_i
|
57
|
-
if value_to_i == 0
|
58
|
-
value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
|
59
|
-
else
|
60
|
-
value_to_i
|
61
|
-
end
|
62
|
-
elsif embedded_document?
|
63
|
-
typecast_embedded_document(value)
|
64
|
-
else
|
65
|
-
value
|
66
|
-
end
|
67
|
-
rescue
|
68
|
-
value
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def typecast_embedded_document(value)
|
73
|
-
value.is_a?(type) ? value : type.new(value)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
1
|
+
module MongoMapper
|
2
|
+
class Key
|
3
|
+
attr_accessor :name, :type, :options, :default_value
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
options = args.extract_options!
|
7
|
+
@name, @type = args.shift.to_s, args.shift
|
8
|
+
self.options = (options || {}).symbolize_keys
|
9
|
+
self.default_value = self.options.delete(:default)
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
@name == other.name && @type == other.type
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(value)
|
17
|
+
type.to_mongo(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def embeddable?
|
21
|
+
type.respond_to?(:embeddable?) && type.embeddable? ? true : false
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(value)
|
25
|
+
if value.nil? && !default_value.nil?
|
26
|
+
return default_value
|
27
|
+
end
|
28
|
+
|
29
|
+
type.from_mongo(value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,15 +1,15 @@
|
|
1
|
-
module MongoMapper
|
2
|
-
module RailsCompatibility
|
3
|
-
module Document
|
4
|
-
def self.included(model)
|
5
|
-
model.class_eval do
|
6
|
-
alias_method :new_record?, :new?
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
def to_param
|
11
|
-
id
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
1
|
+
module MongoMapper
|
2
|
+
module RailsCompatibility
|
3
|
+
module Document
|
4
|
+
def self.included(model)
|
5
|
+
model.class_eval do
|
6
|
+
alias_method :new_record?, :new?
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_param
|
11
|
+
id
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
15
|
end
|
@@ -1,25 +1,27 @@
|
|
1
|
-
module MongoMapper
|
2
|
-
module RailsCompatibility
|
3
|
-
module EmbeddedDocument
|
4
|
-
def self.included(model)
|
5
|
-
model.class_eval do
|
6
|
-
extend ClassMethods
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
1
|
+
module MongoMapper
|
2
|
+
module RailsCompatibility
|
3
|
+
module EmbeddedDocument
|
4
|
+
def self.included(model)
|
5
|
+
model.class_eval do
|
6
|
+
extend ClassMethods
|
7
|
+
|
8
|
+
alias_method :new_record?, :new?
|
9
|
+
end
|
10
|
+
|
11
|
+
class << model
|
12
|
+
alias has_many many
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def column_names
|
18
|
+
keys.keys
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_param
|
23
|
+
raise "Missing to_param method in #{self.class}. You should implement it to return the unique identifier of this embedded document within a document."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
25
27
|
end
|
data/lib/mongomapper/support.rb
CHANGED
@@ -1,30 +1,157 @@
|
|
1
|
-
class BasicObject #:nodoc:
|
2
|
-
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|instance_eval|proxy_|^object_id$)/ }
|
3
|
-
end unless defined?(BasicObject)
|
4
|
-
|
5
|
-
class
|
6
|
-
def self.
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
1
|
+
class BasicObject #:nodoc:
|
2
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|^methods$|instance_eval|proxy_|^object_id$)/ }
|
3
|
+
end unless defined?(BasicObject)
|
4
|
+
|
5
|
+
class Array
|
6
|
+
def self.to_mongo(value)
|
7
|
+
value.to_a
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.from_mongo(value)
|
11
|
+
value || []
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Binary
|
16
|
+
def self.to_mongo(value)
|
17
|
+
if value.is_a?(ByteBuffer)
|
18
|
+
value
|
19
|
+
else
|
20
|
+
ByteBuffer.new(value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_mongo(value)
|
25
|
+
value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Boolean
|
30
|
+
def self.to_mongo(value)
|
31
|
+
if value.is_a?(Boolean)
|
32
|
+
value
|
33
|
+
else
|
34
|
+
['true', 't', '1'].include?(value.to_s.downcase)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.from_mongo(value)
|
39
|
+
!!value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Date
|
44
|
+
def self.to_mongo(value)
|
45
|
+
date = Date.parse(value.to_s)
|
46
|
+
Time.utc(date.year, date.month, date.day)
|
47
|
+
rescue
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.from_mongo(value)
|
52
|
+
value.to_date if value.present?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Float
|
57
|
+
def self.to_mongo(value)
|
58
|
+
value.to_f
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Hash
|
63
|
+
def self.from_mongo(value)
|
64
|
+
HashWithIndifferentAccess.new(value || {})
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_mongo
|
68
|
+
self
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Integer
|
73
|
+
def self.to_mongo(value)
|
74
|
+
value_to_i = value.to_i
|
75
|
+
if value_to_i == 0
|
76
|
+
value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
|
77
|
+
else
|
78
|
+
value_to_i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class NilClass
|
84
|
+
def to_mongo(value)
|
85
|
+
value
|
86
|
+
end
|
87
|
+
|
88
|
+
def from_mongo(value)
|
89
|
+
value
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Object
|
94
|
+
# The hidden singleton lurks behind everyone
|
95
|
+
def metaclass
|
96
|
+
class << self; self end
|
97
|
+
end
|
98
|
+
|
99
|
+
def meta_eval(&blk)
|
100
|
+
metaclass.instance_eval(&blk)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Adds methods to a metaclass
|
104
|
+
def meta_def(name, &blk)
|
105
|
+
meta_eval { define_method(name, &blk) }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Defines an instance method within a class
|
109
|
+
def class_def(name, &blk)
|
110
|
+
class_eval { define_method(name, &blk) }
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.to_mongo(value)
|
114
|
+
value
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.from_mongo(value)
|
118
|
+
value
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class String
|
123
|
+
def self.to_mongo(value)
|
124
|
+
value.nil? ? nil : value.to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.from_mongo(value)
|
128
|
+
value.present? ? value : nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Time
|
133
|
+
def self.to_mongo(value)
|
134
|
+
to_utc_time(value)
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.from_mongo(value)
|
138
|
+
if Time.respond_to?(:zone) && Time.zone && value.present?
|
139
|
+
value.in_time_zone(Time.zone)
|
140
|
+
else
|
141
|
+
value
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.to_utc_time(value)
|
146
|
+
to_local_time(value).try(:utc)
|
147
|
+
end
|
148
|
+
|
149
|
+
# make sure we have a time and that it is local
|
150
|
+
def self.to_local_time(value)
|
151
|
+
if Time.respond_to?(:zone) && Time.zone
|
152
|
+
Time.zone.parse(value.to_s)
|
153
|
+
else
|
154
|
+
Time.parse(value.to_s)
|
155
|
+
end
|
156
|
+
end
|
30
157
|
end
|