mongo_mapper 0.13.0.beta2 → 0.15.0

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.
Files changed (132) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +1 -1
  3. data/README.md +64 -0
  4. data/examples/keys.rb +3 -3
  5. data/examples/modifiers/set.rb +2 -2
  6. data/examples/querying.rb +3 -3
  7. data/examples/safe.rb +2 -2
  8. data/examples/scopes.rb +1 -1
  9. data/lib/mongo_mapper.rb +5 -0
  10. data/lib/mongo_mapper/connection.rb +16 -37
  11. data/lib/mongo_mapper/document.rb +4 -0
  12. data/lib/mongo_mapper/extensions/array.rb +14 -6
  13. data/lib/mongo_mapper/extensions/hash.rb +15 -3
  14. data/lib/mongo_mapper/extensions/object.rb +4 -0
  15. data/lib/mongo_mapper/extensions/object_id.rb +5 -1
  16. data/lib/mongo_mapper/extensions/string.rb +13 -5
  17. data/lib/mongo_mapper/extensions/symbol.rb +18 -0
  18. data/lib/mongo_mapper/plugins/accessible.rb +14 -4
  19. data/lib/mongo_mapper/plugins/associations.rb +7 -6
  20. data/lib/mongo_mapper/plugins/associations/base.rb +18 -13
  21. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +10 -1
  22. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +9 -8
  23. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +12 -11
  24. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +4 -4
  25. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +24 -23
  26. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +18 -16
  27. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +55 -48
  28. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +14 -13
  29. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +7 -6
  30. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +7 -5
  31. data/lib/mongo_mapper/plugins/associations/one_as_proxy.rb +17 -14
  32. data/lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +14 -13
  33. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +9 -9
  34. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +27 -26
  35. data/lib/mongo_mapper/plugins/associations/proxy.rb +31 -28
  36. data/lib/mongo_mapper/plugins/callbacks.rb +14 -1
  37. data/lib/mongo_mapper/plugins/counter_cache.rb +97 -0
  38. data/lib/mongo_mapper/plugins/dirty.rb +29 -37
  39. data/lib/mongo_mapper/plugins/document.rb +1 -1
  40. data/lib/mongo_mapper/plugins/dynamic_querying.rb +10 -9
  41. data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +18 -17
  42. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +2 -1
  43. data/lib/mongo_mapper/plugins/embedded_document.rb +1 -1
  44. data/lib/mongo_mapper/plugins/identity_map.rb +1 -1
  45. data/lib/mongo_mapper/plugins/indexes.rb +37 -2
  46. data/lib/mongo_mapper/plugins/keys.rb +202 -142
  47. data/lib/mongo_mapper/plugins/keys/key.rb +22 -13
  48. data/lib/mongo_mapper/plugins/keys/static.rb +45 -0
  49. data/lib/mongo_mapper/plugins/modifiers.rb +59 -28
  50. data/lib/mongo_mapper/plugins/partial_updates.rb +86 -0
  51. data/lib/mongo_mapper/plugins/persistence.rb +13 -8
  52. data/lib/mongo_mapper/plugins/protected.rb +6 -5
  53. data/lib/mongo_mapper/plugins/querying.rb +85 -42
  54. data/lib/mongo_mapper/plugins/querying/decorated_plucky_query.rb +32 -9
  55. data/lib/mongo_mapper/plugins/rails.rb +1 -0
  56. data/lib/mongo_mapper/plugins/safe.rb +10 -4
  57. data/lib/mongo_mapper/plugins/sci.rb +4 -1
  58. data/lib/mongo_mapper/plugins/scopes.rb +78 -7
  59. data/lib/mongo_mapper/plugins/stats.rb +17 -0
  60. data/lib/mongo_mapper/plugins/timestamps.rb +1 -0
  61. data/lib/mongo_mapper/plugins/touch.rb +1 -1
  62. data/lib/mongo_mapper/plugins/validations.rb +7 -2
  63. data/lib/mongo_mapper/railtie.rb +20 -0
  64. data/lib/mongo_mapper/railtie/database.rake +1 -1
  65. data/lib/mongo_mapper/utils.rb +2 -2
  66. data/lib/mongo_mapper/version.rb +1 -1
  67. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +12 -13
  68. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +9 -9
  69. data/spec/examples.txt +1643 -0
  70. data/spec/functional/accessible_spec.rb +13 -13
  71. data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +13 -13
  72. data/spec/functional/associations/belongs_to_proxy_spec.rb +18 -19
  73. data/spec/functional/associations/in_array_proxy_spec.rb +10 -10
  74. data/spec/functional/associations/many_documents_as_proxy_spec.rb +6 -6
  75. data/spec/functional/associations/many_documents_proxy_spec.rb +85 -14
  76. data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +13 -13
  77. data/spec/functional/associations/many_embedded_proxy_spec.rb +1 -1
  78. data/spec/functional/associations/many_polymorphic_proxy_spec.rb +4 -4
  79. data/spec/functional/associations/one_as_proxy_spec.rb +10 -10
  80. data/spec/functional/associations/one_embedded_polymorphic_proxy_spec.rb +9 -9
  81. data/spec/functional/associations/one_embedded_proxy_spec.rb +3 -3
  82. data/spec/functional/associations/one_proxy_spec.rb +10 -10
  83. data/spec/functional/associations_spec.rb +3 -3
  84. data/spec/functional/binary_spec.rb +2 -2
  85. data/spec/functional/caching_spec.rb +8 -15
  86. data/spec/functional/callbacks_spec.rb +89 -2
  87. data/spec/functional/counter_cache_spec.rb +235 -0
  88. data/spec/functional/dirty_spec.rb +63 -46
  89. data/spec/functional/document_spec.rb +30 -2
  90. data/spec/functional/dumpable_spec.rb +1 -1
  91. data/spec/functional/embedded_document_spec.rb +18 -18
  92. data/spec/functional/identity_map_spec.rb +27 -14
  93. data/spec/functional/indexes_spec.rb +44 -19
  94. data/spec/functional/keys_spec.rb +117 -15
  95. data/spec/functional/logger_spec.rb +3 -3
  96. data/spec/functional/modifiers_spec.rb +67 -19
  97. data/spec/functional/partial_updates_spec.rb +577 -0
  98. data/spec/functional/protected_spec.rb +14 -14
  99. data/spec/functional/querying_spec.rb +55 -28
  100. data/spec/functional/safe_spec.rb +23 -27
  101. data/spec/functional/sci_spec.rb +49 -14
  102. data/spec/functional/scopes_spec.rb +235 -2
  103. data/spec/functional/static_keys_spec.rb +153 -0
  104. data/spec/functional/stats_spec.rb +86 -0
  105. data/spec/functional/touch_spec.rb +6 -6
  106. data/spec/functional/validations_spec.rb +51 -57
  107. data/spec/quality_spec.rb +51 -0
  108. data/spec/spec_helper.rb +37 -9
  109. data/spec/support/matchers.rb +5 -14
  110. data/spec/unit/associations/base_spec.rb +12 -12
  111. data/spec/unit/associations/belongs_to_association_spec.rb +2 -2
  112. data/spec/unit/associations/many_association_spec.rb +2 -2
  113. data/spec/unit/associations/one_association_spec.rb +2 -2
  114. data/spec/unit/associations/proxy_spec.rb +19 -16
  115. data/spec/unit/clone_spec.rb +1 -1
  116. data/spec/unit/document_spec.rb +8 -8
  117. data/spec/unit/dynamic_finder_spec.rb +8 -8
  118. data/spec/unit/embedded_document_spec.rb +18 -19
  119. data/spec/unit/extensions_spec.rb +41 -17
  120. data/spec/unit/identity_map_middleware_spec.rb +65 -96
  121. data/spec/unit/inspect_spec.rb +4 -4
  122. data/spec/unit/key_spec.rb +28 -26
  123. data/spec/unit/keys_spec.rb +10 -10
  124. data/spec/unit/model_generator_spec.rb +2 -4
  125. data/spec/unit/mongo_mapper_spec.rb +38 -85
  126. data/spec/unit/rails_spec.rb +5 -0
  127. data/spec/unit/serialization_spec.rb +1 -1
  128. data/spec/unit/time_zones_spec.rb +2 -2
  129. data/spec/unit/validations_spec.rb +28 -15
  130. metadata +188 -161
  131. data/README.rdoc +0 -55
  132. data/lib/mongo_mapper/extensions/ordered_hash.rb +0 -23
