mongo_mapper-unstable 2010.1.6 → 2010.1.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/VERSION +1 -1
  2. data/lib/mongo_mapper/descendant_appends.rb +44 -0
  3. data/lib/mongo_mapper/document.rb +54 -98
  4. data/lib/mongo_mapper/embedded_document.rb +28 -348
  5. data/lib/mongo_mapper/finder_options.rb +15 -33
  6. data/lib/mongo_mapper/plugins/associations/base.rb +121 -0
  7. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +28 -0
  8. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +23 -0
  9. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  10. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +49 -0
  11. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
  12. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  13. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  14. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  15. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  16. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  17. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +66 -0
  18. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  19. data/lib/mongo_mapper/plugins/associations.rb +104 -0
  20. data/lib/mongo_mapper/plugins/callbacks.rb +65 -0
  21. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  22. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  23. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  24. data/lib/mongo_mapper/plugins/equality.rb +11 -0
  25. data/lib/mongo_mapper/plugins/identity_map.rb +66 -0
  26. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  27. data/lib/mongo_mapper/plugins/keys.rb +295 -0
  28. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  29. data/lib/mongo_mapper/plugins/pagination.rb +85 -0
  30. data/lib/mongo_mapper/plugins/rails.rb +45 -0
  31. data/lib/mongo_mapper/plugins/serialization.rb +109 -0
  32. data/lib/mongo_mapper/plugins/validations.rb +48 -0
  33. data/lib/mongo_mapper/plugins.rb +19 -0
  34. data/lib/mongo_mapper/support.rb +36 -15
  35. data/lib/mongo_mapper.rb +23 -22
  36. data/performance/read_write.rb +52 -0
  37. data/specs.watchr +23 -2
  38. data/test/functional/associations/test_belongs_to_proxy.rb +1 -1
  39. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +58 -39
  40. data/test/functional/associations/test_many_embedded_proxy.rb +103 -69
  41. data/test/functional/test_dirty.rb +1 -1
  42. data/test/functional/test_document.rb +25 -25
  43. data/test/functional/test_embedded_document.rb +66 -63
  44. data/test/functional/test_identity_map.rb +233 -0
  45. data/test/functional/test_modifiers.rb +14 -0
  46. data/test/functional/test_string_id_compatibility.rb +4 -4
  47. data/test/functional/test_validations.rb +13 -0
  48. data/test/models.rb +0 -39
  49. data/test/test_helper.rb +8 -2
  50. data/test/unit/associations/test_base.rb +1 -1
  51. data/test/unit/associations/test_proxy.rb +3 -3
  52. data/test/unit/test_descendant_appends.rb +71 -0
  53. data/test/unit/test_document.rb +35 -46
  54. data/test/unit/test_embedded_document.rb +218 -271
  55. data/test/unit/{test_key.rb → test_keys.rb} +0 -0
  56. data/test/unit/test_pagination.rb +10 -2
  57. data/test/unit/test_plugins.rb +42 -0
  58. data/test/unit/test_rails.rb +123 -0
  59. data/test/unit/{test_serializations.rb → test_serialization.rb} +0 -0
  60. data/test/unit/test_support.rb +10 -6
  61. data/test/unit/test_time_zones.rb +2 -2
  62. metadata +44 -31
  63. data/lib/mongo_mapper/associations/base.rb +0 -119
  64. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +0 -26
  65. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +0 -21
  66. data/lib/mongo_mapper/associations/collection.rb +0 -19
  67. data/lib/mongo_mapper/associations/in_array_proxy.rb +0 -137
  68. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +0 -26
  69. data/lib/mongo_mapper/associations/many_documents_proxy.rb +0 -115
  70. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +0 -31
  71. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +0 -54
  72. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +0 -11
  73. data/lib/mongo_mapper/associations/one_proxy.rb +0 -64
  74. data/lib/mongo_mapper/associations/proxy.rb +0 -116
  75. data/lib/mongo_mapper/associations.rb +0 -78
  76. data/lib/mongo_mapper/callbacks.rb +0 -61
  77. data/lib/mongo_mapper/dirty.rb +0 -117
  78. data/lib/mongo_mapper/key.rb +0 -36
  79. data/lib/mongo_mapper/mongo_mapper.rb +0 -125
  80. data/lib/mongo_mapper/pagination.rb +0 -66
  81. data/lib/mongo_mapper/rails_compatibility/document.rb +0 -15
  82. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +0 -28
  83. data/lib/mongo_mapper/serialization.rb +0 -54
  84. data/lib/mongo_mapper/serializers/json_serializer.rb +0 -48
  85. data/lib/mongo_mapper/validations.rb +0 -39
  86. data/test/functional/test_rails_compatibility.rb +0 -25
