mongo_mapper 0.13.1 → 0.14.0.rc1

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 (103) hide show
  1. checksums.yaml +13 -5
  2. data/README.rdoc +3 -6
  3. data/lib/mongo_mapper.rb +1 -0
  4. data/lib/mongo_mapper/document.rb +2 -0
  5. data/lib/mongo_mapper/extensions/array.rb +14 -6
  6. data/lib/mongo_mapper/extensions/hash.rb +15 -3
  7. data/lib/mongo_mapper/extensions/object.rb +4 -0
  8. data/lib/mongo_mapper/extensions/string.rb +13 -5
  9. data/lib/mongo_mapper/plugins/accessible.rb +12 -11
  10. data/lib/mongo_mapper/plugins/associations.rb +7 -6
  11. data/lib/mongo_mapper/plugins/associations/base.rb +13 -12
  12. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +9 -8
  13. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +12 -11
  14. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +4 -4
  15. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +24 -23
  16. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +18 -16
  17. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +55 -48
  18. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +14 -13
  19. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +7 -6
  20. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +7 -5
  21. data/lib/mongo_mapper/plugins/associations/one_as_proxy.rb +14 -11
  22. data/lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +14 -13
  23. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +9 -9
  24. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +27 -26
  25. data/lib/mongo_mapper/plugins/associations/proxy.rb +29 -26
  26. data/lib/mongo_mapper/plugins/callbacks.rb +13 -0
  27. data/lib/mongo_mapper/plugins/counter_cache.rb +23 -4
  28. data/lib/mongo_mapper/plugins/dirty.rb +2 -2
  29. data/lib/mongo_mapper/plugins/dynamic_querying.rb +10 -9
  30. data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +17 -16
  31. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +1 -1
  32. data/lib/mongo_mapper/plugins/identity_map.rb +1 -1
  33. data/lib/mongo_mapper/plugins/indexes.rb +1 -1
  34. data/lib/mongo_mapper/plugins/keys.rb +158 -158
  35. data/lib/mongo_mapper/plugins/keys/key.rb +16 -10
  36. data/lib/mongo_mapper/plugins/keys/static.rb +45 -0
  37. data/lib/mongo_mapper/plugins/modifiers.rb +27 -26
  38. data/lib/mongo_mapper/plugins/partial_updates.rb +86 -0
  39. data/lib/mongo_mapper/plugins/persistence.rb +7 -6
  40. data/lib/mongo_mapper/plugins/protected.rb +6 -5
  41. data/lib/mongo_mapper/plugins/querying.rb +80 -43
  42. data/lib/mongo_mapper/plugins/querying/decorated_plucky_query.rb +14 -9
  43. data/lib/mongo_mapper/plugins/scopes.rb +78 -7
  44. data/lib/mongo_mapper/plugins/timestamps.rb +1 -0
  45. data/lib/mongo_mapper/plugins/validations.rb +0 -0
  46. data/lib/mongo_mapper/version.rb +1 -1
  47. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +12 -13
  48. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +9 -9
  49. data/spec/functional/accessible_spec.rb +12 -12
  50. data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +11 -11
  51. data/spec/functional/associations/belongs_to_proxy_spec.rb +14 -15
  52. data/spec/functional/associations/in_array_proxy_spec.rb +6 -6
  53. data/spec/functional/associations/many_documents_proxy_spec.rb +89 -18
  54. data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +11 -11
  55. data/spec/functional/associations/many_embedded_proxy_spec.rb +1 -1
  56. data/spec/functional/associations/one_as_proxy_spec.rb +14 -14
  57. data/spec/functional/associations/one_embedded_polymorphic_proxy_spec.rb +9 -9
  58. data/spec/functional/associations/one_embedded_proxy_spec.rb +3 -3
  59. data/spec/functional/associations/one_proxy_spec.rb +14 -14
  60. data/spec/functional/caching_spec.rb +8 -8
  61. data/spec/functional/callbacks_spec.rb +87 -0
  62. data/spec/functional/counter_cache_spec.rb +89 -0
  63. data/spec/functional/dirty_spec.rb +41 -41
  64. data/spec/functional/document_spec.rb +3 -3
  65. data/spec/functional/embedded_document_spec.rb +18 -18
  66. data/spec/functional/identity_map_spec.rb +28 -15
  67. data/spec/functional/indexes_spec.rb +4 -4
  68. data/spec/functional/keys_spec.rb +12 -3
  69. data/spec/functional/logger_spec.rb +1 -1
  70. data/spec/functional/modifiers_spec.rb +2 -2
  71. data/spec/functional/partial_updates_spec.rb +577 -0
  72. data/spec/functional/protected_spec.rb +13 -13
  73. data/spec/functional/querying_spec.rb +11 -10
  74. data/spec/functional/safe_spec.rb +2 -2
  75. data/spec/functional/sci_spec.rb +3 -3
  76. data/spec/functional/scopes_spec.rb +234 -1
  77. data/spec/functional/static_keys_spec.rb +153 -0
  78. data/spec/functional/stats_spec.rb +0 -4
  79. data/spec/functional/touch_spec.rb +1 -1
  80. data/spec/functional/validations_spec.rb +59 -57
  81. data/spec/quality_spec.rb +1 -1
  82. data/spec/spec_helper.rb +7 -3
  83. data/spec/support/matchers.rb +4 -13
  84. data/spec/unit/associations/base_spec.rb +12 -12
  85. data/spec/unit/associations/belongs_to_association_spec.rb +2 -2
  86. data/spec/unit/associations/many_association_spec.rb +2 -2
  87. data/spec/unit/associations/one_association_spec.rb +2 -2
  88. data/spec/unit/associations/proxy_spec.rb +13 -15
  89. data/spec/unit/document_spec.rb +5 -5
  90. data/spec/unit/dynamic_finder_spec.rb +8 -8
  91. data/spec/unit/embedded_document_spec.rb +14 -14
  92. data/spec/unit/extensions_spec.rb +17 -17
  93. data/spec/unit/identity_map_middleware_spec.rb +5 -5
  94. data/spec/unit/key_spec.rb +24 -21
  95. data/spec/unit/keys_spec.rb +5 -5
  96. data/spec/unit/mongo_mapper_spec.rb +26 -26
  97. data/spec/unit/rails_spec.rb +2 -2
  98. data/spec/unit/serialization_spec.rb +1 -1
  99. data/spec/unit/time_zones_spec.rb +2 -2
  100. data/spec/unit/validations_spec.rb +28 -15
  101. metadata +16 -14
  102. data/lib/mongo_mapper/connections/10gen.rb +0 -0
  103. data/lib/mongo_mapper/connections/moped.rb +0 -0