@@ -62,7 +62,7 @@ module MongoMapper
62
62
 
63
63
  if @typecast
64
64
  klass = typecast_class # Don't make this lookup on every call
65
- type.from_mongo(value).map! { |v| klass.from_mongo(v) }
65
+ type.from_mongo(value).map { |v| klass.from_mongo(v) }
66
66
  else
67
67
  type.from_mongo(value)
68
68
  end
@@ -71,8 +71,11 @@ module MongoMapper
71
71
  def set(value)
72
72
  # Avoid tap here so we don't have to create a block binding.
73
73
  values = type.to_mongo(value)
74
- values.map! { |v| typecast_class.to_mongo(v) } if @typecast
75
- values
74
+ if @typecast
75
+ values.map { |v| typecast_class.to_mongo(v) }
76
+ else
77
+ values
78
+ end
76
79
  end
77
80
 
78
81
  def default_value
@@ -90,6 +93,11 @@ module MongoMapper
90
93
  !!@name.match(/\A[a-z_][a-z0-9_]*\z/i)
91
94
  end
92
95
 
96
+ RESERVED_KEYS = %w( id class object_id )
97
+ def reserved_name?
98
+ RESERVED_KEYS.include?(@name)
99
+ end
100
+
93
101
  def read_accessor?
94
102
  any_accessor? ["read"]
