mongo_mapper 0.7.3 → 0.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Rakefile +3 -2
  2. data/lib/mongo_mapper.rb +2 -3
  3. data/lib/mongo_mapper/plugins/associations.rb +10 -1
  4. data/lib/mongo_mapper/plugins/associations/base.rb +2 -2
  5. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +12 -3
  6. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +41 -0
  7. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +1 -0
  8. data/lib/mongo_mapper/plugins/associations/proxy.rb +8 -2
  9. data/lib/mongo_mapper/plugins/callbacks.rb +7 -3
  10. data/lib/mongo_mapper/plugins/descendants.rb +2 -2
  11. data/lib/mongo_mapper/plugins/keys.rb +14 -7
  12. data/lib/mongo_mapper/plugins/modifiers.rb +30 -14
  13. data/lib/mongo_mapper/plugins/protected.rb +1 -1
  14. data/lib/mongo_mapper/plugins/serialization.rb +1 -1
  15. data/lib/mongo_mapper/query.rb +27 -19
  16. data/lib/mongo_mapper/support.rb +10 -6
  17. data/lib/mongo_mapper/version.rb +1 -1
  18. data/mongo_mapper.gemspec +14 -8
  19. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +1 -1
  20. data/test/functional/associations/test_belongs_to_proxy.rb +1 -1
  21. data/test/functional/associations/test_many_documents_proxy.rb +100 -17
  22. data/test/functional/associations/test_one_embedded_proxy.rb +68 -0
  23. data/test/functional/associations/test_one_proxy.rb +48 -13
  24. data/test/functional/test_binary.rb +1 -1
  25. data/test/functional/test_document.rb +7 -7
  26. data/test/functional/test_embedded_document.rb +8 -0
  27. data/test/functional/test_identity_map.rb +2 -2
  28. data/test/functional/test_modifiers.rb +249 -185
  29. data/test/functional/test_protected.rb +4 -0
  30. data/test/functional/test_string_id_compatibility.rb +1 -1
  31. data/test/support/custom_matchers.rb +0 -18
  32. data/test/test_helper.rb +6 -4
  33. data/test/unit/associations/test_base.rb +7 -2
  34. data/test/unit/test_document.rb +5 -5
  35. data/test/unit/test_embedded_document.rb +7 -7
  36. data/test/unit/test_query.rb +17 -7
  37. data/test/unit/test_support.rb +26 -14
  38. metadata +33 -16
data/Rakefile CHANGED
@@ -13,9 +13,10 @@ Jeweler::Tasks.new do |gem|
13
13
  gem.version = MongoMapper::Version
14
14
 
15
15
  gem.add_dependency('activesupport', '>= 2.3.4')
16
- gem.add_dependency('mongo', '0.19.3')
17
- gem.add_dependency('jnunemaker-validatable', '1.8.3')
16
+ gem.add_dependency('mongo', '0.20.1')
17
+ gem.add_dependency('jnunemaker-validatable', '1.8.4')
18
18
 
19
+ gem.add_development_dependency('json', '>= 1.2.3')
19
20
  gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
20
21
  gem.add_development_dependency('shoulda', '2.10.2')
21
22
  gem.add_development_dependency('timecop', '0.3.1')
@@ -1,8 +1,7 @@
1
1
  # Make sure you have the following libs in your load path or you could have issues:
2
2
  # gem 'activesupport', '>= 2.3.4'
3
- # gem 'mongo', '0.19.3'
4
- # gem 'jnunemaker-validatable', '1.8.3'
5
- # gem 'activesupport', '= 2.3.4'
3
+ # gem 'mongo', '0.20.1'
4
+ # gem 'jnunemaker-validatable', '1.8.4'
6
5
  require 'set'
7
6
  require 'uri'
8
7
  require 'mongo'
@@ -84,6 +84,14 @@ module MongoMapper
84
84
 
85
85
  proxy
86
86
  end