@@ -0,0 +1,295 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Keys
4
+ module ClassMethods
5
+ def inherited(descendant)
6
+ descendant.instance_variable_set(:@keys, keys.dup)
7
+ super
8
+ end
9
+
10
+ def keys
11
+ @keys ||= HashWithIndifferentAccess.new
12
+ end
13
+
14
+ def key(*args)
15
+ key = Key.new(*args)
16
+ keys[key.name] = key
17
+
18
+ create_accessors_for(key)
19
+ create_key_in_descendants(*args)
20
+ create_indexes_for(key)
21
+ create_validations_for(key)
22
+
23
+ key
24
+ end
25
+
26
+ def using_object_id?
27
+ object_id_key?(:_id)
28
+ end
29
+
30
+ def object_id_key?(name)
31
+ key = keys[name.to_s]
32
+ key && key.type == ObjectId
33
+ end
34
+
35
+ def to_mongo(instance)
36
+ return nil if instance.nil?
37
+ instance.to_mongo
38
+ end
39
+
40
+ def from_mongo(value)
41
+ return nil if value.nil?
42
+ value.is_a?(self) ? value : load(value)
43
+ end
44
+
45
+ def load(attrs)
46
+ begin
47
+ klass = attrs['_type'].present? ? attrs['_type'].constantize : self
48
+ doc = klass.new(attrs)
49
+ doc.instance_variable_set("@new", false)
50
+ doc
51
+ rescue NameError
52
+ doc = new(attrs)
53
+ doc.instance_variable_set("@new", false)
54
+ doc
55
+ end
56
+ end
57
+
58
+ private
59
+ def accessors_module
60
+ module_defined = if method(:const_defined?).arity == 1 # Ruby 1.9 compat check
61
+ const_defined?('MongoMapperKeys')
62
+ else
63
+ const_defined?('MongoMapperKeys', false)
64
+ end
65
+
66
+ if module_defined
67
+ const_get 'MongoMapperKeys'
68
+ else
69
+ const_set 'MongoMapperKeys', Module.new
70
+ end
71
+ end
72
+
73
+ def create_accessors_for(key)
74
+ accessors_module.module_eval <<-end_eval
75
+ def #{key.name}
76
+ read_key(:#{key.name})
77
+ end
78
+
79
+ def #{key.name}_before_typecast
80
+ read_key_before_typecast(:#{key.name})
81
+ end
82
+
83
+ def #{key.name}=(value)
84
+ write_key(:#{key.name}, value)
85
+ end
86
+
87
+ def #{key.name}?
88
+ read_key(:#{key.name}).present?
89
+ end
90
+ end_eval
91
+
92
+ include accessors_module
93
+ end
94
+
95
+ def create_key_in_descendants(*args)
96
+ return if descendants.blank?
97
+ descendants.each { |descendant| descendant.key(*args) }
98
+ end
99
+
100
+ def create_indexes_for(key)
101
+ ensure_index key.name if key.options[:index] && !key.embeddable?
102
+ end
103
+
104
+ def create_validations_for(key)
105
+ attribute = key.name.to_sym
106
+
107
+ if key.options[:required]
108
+ validates_presence_of(attribute)
109
+ end
110
+
111
+ if key.options[:unique]
112
+ validates_uniqueness_of(attribute)
113
+ end
114
+
115
+ if key.options[:numeric]
116
+ number_options = key.type == Integer ? {:only_integer => true} : {}
117
+ validates_numericality_of(attribute, number_options)
118
+ end
119
+
120
+ if key.options[:format]
121
+ validates_format_of(attribute, :with => key.options[:format])
122
+ end
123
+
124
+ if key.options[:length]
125
+ length_options = case key.options[:length]
126
+ when Integer
127
+ {:minimum => 0, :maximum => key.options[:length]}
128
+ when Range
129
+ {:within => key.options[:length]}
130
+ when Hash
131
+ key.options[:length]
132
+ end
133
+ validates_length_of(attribute, length_options)
134
+ end
135
+ end
136
+ end
137
+
138
+ module InstanceMethods
139
+ def self.included(model)
140
+ model.key :_id, ObjectId
141
+ end
142
+
143
+ def initialize(attrs={})
144
+ unless attrs.nil?
145
+ provided_keys = attrs.keys.map { |k| k.to_s }
146
+ unless provided_keys.include?('_id') || provided_keys.include?('id')
147
+ write_key :_id, Mongo::ObjectID.new
148
+ end
149
+ end
150
+
151
+ @new = true
152
+ self._type = self.class.name if respond_to?(:_type=)
153
+ self.attributes = attrs
154
+ end
155
+
156
+ def new?
157
+ @new
158
+ end
159
+
160
+ def attributes=(attrs)
161
+ return if attrs.blank?
162
+
163
+ attrs.each_pair do |name, value|
164
+ writer_method = "#{name}="
165
+
166
+ if respond_to?(writer_method)
167
+ self.send(writer_method, value)
168
+ else
169
+ self[name.to_s] = value
170
+ end
171
+ end
172
+ end
173
+
174
+ def attributes
175
+ attrs = HashWithIndifferentAccess.new
176
+
177
+ keys.each_pair do |name, key|
178
+ value = key.set(self[key.name])
179
+ attrs[name] = value
180
+ end
181
+
182
+ embedded_associations.each do |association|
183
+ if documents = instance_variable_get(association.ivar)
184
+ attrs[association.name] = documents.map { |document| document.to_mongo }
185
+ end
186
+ end
187
+
188
+ attrs
189
+ end
190
+ alias :to_mongo :attributes
191
+
192
+ def id
193
+ _id
194
+ end
195
+
196
+ def id=(value)
197
+ if self.class.using_object_id?
198
+ value = MongoMapper.normalize_object_id(value)
199
+ end
200
+
201
+ self[:_id] = value
202
+ end
203
+
204
+ def [](name)
205
+ read_key(name)
206
+ end
207
+
208
+ def []=(name, value)
209
+ ensure_key_exists(name)
210
+ write_key(name, value)
211
+ end
212
+
213
+ # @api public
214
+ def keys
215
+ self.class.keys
216
+ end
217
+
218
+ # @api private?
219
+ def key_names
220
+ keys.keys
221
+ end
222
+
223
+ # @api private?
224
+ def non_embedded_keys
225
+ keys.values.select { |key| !key.embeddable? }
226
+ end
227
+
228
+ # @api private?
229
+ def embedded_keys
230
+ keys.values.select { |key| key.embeddable? }
231
+ end
232
+
233
+ private
234
+ def ensure_key_exists(name)
235
+ self.class.key(name) unless respond_to?("#{name}=")
236
+ end
237
+
238
+ def read_key(name)
239
+ if key = keys[name]
240
+ var_name = "@#{name}"
241
+ value = key.get(instance_variable_get(var_name))
242
+ instance_variable_set(var_name, value)
243
+ else
244
+ raise KeyNotFound, "Could not find key: #{name.inspect}"
245
+ end
246
+ end
247
+
248
+ def read_key_before_typecast(name)
249
+ instance_variable_get("@#{name}_before_typecast")
250
+ end
251
+
252
+ def write_key(name, value)
253
+ key = keys[name]
254
+ instance_variable_set "@#{name}_before_typecast", value
255
+ instance_variable_set "@#{name}", key.set(value)
256
+ end
257
+ end
258
+
259
+ class Key
260
+ attr_accessor :name, :type, :options, :default_value
261
+
262
+ def initialize(*args)
263
+ options = args.extract_options!
264
+ @name, @type = args.shift.to_s, args.shift
265
+ self.options = (options || {}).symbolize_keys
266
+ self.default_value = self.options.delete(:default)
267
+ end
268
+
269
+ def ==(other)
270
+ @name == other.name && @type == other.type
271
+ end
272
+
273
+ def embeddable?
274
+ type.respond_to?(:embeddable?) && type.embeddable? ? true : false
275
+ end
276
+
277
+ def number?
278
+ [Integer, Float].include?(type)
279
+ end
280
+
281
+ def get(value)
282
+ if value.nil? && !default_value.nil?
283
+ return default_value
284
+ end
285
+
286
+ type.from_mongo(value)
287
+ end
288
+
289
+ def set(value)
290
+ type.to_mongo(value)
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,17 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Logger
4
+ module ClassMethods
5
+ def logger
6
+ MongoMapper.logger
7
+ end
8
+ end
9
+
10
+ module InstanceMethods
11
+ def logger
12
+ self.class.logger
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,85 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Pagination
4
+ module ClassMethods
5
+ def per_page
6
+ 25
7
+ end
8
+
9
+ def paginate(options)
10
+ per_page = options.delete(:per_page) || self.per_page
11
+ page = options.delete(:page)
12
+ total_entries = count(options)
13
+ pagination = Pagination::PaginationProxy.new(total_entries, page, per_page)
14
+
15
+ options.merge!(:limit => pagination.limit, :skip => pagination.skip)
16
+ pagination.subject = find_every(options)
17
+ pagination
18
+ end
19
+ end
20
+
21
+ class PaginationProxy
22
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
23
+
24
+ attr_accessor :subject
25
+ attr_reader :total_entries, :per_page, :current_page
26
+ alias limit per_page
27
+
28
+ def initialize(total_entries, current_page, per_page=nil)
29
+ @total_entries = total_entries.to_i
30
+ self.per_page = per_page
31
+ self.current_page = current_page
32
+ end
33
+
34
+ def total_pages
35
+ (total_entries / per_page.to_f).ceil
36
+ end
37
+
38
+ def out_of_bounds?
39
+ current_page > total_pages
40
+ end
41
+
42
+ def previous_page
43
+ current_page > 1 ? (current_page - 1) : nil
44
+ end
45
+
46
+ def next_page
47
+ current_page < total_pages ? (current_page + 1) : nil
48
+ end
49
+
50
+ def skip
51
+ (current_page - 1) * per_page
52
+ end
53
+ alias offset skip # for will paginate support
54
+
55
+ def send(method, *args, &block)
56
+ if respond_to?(method)
57
+ super
58
+ else
59
+ subject.send(method, *args, &block)
60
+ end
61
+ end
62
+
63
+ def ===(other)
64
+ other === subject
65
+ end
66
+
67
+ def method_missing(name, *args, &block)
68
+ @subject.send(name, *args, &block)
69
+ end
70
+
71
+ private
72
+ def per_page=(value)
73
+ value = 25 if value.blank?
74
+ @per_page = value.to_i
75
+ end
76
+
77
+ def current_page=(value)
78
+ value = value.to_i
79
+ value = 1 if value < 1
80
+ @current_page = value
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,45 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Rails
4
+ module InstanceMethods
5
+ def to_param
6
+ id.to_s
7
+ end
8
+
9
+ def new_record?
10
+ new?
11
+ end
12
+
13
+ def read_attribute(name)
14
+ self[name]
15
+ end
16
+
17
+ def read_attribute_before_typecast(name)
18
+ read_key_before_typecast(name)
19
+ end
20
+
21
+ def write_attribute(name, value)
22
+ self[name] = value
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+ def has_one(*args)
28
+ one(*args)
29
+ end
30
+
31
+ def has_many(*args)
32
+ many(*args)
33
+ end
34
+
35
+ def column_names
36
+ keys.keys
37
+ end
38
+
39
+ def human_name
40
+ self.name.demodulize.titleize
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,109 @@
1
+ require 'active_support/json'
2
+
3
+ module MongoMapper
4
+ module Plugins
5
+ module Serialization
6
+ class Serializer
7
+ attr_reader :options
8
+
9
+ def initialize(record, options={})
10
+ @record, @options = record, options.dup
11
+ end
12
+
13
+ def serializable_key_names
14
+ key_names = @record.attributes.keys
15
+
16
+ if options[:only]
17
+ options.delete(:except)
18
+ key_names = key_names & Array(options[:only]).collect { |n| n.to_s }
19
+ else
20
+ options[:except] = Array(options[:except])
21
+ key_names = key_names - options[:except].collect { |n| n.to_s }
22
+ end
23
+
24
+ key_names
25
+ end
26
+
27
+ def serializable_method_names
28
+ Array(options[:methods]).inject([]) do |method_attributes, name|
29
+ method_attributes << name if @record.respond_to?(name.to_s)
30
+ method_attributes
31
+ end
32
+ end
33
+
34
+ def serializable_names
35
+ serializable_key_names + serializable_method_names
36
+ end
37
+
38
+ def serializable_record
39
+ returning(serializable_record = {}) do
40
+ serializable_names.each { |name| serializable_record[name] = @record.send(name) }
41
+ end
42
+ end
43
+
44
+ def serialize
45
+ # overwrite to implement
46
+ end
47
+
48
+ def to_s(&block)
49
+ serialize(&block)
50
+ end
51
+ end
52
+
53
+ module Json
54
+ def self.included(base)
55
+ base.cattr_accessor :include_root_in_json, :instance_writer => false
56
+ base.extend ClassMethods
57
+ end
58
+
59
+ module ClassMethods
60
+ def json_class_name
61
+ @json_class_name ||= name.demodulize.underscore.inspect
62
+ end
63
+ end
64
+
65
+ def to_json(options={})
66
+ apply_to_json_defaults(options)
67
+
68
+ if include_root_in_json
69
+ "{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
70
+ else
71
+ JsonSerializer.new(self, options).to_s
72
+ end
73
+ end
74
+
75
+ def from_json(json)
76
+ self.attributes = ActiveSupport::JSON.decode(json)
77
+ self
78
+ end
79
+
80
+ class JsonSerializer < Serializer
81
+ def serialize
82
+ serializable_record.to_json
83
+ end
84
+ end
85
+
86
+ private
87
+ def apply_to_json_defaults(options)
88
+ unless options[:only]
89
+ methods = [options.delete(:methods)].flatten.compact
90
+ methods << :id
91
+ options[:methods] = methods.uniq
92
+ end
93
+
94
+ except = [options.delete(:except)].flatten.compact
95
+ except << :_id
96
+ options[:except] = except
97
+ end
98
+ end
99
+
100
+ module InstanceMethods
101
+ def self.included(model)
102
+ model.class_eval do
103
+ include Json
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,48 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Validations
4
+ module InstanceMethods
5
+ def self.included(model)
6
+ model.class_eval { include Validatable }
7
+ end
8
+ end
9
+
10
+ module DocumentMacros
11
+ def validates_uniqueness_of(*args)
12
+ add_validations(args, MongoMapper::Plugins::Validations::ValidatesUniquenessOf)
13
+ end
14
+ end
15
+
16
+ class ValidatesUniquenessOf < Validatable::ValidationBase
17
+ option :scope, :case_sensitive
18
+ default :case_sensitive => true
19
+
20
+ def valid?(instance)
21
+ value = instance[attribute]
22
+ return true if allow_blank && value.blank?
23
+ return true if allow_nil && value.nil?
24
+ base_conditions = case_sensitive ? {self.attribute => value} : {}
25
+ doc = instance.class.first(base_conditions.merge(scope_conditions(instance)).merge(where_conditions(instance)))
26
+ doc.nil? || instance._id == doc._id
27
+ end
28
+
29
+ def message(instance)
30
+ super || "has already been taken"
31
+ end
32
+
33
+ def scope_conditions(instance)
34
+ return {} unless scope
35
+ Array(scope).inject({}) do |conditions, key|
36
+ conditions.merge(key => instance[key])
37
+ end
38
+ end
39
+
40
+ def where_conditions(instance)
41
+ conditions = {}
42
+ conditions[attribute] = /#{instance[attribute].to_s}/i unless case_sensitive
43
+ conditions
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ def plugins
4
+ @plugins ||= []
5
+ end
6
+
7
+ def plugin(mod)
8
+ if mod.const_defined?(:ClassMethods)
9
+ extend mod::ClassMethods
10
+ end
11
+
12
+ if mod.const_defined?(:InstanceMethods)
13
+ include mod::InstanceMethods
14
+ end
15
+
16
+ plugins << mod
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,3 @@
1
- require 'set'
2
-
3
1
  class Array
4
2
  def self.to_mongo(value)
5
3
  value = value.respond_to?(:lines) ? value.lines : value
@@ -26,11 +24,18 @@ class Binary
26
24
  end
27
25
 
28
26
  class Boolean
27
+ BOOLEAN_MAPPING = {
28
+ true => true, 'true' => true, 'TRUE' => true, 'True' => true, 't' => true, 'T' => true, '1' => true, 1 => true, 1.0 => true,
29
+ false => false, 'false' => false, 'FALSE' => false, 'False' => false, 'f' => false, 'F' => false, '0' => false, 0 => false, 0.0 => false, nil => false
30
+ }
31
+
29
32
  def self.to_mongo(value)
30
33
  if value.is_a?(Boolean)
31
34
  value
32
35
  else
33
- ['true', 't', '1'].include?(value.to_s.downcase)
36
+ v = BOOLEAN_MAPPING[value]
37
+ v = value.to_s.downcase == 'true' if v.nil? # Check all mixed case spellings for true
38
+ v
34
39
  end
35
40
  end
36
41
 
@@ -41,8 +46,12 @@ end
41
46
 
42
47
  class Date
43
48
  def self.to_mongo(value)
44
- date = Date.parse(value.to_s)
45
- Time.utc(date.year, date.month, date.day)
49
+ if value.nil? || value == ''
50
+ nil
51
+ else
52
+ date = value.is_a?(Date) || value.is_a?(Time) ? value : Date.parse(value.to_s)
53
+ Time.utc(date.year, date.month, date.day)
54
+ end
46
55
  rescue
47
56
  nil
48
57
  end
@@ -71,7 +80,7 @@ end
71
80
  class Integer
72
81
  def self.to_mongo(value)
73
82
  value_to_i = value.to_i
74
- if value_to_i == 0
83
+ if value_to_i == 0 && value != value_to_i
75
84
  value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
76
85
  else
77
86
  value_to_i
@@ -154,15 +163,26 @@ class String
154
163
  end
155
164
  end
156
165
 
157
- class Symbol
158
- %w{gt lt gte lte ne in nin mod size where exists}.each do |operator|
159
- define_method operator do
160
- MongoMapper::FinderOperator.new(self, "$#{operator}")
161
- end
166
+ class SymbolOperator
167
+ def initialize(field, operator, options={})
168
+ @field, @operator = field, operator
169
+ end unless method_defined?(:initialize)
170
+
171
+ def to_mm_criteria(value)
172
+ {MongoMapper::FinderOptions.normalized_field(@field) => {"$#{@operator}" => value}}
162
173
  end
163
174
 
164
- def asc; MongoMapper::OrderOperator.new(self, 'asc') end
165
- def desc; MongoMapper::OrderOperator.new(self, 'desc') end
175
+ def to_mm_order
176
+ [@field.to_s, MongoMapper::FinderOptions.normalized_order_direction(@operator)]
177
+ end
178
+ end
179
+
180
+ class Symbol
181
+ %w(gt lt gte lte ne in nin mod size where exists asc desc).each do |operator|
182
+ define_method(operator) do
183
+ SymbolOperator.new(self, operator)
184
+ end unless method_defined?(operator)
185
+ end
166
186
  end
167
187
 
168
188
  class Time
@@ -170,8 +190,9 @@ class Time
170
190
  if value.nil? || value == ''
171
191
  nil
172
192
  else
173
- time = MongoMapper.time_class.parse(value.to_s)
174
- time && time.utc
193
+ time = value.is_a?(Time) ? value : MongoMapper.time_class.parse(value.to_s)
194
+ # Convert time to milliseconds since BSON stores dates with that accurracy, but Ruby uses microseconds
195
+ Time.at((time.to_f * 1000).round / 1000.0).utc if time
175
196
  end
176
197
  end
177
198