95
103
  end
@@ -108,18 +116,19 @@ module MongoMapper
108
116
  return !(@accessors & arr_opt).empty?
109
117
  end
110
118
 
111
- private
112
- def typecast_class
113
- @typecast_class ||= options[:typecast].constantize
114
- end
119
+ private
120
+
121
+ def typecast_class
122
+ @typecast_class ||= options[:typecast].constantize
123
+ end
115
124
 
116
- def validate_key_name!
117
- if %w( id ).include? @name
118
- raise MongoMapper::InvalidKey.new("`#{@name}` is a reserved key name (did you mean to use _id?)")
119
- elsif !valid_ruby_name?
120
- raise MongoMapper::InvalidKey.new("`#{@name}` is not a valid key name. Keys must match [a-z][a-z0-9_]*")
121
- end
125
+ def validate_key_name!
126
+ if reserved_name?
127
+ raise MongoMapper::InvalidKey.new("`#{@name}` is a reserved key name (did you mean to use _id?)")
128
+ elsif !valid_ruby_name?
129
+ raise MongoMapper::InvalidKey.new("`#{@name}` is not a valid key name. Keys must match [a-z][a-z0-9_]*")
122
130
  end
131
+ end
123
132
  end
124
133
  end
125
134
  end
@@ -0,0 +1,45 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Keys
4
+ module Static
5
+ class MissingKeyError < StandardError; end
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ attr_writer :static_keys
11
+
12
+ def static_keys
13
+ @static_keys || false
14
+ end
15
+ end
16
+
17
+ def read_key(name)
18
+ if !self.class.static_keys || self.class.key?(name)
19
+ super
20
+ else
21
+ raise MissingKeyError, "Tried to read the key #{name.inspect}, but no key was defined. Either define key :#{name} or set self.static_keys = false"
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def write_key(name, value)
28
+ if !self.class.static_keys || self.class.key?(name)
29
+ super
30
+ else
31
+ raise MissingKeyError, "Tried to write the key #{name.inspect}, but no key was defined. Either define key :#{name} or set self.static_keys = false"
32
+ end
33
+ end
34
+
35
+ def load_from_database(attrs, with_cast = false)
36
+ return super if !self.class.static_keys || !attrs.respond_to?(:each)
37
+
38
+ attrs = attrs.select { |key, _| self.class.key?(key) }
39
+
40
+ super(attrs, with_cast)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -13,7 +13,7 @@ module MongoMapper
13
13
  criteria, keys, options = criteria_and_keys_from_args(args)
14
14
  values, to_decrement = keys.values, {}
15
15
  keys.keys.each_with_index { |k, i| to_decrement[k] = -values[i].abs }
16
- collection.update(criteria, {'$inc' => to_decrement}, :multi => true)
16
+ collection.update_many(criteria, {'$inc' => to_decrement}, options || {})
17
17
  end
18
18
 
19
19
  def set(*args)
@@ -44,7 +44,16 @@ module MongoMapper
44
44
  end