87
+
88
+ def save_to_collection(options = {})
89
+ super
90
+ associations.each do |association_name, association|
91
+ proxy = get_proxy(association)
92
+ proxy.save_to_collection(options) if proxy.proxy_respond_to?(:save_to_collection)
93
+ end
94
+ end
87
95
  end
88
96
 
89
97
  autoload :Base, 'mongo_mapper/plugins/associations/base'
@@ -97,9 +105,10 @@ module MongoMapper
97
105
  autoload :ManyEmbeddedPolymorphicProxy, 'mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy'
98
106
  autoload :ManyDocumentsAsProxy, 'mongo_mapper/plugins/associations/many_documents_as_proxy'
99
107
  autoload :OneProxy, 'mongo_mapper/plugins/associations/one_proxy'
108
+ autoload :OneEmbeddedProxy, 'mongo_mapper/plugins/associations/one_embedded_proxy'
100
109
  autoload :InArrayProxy, 'mongo_mapper/plugins/associations/in_array_proxy'
101
110
  end
102
111
  end
103
112
  end
104
113
 
105
- require 'mongo_mapper/plugins/associations/proxy'
114
+ require 'mongo_mapper/plugins/associations/proxy'
@@ -56,7 +56,7 @@ module MongoMapper
56
56
  end
57
57
 
58
58
  def embeddable?
59
- many? && klass.embeddable?
59
+ (one? || many?) && klass.embeddable?
60
60
  end
61
61
 
62
62
  def type_key_name
@@ -95,7 +95,7 @@ module MongoMapper
95
95
  end
96
96
  end
97
97
  elsif one?
98
- OneProxy
98
+ klass.embeddable? ? OneEmbeddedProxy : OneProxy
99
99
  else
100
100
  polymorphic? ? BelongsToPolymorphicProxy : BelongsToProxy
101
101
  end
@@ -37,13 +37,13 @@ module MongoMapper
37
37
  def replace(docs)
38
38
  load_target
39
39
  target.map(&:destroy)
40
- docs.each { |doc| apply_scope(doc).save }
40
+ docs.each { |doc| prepare(doc).save }
41
41
  reset
42
42
  end
43
43
 
44
44
  def <<(*docs)
45
45
  ensure_owner_saved
46
- flatten_deeper(docs).each { |doc| apply_scope(doc).save }
46
+ flatten_deeper(docs).each { |doc| prepare(doc).save }
47
47
  reset
48
48
  end
49
49
  alias_method :push, :<<
@@ -52,7 +52,8 @@ module MongoMapper
52
52
  def build(attrs={})
53
53
  doc = klass.new(attrs)
54
54
  apply_scope(doc)
55
- reset
55
+ @target ||= [] unless loaded?
56
+ @target << doc
56
57
  doc
57
58
  end
58
59
 
@@ -87,6 +88,10 @@ module MongoMapper
87
88
  end
88
89
  reset
89
90
  end
91
+
92
+ def save_to_collection(options = {})
93
+ @target.each { |doc| doc.save(options) } if @target
94
+ end
90
95
 
91
96
  protected
92
97
  def scoped_conditions
@@ -105,6 +110,10 @@ module MongoMapper
105
110
  owner.save if owner.new?
106
111
  end
107
112
 
113
+ def prepare(doc)
114
+ klass === doc ? apply_scope(doc) : build(doc)
115
+ end
116
+
108
117
  def apply_scope(doc)
109
118
  ensure_owner_saved
110
119
  doc[foreign_key] = owner.id
@@ -0,0 +1,41 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Associations
4
+ class OneEmbeddedProxy < Proxy
5
+ undef_method :object_id
6
+
7
+ def build(attributes={})
8
+ @target = klass.new(attributes)
9
+ assign_references(@target)
10
+ loaded
11
+ @target
12
+ end
13
+
14
+ def replace(doc)
15
+ if doc.respond_to?(:attributes)
16
+ @target = klass.load(doc.attributes)
17
+ else
18
+ @target = klass.load(doc)
19
+ end
20
+ assign_references(@target)
21
+ loaded
22
+ @target
23
+ end
24
+
25
+ protected
26
+
27
+ def find_target
28
+ if @value
29
+ klass.load(@value).tap do |child|
30
+ assign_references(child)
31
+ end
32
+ end
33
+ end
34
+
35
+ def assign_references(doc)
36
+ doc._parent_document = owner
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -37,6 +37,7 @@ module MongoMapper
37
37
 