@@ -93,6 +93,11 @@ module MongoMapper
93
93
  !!@name.match(/\A[a-z_][a-z0-9_]*\z/i)
94
94
  end
95
95
 
96
+ RESERVED_KEYS = %w( id class object_id )
97
+ def reserved_name?
98
+ RESERVED_KEYS.include?(@name)
99
+ end
100
+
96
101
  def read_accessor?
97
102
  any_accessor? ["read"]
98
103
  end
@@ -111,18 +116,19 @@ module MongoMapper
111
116
  return !(@accessors & arr_opt).empty?
112
117
  end
113
118
 
114
- private
115
- def typecast_class
116
- @typecast_class ||= options[:typecast].constantize
117
- end
119
+ private
118
120
 
119
- def validate_key_name!
120
- if %w( id ).include? @name
121
- raise MongoMapper::InvalidKey.new("`#{@name}` is a reserved key name (did you mean to use _id?)")
122
- elsif !valid_ruby_name?
123
- raise MongoMapper::InvalidKey.new("`#{@name}` is not a valid key name. Keys must match [a-z][a-z0-9_]*")
124
- end
121
+ def typecast_class
122
+ @typecast_class ||= options[:typecast].constantize
123
+ end
124
+
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_]*")
125
130
  end
131
+ end
126
132
  end
127
133
  end
128
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
@@ -76,37 +76,38 @@ module MongoMapper
76
76
  collection.update(criteria, updates, args)
77
77
  end
78
78
 
79
- private
80
- def modifier_update(modifier, args)
81
- criteria, updates, options = criteria_and_keys_from_args(args)
82
- if options
83
- collection.update(criteria, {modifier => updates}, options.merge(:multi => true))
84
- else
85
- collection.update(criteria, {modifier => updates}, :multi => true)
86
- end
79
+ private
80
+
81
+ def modifier_update(modifier, args)
82
+ criteria, updates, options = criteria_and_keys_from_args(args)
83
+ if options
84
+ collection.update(criteria, {modifier => updates}, options.merge(:multi => true))
85
+ else
86
+ collection.update(criteria, {modifier => updates}, :multi => true)
87
87
  end