45
45
 
46
46
  def push_all(*args)
47
- modifier_update('$pushAll', args)
47
+ Kernel.warn "push_all no longer supported. use $push with $each"
48
+
49
+ hash = args.pop
50
+ ids = args
51
+
52
+ push_values = hash.inject({}) do |hsh, (key, values)|
53
+ { key => { '$each' => values } }
54
+ end
55
+
56
+ modifier_update('$addToSet', [ids, push_values].flatten)
48
57
  end
49
58
 
50
59
  def add_to_set(*args)
@@ -64,36 +73,52 @@ module MongoMapper
64
73
  modifier_update('$pop', args)
65
74
  end
66
75
 
67
- private
68
- def modifier_update(modifier, args)
69
- criteria, updates, options = criteria_and_keys_from_args(args)
70
- if options
71
- collection.update(criteria, {modifier => updates}, options.merge(:multi => true))
72
- else
73
- collection.update(criteria, {modifier => updates}, :multi => true)
74
- end
76
+ def find_one_and_update(args)
77
+ args = args.dup
78
+ args[:query] = dealias_keys(args.delete :query) if args.key? :query
79
+ args[:update] = dealias_keys(args.delete :update) if args.key? :update
80
+ collection.find_one_and_update(args[:query], args[:update], args)
81
+ end
82
+ alias_method :find_and_modify, :find_one_and_update
83
+
84
+ def upsert(selector, updates, args = {})
85
+ criteria = dealias_keys(selector)
86
+ updates = dealias_keys(updates)
87
+ collection.update_one(criteria, updates, args.merge(upsert: true))
88
+ end
89
+
90
+ private
91
+
92
+ def modifier_update(modifier, args)
93
+ criteria, updates, options = criteria_and_keys_from_args(args)
94
+ if options
95
+ collection.update_many(criteria, {modifier => updates}, options)
96
+ else
97
+ collection.update_many(criteria, {modifier => updates})
75
98
  end
99
+ end
76
100
 
77
- def criteria_and_keys_from_args(args)
78
- if args[0].is_a?(Hash)
79
- criteria = args[0]
80
- updates = args[1]
81
- options = args[2]
82
- else
83
- criteria, (updates, options) = args.partition { |a| !a.is_a?(Hash) }
84
- criteria = { :id => criteria }
85
- end
86
- upgrade_legacy_safe_usage!(options)
87
-
88
- [criteria_hash(criteria).to_hash, updates, options]
101
+ def criteria_and_keys_from_args(args)
102
+ if args[0].is_a?(Hash)
103
+ criteria = args[0]
104
+ updates = args[1]
105
+ options = args[2]
106
+ else
107
+ criteria, (updates, options) = args.partition { |a| !a.is_a?(Hash) }
108
+ criteria = { :id => criteria }
89
109
  end
110
+ upgrade_legacy_safe_usage!(options)
111
+ updates = dealias_keys updates
112
+
113
+ [criteria_hash(criteria).to_hash, updates, options]
114
+ end
90
115
 
91
- def upgrade_legacy_safe_usage!(options)
92
- if options and options.key?(:safe)
93
- options.merge! Utils.get_safe_options(options)
94
- options.delete :safe
95
- end
116
+ def upgrade_legacy_safe_usage!(options)
117
+ if options and options.key?(:safe)
118
+ options.merge! Utils.get_safe_options(options)
119
+ options.delete :safe
96
120
  end
121
+ end
97
122
  end
98
123
 
99
124
  def unset(*args)
@@ -117,7 +142,13 @@ module MongoMapper
117
142
  end
118
143
 
119
144
  def push_all(hash, options=nil)
120
- self.class.push_all({:_id => id}, hash, options)
145
+ Kernel.warn "push_all no longer supported. use $push with $each"
146
+
147
+ push_values = hash.inject({}) do |hsh, (key, values)|
148
+ { key => { '$each' => values } }
149
+ end
150
+
151
+ self.class.push({:_id => id}, push_values, options)
121
152
  end
122
153
 
123
154
  def pull(hash, options=nil)