38
38
  unless doc.nil?
39
39
  owner.save if owner.new?
40
+ doc = klass.new(doc) unless klass === doc
40
41
  doc[foreign_key] = owner.id
41
42
  doc.save if doc.new?
42
43
  loaded
@@ -96,8 +96,14 @@ module MongoMapper
96
96
  end
97
97
 
98
98
  def load_target
99
- @target = find_target unless loaded?
100
- loaded
99
+ unless loaded?
100
+ if @target.is_a?(Array) && @target.any?
101
+ @target = find_target + @target.find_all { |record| record.new? }
102
+ else
103
+ @target = find_target
104
+ end
105
+ loaded
106
+ end
101
107
  @target
102
108
  rescue MongoMapper::DocumentNotFound
103
109
  reset
@@ -81,8 +81,12 @@ module MongoMapper
81
81
  return unless self.class.respond_to?(callback_chain_method)
82
82
  self.class.send(callback_chain_method).run(self, options, &block)
83
83
  self.embedded_associations.each do |association|
84
- self.send(association.name).each do |document|
85
- document.run_callbacks(kind, options, &block)
84
+ if association.one?
85
+ self.send(association.name).run_callbacks(kind, options, &block)
86
+ else
87
+ self.send(association.name).each do |document|
88
+ document.run_callbacks(kind, options, &block)
89
+ end
86
90
  end
87
91
  end
88
92
  end
@@ -233,4 +237,4 @@ module MongoMapper
233
237
  end
234
238
  end
235
239
  end
236
- end
240
+ end
@@ -3,12 +3,12 @@ module MongoMapper
3
3
  module Descendants
4
4
  module ClassMethods
5
5
  def inherited(descendant)
6
- (@descendants ||= []) << descendant
6
+ descendants << descendant
7
7
  super
8
8
  end
9
9
 
10
10
  def descendants
11
- @descendants
11
+ @descendants ||= []
12
12
  end
13
13
  end
14
14
  end
@@ -95,7 +95,6 @@ module MongoMapper
95
95
  end
96
96
 
97
97
  def create_key_in_descendants(*args)
98
- return if descendants.blank?
99
98
  descendants.each { |descendant| descendant.key(*args) }
100
99
  end
101
100
 
@@ -188,7 +187,11 @@ module MongoMapper
188
187
 
189
188
  embedded_associations.each do |association|
190
189
  if documents = instance_variable_get(association.ivar)
191
- attrs[association.name] = documents.map { |document| document.to_mongo }
190
+ if association.one?
191
+ attrs[association.name] = documents.to_mongo
192
+ else
193
+ attrs[association.name] = documents.map { |document| document.to_mongo }
194
+ end
192
195
  end
193
196
  end
194
197
 
@@ -252,7 +255,7 @@ module MongoMapper
252
255
  unless attrs.nil?
253
256
  provided_keys = attrs.keys.map { |k| k.to_s }
254
257
  unless provided_keys.include?('_id') || provided_keys.include?('id')
255
- write_key :_id, Mongo::ObjectID.new
258
+ write_key :_id, BSON::ObjectID.new
256
259
  end
257
260
  end
258
261
  end
@@ -265,10 +268,17 @@ module MongoMapper
265
268
  self.class.key(name) unless respond_to?("#{name}=")
266
269
  end
267
270
 
271
+ def set_parent_document(key, value)
272
+ if key.embeddable? && value.is_a?(key.type)
273
+ value._parent_document = self
274
+ end
275
+ end
276
+
268
277
  def read_key(name)
269
278
  if key = keys[name]
270
279
  var_name = "@#{name}"
271
280
  value = key.get(instance_variable_get(var_name))