88
+ end
88
89
 
89
- def criteria_and_keys_from_args(args)
90
- if args[0].is_a?(Hash)
91
- criteria = args[0]
92
- updates = args[1]
93
- options = args[2]
94
- else
95
- criteria, (updates, options) = args.partition { |a| !a.is_a?(Hash) }
96
- criteria = { :id => criteria }
97
- end
98
- upgrade_legacy_safe_usage!(options)
99
- updates = dealias_keys updates
100
-
101
- [criteria_hash(criteria).to_hash, updates, options]
90
+ def criteria_and_keys_from_args(args)
91
+ if args[0].is_a?(Hash)
92
+ criteria = args[0]
93
+ updates = args[1]
94
+ options = args[2]
95
+ else
96
+ criteria, (updates, options) = args.partition { |a| !a.is_a?(Hash) }
97
+ criteria = { :id => criteria }
102
98
  end
99
+ upgrade_legacy_safe_usage!(options)
100
+ updates = dealias_keys updates
101
+
102
+ [criteria_hash(criteria).to_hash, updates, options]
103
+ end
103
104
 
104
- def upgrade_legacy_safe_usage!(options)
105
- if options and options.key?(:safe)
106
- options.merge! Utils.get_safe_options(options)
107
- options.delete :safe
108
- end
105
+ def upgrade_legacy_safe_usage!(options)
106
+ if options and options.key?(:safe)
107
+ options.merge! Utils.get_safe_options(options)
108
+ options.delete :safe
109
109
  end
110
+ end
110
111
  end
111
112
 
112
113
  def unset(*args)
@@ -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
@@ -58,13 +58,14 @@ module MongoMapper
58
58
  database.collection(collection_name)
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
+ private
62
+
63
+ def assert_supported
64
+ @embeddable ||= embeddable?
65
+ if @embeddable
66
+ raise MongoMapper::NotSupported.new('This is not supported for embeddable documents at this time.')
67
67
  end
68
+ end
68
69
  end
69
70
 
70
71
  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,61 @@ 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))
131
- end
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)
132
140
 
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))
141
+ case method
142
+ when :insert
143
+ collection.insert(update, query_options)
144
+ when :save
145
+ collection.save(update, query_options)
146
+ when :update
147
+ update.stringify_keys!
148
+
149
+ id = update.delete("_id")
150
+
151
+ set_values = update
152
+ unset_values = {}
153
+
154
+ if fields_for_set = options.delete(:set_fields)
155
+ set_values = set_values.slice(*fields_for_set)
156
+ end
157
+
158
+ if fields_for_unset = options.delete(:unset_fields)
159
+ fields_for_unset.each do |field|
160
+ unset_values[field] = true
161
+ end
162
+ end
163
+
164
+ find_query = { :_id => id }
165
+
166
+ update_query = {}
167
+ update_query["$set"] = set_values if set_values.any?
168
+ update_query["$unset"] = unset_values if unset_values.any?
169
+
170
+ if update_query.any?
171
+ collection.update(find_query, update_query, query_options)
172
+ end
137
173
  end
174
+ end
138
175
  end
139
176
  end
140
177
  end
@@ -58,17 +58,22 @@ module MongoMapper
58
58
  end
59
59
  end
60
60
 
61
- protected
61
+ private
62
62
 
63
- def method_missing(method, *args, &block)
64
- return super unless model.respond_to?(method)
65
- result = model.send(method, *args, &block)
66
- if result.is_a?(Plucky::Query)
67
- merge(result)
68
- else
69
- result
70
- end
63
+ def method_missing(method, *args, &block)
64
+ return super unless model.respond_to?(method)
65
+
66
+ result = model.with_scope(criteria_hash) do
67
+ model.send(method, *args, &block)
71
68
  end
69
+
70
+ case result
71
+ when Plucky::Query
72
+ merge(result)
73
+ else
74
+ result
75
+ end
76
+ end
72
77
  end
73
78
  end
74
79
  end