@@ -0,0 +1,86 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module PartialUpdates
4
+ extend ActiveSupport::Concern
5
+
6
+ class PartialUpdatesDisabledError < StandardError; end
7
+
8
+ included do
9
+ class_attribute :partial_updates
10
+ self.partial_updates = false
11
+
12
+ self.after_find :_reset_partial_updates_callback
13
+ self.after_save :_reset_partial_updates_callback
14
+ end
15
+
16
+ def initialize(*)
17
+ _reset_partial_updates_callback
18
+ super
19
+ end
20
+
21
+ def fields_for_partial_update
22
+ raise PartialUpdatesDisabledError if !partial_updates
23
+
24
+ Hash.new.tap do |hash|
25
+ attrs = _dealiased_attributes
26
+
27
+ hash[:set_fields] = Array.new.tap do |array|
28
+ attrs.each do |key, value|
29
+ if !@_last_saved_attributes.include?(key) ||
30
+ @_last_saved_attributes[key] != value
31
+ array << key
32
+ end
33
+ end
34
+ end
35
+
36
+ hash[:unset_fields] = @_last_saved_attributes.keys - attrs.keys
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def _reset_partial_updates_callback
43
+ _reset_attributes_for_partial_update if partial_updates
44
+ true
45
+ end
46
+
47
+ def update(options={})
48
+ if partial_updates
49
+ super(options.merge(:persistence_method => :update))
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ def save_to_collection(options={})
56
+ if partial_updates && options[:persistence_method] == :update
57
+ updates = fields_for_partial_update
58
+
59
+ set_fields = updates[:set_fields]
60
+ unset_fields = updates[:unset_fields]
61
+
62
+ if set_fields.any? || unset_fields.any?
63
+ set_fields.push("_id") if !set_fields.include?("_id")
64
+ end
65
+
66
+ options = options.merge({
67
+ :set_fields => set_fields,
68
+ :unset_fields => unset_fields
69
+ })
70
+
71
+ super(options)
72
+ else
73
+ super
74
+ end
75
+ end
76
+
77
+ def _dealiased_attributes
78
+ self.class.dealias_keys(attributes)
79
+ end
80
+
81
+ def _reset_attributes_for_partial_update
82
+ @_last_saved_attributes = _dealiased_attributes._mongo_mapper_deep_copy_
83
+ end
84
+ end
85
+ end
86
+ end
@@ -39,7 +39,7 @@ module MongoMapper
39
39
  if database_name.nil?
40
40
  MongoMapper.database
41
41
  else
42
- connection.db(database_name)
42
+ connection.use(database_name).database
43
43
  end
44
44
  end
45
45
 
@@ -55,16 +55,21 @@ module MongoMapper
55
55
 
56
56
  def collection
57
57
  assert_supported
58
- database.collection(collection_name)
58
+ database.collection(collection_name, collection_options)
59
59
  end
60
60
 
61
- private
62
- def assert_supported
63
- @embeddable ||= embeddable?
64
- if @embeddable
65
- raise MongoMapper::NotSupported.new('This is not supported for embeddable documents at this time.')
66
- end
61
+ def collection_options
62
+ {}
63
+ end
64
+
65
+ private
66
+
67
+ def assert_supported
68
+ @embeddable ||= embeddable?
69
+ if @embeddable
70
+ raise MongoMapper::NotSupported.new('This is not supported for embeddable documents at this time.')
67
71
  end
72
+ end
68
73
  end
69
74
 
70
75
  def collection
@@ -35,11 +35,12 @@ module MongoMapper
35
35
  super(filter_protected_attrs(attrs))
36
36
  end
37
37
 
38
- protected
39
- def filter_protected_attrs(attrs)
40
- return attrs if protected_attributes.blank? || attrs.blank?
41
- attrs.dup.delete_if { |key, val| protected_attributes.include?(key.to_sym) }
42
- end
38
+ protected
39
+
40
+ def filter_protected_attrs(attrs)
41
+ return attrs if protected_attributes.blank? || attrs.blank?
42
+ attrs.dup.delete_if { |key, val| protected_attributes.include?(key.to_sym) }
43
+ end
43
44
  end
44
45
  end
45
46
  end
@@ -61,41 +61,42 @@ module MongoMapper
61
61
  Plucky::CriteriaHash.new(criteria, :object_ids => object_id_keys)
62
62
  end
63
63
 
64
- private
65
- def transformer
66
- @transformer ||= lambda { |doc| load(doc) }
67
- end
64
+ private
68
65
 