281
+ set_parent_document(key, value)
272
282
  instance_variable_set(var_name, value)
273
283
  else
274
284
  raise KeyNotFound, "Could not find key: #{name.inspect}"
@@ -282,10 +292,7 @@ module MongoMapper
282
292
  def write_key(name, value)
283
293
  key = keys[name]
284
294
 
285
- if key.embeddable? && value.is_a?(key.type)
286
- value._parent_document = self
287
- end
288
-
295
+ set_parent_document(key, value)
289
296
  instance_variable_set "@#{name}_before_typecast", value
290
297
  instance_variable_set "@#{name}", key.set(value)
291
298
  end
@@ -17,6 +17,19 @@ module MongoMapper
17
17
  modifier_update('$set', args)
18
18
  end
19
19
 
20
+ def unset(*args)
21
+ if args[0].is_a?(Hash)
22
+ criteria, keys = args.shift, args
23
+ else
24
+ keys, ids = args.partition { |arg| arg.is_a?(Symbol) }
25
+ criteria = {:id => ids}
26
+ end
27
+
28
+ criteria = to_criteria(criteria)
29
+ modifiers = keys.inject({}) { |hash, key| hash[key] = 1; hash }
30
+ collection.update(criteria, {'$unset' => modifiers}, :multi => true)
31
+ end
32
+
20
33
  def push(*args)
21
34
  modifier_update('$push', args)
22
35
  end
@@ -25,11 +38,10 @@ module MongoMapper
25
38
  modifier_update('$pushAll', args)
26
39
  end
27
40
 
28
- def push_uniq(*args)
29
- criteria, keys = criteria_and_keys_from_args(args)
30
- keys.each { |key, value | criteria[key] = {'$ne' => value} }
31
- collection.update(criteria, {'$push' => keys}, :multi => true)
41
+ def add_to_set(*args)
42
+ modifier_update('$addToSet', args)
32
43
  end
44
+ alias push_uniq add_to_set
33
45
 
34
46
  def pull(*args)
35
47
  modifier_update('$pull', args)
@@ -46,8 +58,7 @@ module MongoMapper
46
58
  private
47
59
  def modifier_update(modifier, args)
48
60
  criteria, keys = criteria_and_keys_from_args(args)
49
- modifiers = {modifier => keys}
50
- collection.update(criteria, modifiers, :multi => true)
61
+ collection.update(criteria, {modifier => keys}, :multi => true)
51
62
  end
52
63
 
53
64
  def criteria_and_keys_from_args(args)
@@ -58,32 +69,37 @@ module MongoMapper
58
69
  end
59
70
 
60
71
  module InstanceMethods
72
+ def unset(*keys)
73
+ self.class.unset(id, *keys)
74
+ end
75
+
61
76
  def increment(hash)
62
- self.class.increment({:_id => id}, hash)
77
+ self.class.increment(id, hash)
63
78
  end
64
79
 
65
80
  def decrement(hash)
66
- self.class.decrement({:_id => id}, hash)
81
+ self.class.decrement(id, hash)
67
82
  end
68
83
 
69
84
  def set(hash)
70
- self.class.set({:_id => id}, hash)
85
+ self.class.set(id, hash)
71
86
  end
72
87
 
73
88
  def push(hash)
74
- self.class.push({:_id => id}, hash)
89
+ self.class.push(id, hash)
75
90
  end
76
91
 
77
92
  def pull(hash)
78
- self.class.pull({:_id => id}, hash)
93
+ self.class.pull(id, hash)
79
94
  end
80
95
 
81
- def push_uniq(hash)
82
- self.class.push_uniq({:_id => id}, hash)
96
+ def add_to_set(hash)
97
+ self.class.push_uniq(id, hash)
83
98
  end
99
+ alias push_uniq add_to_set
84
100
 
85
101
  def pop(hash)
86
- self.class.pop({:_id => id}, hash)
102
+ self.class.pop(id, hash)
87
103
  end
88
104
  end
89
105
  end
@@ -36,7 +36,7 @@ module MongoMapper
36
36
 
37
37
  protected
38
38
  def filter_protected_attrs(attrs)
39
- return attrs if protected_attributes.blank?
39
+ return attrs if protected_attributes.blank? || attrs.blank?
40
40
  attrs.dup.delete_if { |key, val| protected_attributes.include?(key.to_sym) }
41
41
  end
42
42
  end
@@ -51,7 +51,7 @@ module MongoMapper
51
51
  hash[key] = value.map do |item|
52
52
  item.respond_to?(:as_json) ? item.as_json(options) : item
53
53
  end
54
- elsif value.is_a? Mongo::ObjectID
54
+ elsif value.is_a? BSON::ObjectID
55
55
  hash[key] = value.to_s
56
56
  elsif value.respond_to?(:as_json)
57
57
  hash[key] = value.as_json(options)
@@ -68,49 +68,52 @@ module MongoMapper
68
68
  if model.object_id_key?(key)
69
69
  case value
70
70
  when String
71
- value = Mongo::ObjectID.from_string(value)
71
+ value = ObjectId.to_mongo(value)
72
72
  when Array
73
73
  value.map! { |id| ObjectId.to_mongo(id) }
74
74
  end
75
75
  end
76
76
 
77
77
  if symbol_operator?(key)
78
- value = {"$#{key.operator}" => value}
79
- key = normalized_key(key.field)
78
+ key, value = normalized_key(key.field), {"$#{key.operator}" => value}
80
79
  end
81
80
 
82
- criteria[key] = normalized_value(key, value)
81
+ criteria[key] = normalized_value(criteria, key, value)
83
82
  end
84
83
 
85
84
  criteria
86
85
  end
87
86
 
88
- def to_fields(fields)
89
- return if fields.blank?
87
+ def to_fields(keys)
88
+ return keys if keys.is_a?(Hash)
89
+ return nil if keys.blank?
90
90
 
91
- if fields.respond_to?(:flatten, :compact)
92
- fields.flatten.compact
91
+ if keys.respond_to?(:flatten, :compact)
92
+ keys.flatten.compact
93
93
  else
94
- fields.split(',').map { |field| field.strip }
94
+ keys.split(',').map { |key| key.strip }
95
95
  end
96
96
  end
97
97
 
98
- def to_order(field, direction=nil)
99
- direction ||= 'ASC'
100
- direction = direction.upcase == 'ASC' ? 1 : -1
101
- [normalized_key(field).to_s, direction]
98
+ def to_order(key, direction=nil)
99
+ [normalized_key(key).to_s, normalized_direction(direction)]
102
100
  end
103
101
 
104
- def normalized_key(field)
105
- field.to_s == 'id' ? :_id : field
102
+ def normalized_key(key)
103
+ key.to_s == 'id' ? :_id : key
106
104
  end
107
105
 
108
- def normalized_value(field, value)
106
+ # TODO: this is getting heavy enough to move to a class
107
+ def normalized_value(criteria, key, value)
109
108
  case value
110
109
  when Array, Set
111
- modifier?(field) ? value.to_a : {'$in' => value.to_a}
110
+ modifier?(key) ? value.to_a : {'$in' => value.to_a}
112
111
  when Hash
113
- to_criteria(value, field)
112
+ if criteria[key].kind_of?(Hash)
113
+ criteria[key].dup.merge(to_criteria(value, key))
114
+ else
115
+ to_criteria(value, key)
116
+ end
114
117
  when Time
115
118
  value.utc
116
119
  else
@@ -118,6 +121,11 @@ module MongoMapper
118
121
  end
119
122
  end
120
123
 
124
+ def normalized_direction(direction)
125
+ direction ||= 'asc'
126
+ direction.downcase == 'asc' ? Mongo::ASCENDING : Mongo::DESCENDING
127
+ end
128
+
121
129
  def normalized_sort(sort)
122
130
  return if sort.blank?
123
131
 
@@ -132,4 +140,4 @@ module MongoMapper
132
140
  end
133
141
  end
134
142
  end
135
- end
143
+ end