69
- def initialize_each(*docs)
70
- instances = []
71
- docs = [{}] if docs.blank?
72
- docs.flatten.each do |attrs|
73
- doc = new(attrs)
74
- yield(doc)
75
- instances << doc
76
- end
77
- instances.size == 1 ? instances[0] : instances
78
- end
66
+ def transformer
67
+ @transformer ||= lambda { |doc| load(doc) }
68
+ end
79
69
 
80
- def update_single(id, attrs)
81
- if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
82
- raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
83
- end
70
+ def initialize_each(*docs)
71
+ instances = []
72
+ docs = [{}] if docs.blank?
73
+ docs.flatten.each do |attrs|
74
+ doc = new(attrs)
75
+ yield(doc)
76
+ instances << doc
77
+ end
78
+ instances.size == 1 ? instances[0] : instances
79
+ end
84
80
 
85
- find(id).tap do |doc|
86
- doc.update_attributes(attrs)
87
- end
81
+ def update_single(id, attrs)
82
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
83
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
88
84
  end
89
85
 
90
- def update_multiple(docs)
91
- unless docs.is_a?(Hash)
92
- raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
93
- end
86
+ find(id).tap do |doc|
87
+ doc.update_attributes(attrs)
88
+ end
89
+ end
94
90
 
95
- instances = []
96
- docs.each_pair { |id, attrs| instances << update(id, attrs) }
97
- instances
91
+ def update_multiple(docs)
92
+ unless docs.is_a?(Hash)
93
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
98
94
  end
95
+
96
+ instances = []
97
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
98
+ instances
99
+ end
99
100
  end
100
101
 
101
102
  def save(options={})
@@ -116,25 +117,67 @@ module MongoMapper
116
117
  self.class.delete(id).tap { @_destroyed = true } if persisted?
117
118
  end
118
119
 
119
- private
120
- def create_or_update(options={})
121
- result = persisted? ? update(options) : create(options)
122
- result != false
123
- end
120
+ private
124
121
 
125
- def create(options={})
126
- save_to_collection(options.merge(:persistence_method => :insert))
127
- end
122
+ def create_or_update(options={})
123
+ result = persisted? ? update(options) : create(options)
124
+ result != false
125
+ end
128
126
 
129
- def update(options={})
130
- save_to_collection(options.merge(:persistence_method => :save))
127
+ def create(options={})
128
+ save_to_collection(options.merge(:persistence_method => :insert))
129
+ end
130
+
131
+ def update(options={})
132
+ save_to_collection(options.reverse_merge(:persistence_method => :save))
133
+ end
134
+
135
+ def save_to_collection(options={})
136
+ @_new = false
137
+ method = options.delete(:persistence_method) || :save
138
+ update = to_mongo
139
+ query_options = Utils.get_safe_options(options)
140
+
141
+ if query_options.any?
142
+ collection = self.collection.with(write: query_options)
143
+ else
144
+ collection = self.collection
131
145
  end
132
146
 
133
- def save_to_collection(options={})
134
- @_new = false
135
- method = options.delete(:persistence_method) || :save
136
- collection.send(method, to_mongo, Utils.get_safe_options(options))
147
+ case method
148
+ when :insert
149
+ collection.insert_one(update, query_options)
150
+ when :save
151
+ collection.update_one({:_id => _id}, update, query_options.merge(upsert: true))
152
+ when :update
153
+ update.stringify_keys!
154
+
155
+ id = update.delete("_id")
156
+
157
+ set_values = update
158
+ unset_values = {}
159
+
160
+ if fields_for_set = options.delete(:set_fields)
161
+ set_values = set_values.slice(*fields_for_set)
162
+ end
163
+
164
+ if fields_for_unset = options.delete(:unset_fields)
165
+ fields_for_unset.each do |field|
166
+ unset_values[field] = true
167
+ end
168
+ end
169
+
170
+ find_query = { :_id => id }
171
+
172
+ update_query = {}
173
+ update_query["$set"] = set_values if set_values.any?
174
+ update_query["$unset"] = unset_values if unset_values.any?
175
+
176
+ if update_query.any?
177
+ collection.update_one(find_query, update_query, query_options)
178
+ end
137
179
  end
180
+ end
138
181
  end
139
182
  end
140
183
  end