mongoid 3.0.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,53 @@
3
3
  For instructions on upgrading to newer versions, visit
4
4
  [mongoid.org](http://mongoid.org/docs/upgrading.html).
5
5
 
6
+ ## 3.0.2 (branch: 3.0.0-stable)
7
+
8
+ ### Resolved Issues
9
+
10
+ * \#2244 Get rid of id mass assignment warnings in nested attributes.
11
+
12
+ * \#2242 Fix eager loading not to load all documents when calling first or
13
+ last.
14
+
15
+ * \#2241 Map/reduce operations now always use strong consistency since they
16
+ have the potential to write to collections, most of the time.
17
+
18
+ * \#2238 Ensure n-n foreign key fields are flagged as resizable to prevent
19
+ `nil` -> `[]` changes when using `#only` and updating.
20
+
21
+ * \#2236 Keep the instance of the document in the validations exception
22
+ accessible via `document` or `record`.
23
+
24
+ * \#2234 Ensure validations when document is getting persisted with custom
25
+ options work properly with custom options, and do not clear them out if
26
+ validation passes.
27
+
28
+ * \#2224 `Model#inc` now accepts `BigDecimal` values.
29
+
30
+ * \#2216 Fixed assignment of metadata on embeds one relations when setting
31
+ multiple times in a row.
32
+
33
+ * \#2212 Ensure errors are cleared after a save with `validate: false` in all
34
+ situations.
35
+
36
+ * \#2207 When eager loading ids the query must be duped to avoid multiple
37
+ iteration problems not getting the required fields.
38
+
39
+ * \#2204 Raise an `InvalidIncludes` error when passing arguments to
40
+ `Criteria.includes` that are invalid (not relations, or more than 1 level.)
41
+
42
+ * \#2203 Map/Reduce now works properly in conjunction with `Model#with`.
43
+
44
+ Band.
45
+ with(session: "secondary").
46
+ where(:likes.gt => 100).
47
+ map_reduce(map, reduce).
48
+ out(inline: 1)
49
+
50
+ * \#2199 Autosave false is now respected when automatically adding
51
+ presence validation. (John Nishinaga)
52
+
6
53
  ## 3.0.1
7
54
 
8
55
  ### Resolved Issues
@@ -132,6 +132,15 @@ en:
132
132
  \_\_\_\_include Mongoid::Document\n
133
133
  \_\_\_\_field :%{name}, %{option}: true\n
134
134
  \_\_end\n\n"
135
+ invalid_includes:
136
+ message: "Invalid includes directive: %{klass}.includes(%{args})"
137
+ summary: "Eager loading in Mongoid only supports providing arguments
138
+ to %{klass}.includes that are the names of relations on the %{klass}
139
+ model, and only supports one level of eager loading. (ie, eager loading
140
+ associations not on the %{klass} but one step away via another relation
141
+ is not allowed."
142
+ resolution: "Ensure that each parameter passed to %{klass}.includes is
143
+ a valid name of a relation on the %{klass} model. These are: %{relations}."
135
144
  invalid_index:
136
145
  message: "Invalid index specification on %{klass}: %{spec}, %{options}"
137
146
  summary: "Indexes in Mongoid are defined as a hash of field name and
@@ -3,6 +3,10 @@ module Mongoid
3
3
  module Contextual
4
4
  module Command
5
5
 
6
+ # @attribute [r] collection The collection to query against.
7
+ # @attribute [r] criteria The criteria for the context.
8
+ attr_reader :collection, :criteria
9
+
6
10
  # The database command that is being built to send to the db.
7
11
  #
8
12
  # @example Get the command.
@@ -15,18 +19,6 @@ module Mongoid
15
19
  @command ||= {}
16
20
  end
17
21
 
18
- # Get the criteria for the command.
19
- #
20
- # @example Get the criteria.
21
- # command.criteria
22
- #
23
- # @return [ Criteria ] The criteria.
24
- #
25
- # @since 3.0.0
26
- def criteria
27
- @criteria
28
- end
29
-
30
22
  # Get the database session.
31
23
  #
32
24
  # @example Get the session.
@@ -36,7 +28,7 @@ module Mongoid
36
28
  #
37
29
  # @since 3.0.0
38
30
  def session
39
- criteria.klass.mongo_session
31
+ collection.database.session
40
32
  end
41
33
  end
42
34
  end
@@ -25,9 +25,10 @@ module Mongoid
25
25
  # @option options [ true, false ] :upsert Create the document if it doesn't exist.
26
26
  #
27
27
  # @since 3.0.0
28
- def initialize(criteria, update, options = {})
29
- @criteria, @options, @update = criteria, options, update
30
- @query = criteria.klass.collection.find(criteria.selector)
28
+ def initialize(collection, criteria, update, options = {})
29
+ @collection, @criteria, @options, @update =
30
+ collection, criteria, options, update
31
+ @query = collection.find(criteria.selector)
31
32
  apply_criteria_options
32
33
  end
33
34
 
@@ -78,9 +78,9 @@ module Mongoid
78
78
  # @param [ String ] reduce The reduce js function.
79
79
  #
80
80
  # @since 3.0.0
81
- def initialize(criteria, map, reduce)
82
- @criteria = criteria
83
- command[:mapreduce] = criteria.klass.collection_name.to_s
81
+ def initialize(collection, criteria, map, reduce)
82
+ @collection, @criteria = collection, criteria
83
+ command[:mapreduce] = collection.name.to_s
84
84
  command[:map], command[:reduce] = map, reduce
85
85
  apply_criteria_options
86
86
  end
@@ -266,7 +266,7 @@ module Mongoid
266
266
  # @since 3.0.0
267
267
  def results
268
268
  raise Errors::NoMapReduceOutput.new(command) unless command[:out]
269
- @results ||= session.command(command)
269
+ @results ||= session.with(consistency: :strong).command(command)
270
270
  end
271
271
  end
272
272
  end
@@ -12,10 +12,11 @@ module Mongoid
12
12
  include Aggregable::Mongo
13
13
  include Atomic
14
14
 
15
+ # @attribute [r] collection The collection to query against.
15
16
  # @attribute [r] criteria The criteria for the context.
16
17
  # @attribute [r] klass The klass for the criteria.
17
18
  # @attribute [r] query The Moped query.
18
- attr_reader :criteria, :klass, :query
19
+ attr_reader :collection, :criteria, :klass, :query
19
20
 
20
21
  # @attribute [rw] eager_loaded Has the context been eager loaded?
21
22
  attr_accessor :eager_loaded
@@ -66,7 +67,7 @@ module Mongoid
66
67
  def count(document = nil, &block)
67
68
  return super(&block) if block_given?
68
69
  return query.count unless document
69
- klass.collection.find(criteria.and(_id: document.id).selector).count
70
+ collection.find(criteria.and(_id: document.id).selector).count
70
71
  end
71
72
 
72
73
  # Delete all documents in the database that match the selector.
@@ -182,7 +183,7 @@ module Mongoid
182
183
  #
183
184
  # @since 3.0.0
184
185
  def find_and_modify(update, options = {})
185
- if doc = FindAndModify.new(criteria, update, options).result
186
+ if doc = FindAndModify.new(collection, criteria, update, options).result
186
187
  Factory.from_db(klass, doc)
187
188
  end
188
189
  end
@@ -212,8 +213,9 @@ module Mongoid
212
213
  # @since 3.0.0
213
214
  def initialize(criteria)
214
215
  @criteria, @klass, @cache = criteria, criteria.klass, criteria.options[:cache]
216
+ @collection = klass.collection
215
217
  add_type_selection
216
- @query = klass.collection.find(criteria.selector)
218
+ @query = collection.find(criteria.selector)
217
219
  apply_options
218
220
  end
219
221
 
@@ -269,7 +271,7 @@ module Mongoid
269
271
  #
270
272
  # @since 3.0.0
271
273
  def map_reduce(map, reduce)
272
- MapReduce.new(criteria, map, reduce)
274
+ MapReduce.new(collection, criteria, map, reduce)
273
275
  end
274
276
 
275
277
  # Skips the provided number of documents.
@@ -536,7 +538,7 @@ module Mongoid
536
538
  # @since 3.0.0
537
539
  def eager_loaded_ids(docs, metadata)
538
540
  if metadata.stores_foreign_key?
539
- load_ids(metadata.foreign_key).flatten
541
+ docs.flat_map{ |doc| doc.send(metadata.foreign_key) }
540
542
  else
541
543
  docs.map(&:id)
542
544
  end
@@ -582,23 +584,6 @@ module Mongoid
582
584
  @length = 0
583
585
  end
584
586
 
585
- # Loads an array of ids only for the current criteria. Used by eager
586
- # loading to determine the documents to load.
587
- #
588
- # @example Load the related ids.
589
- # criteria.load_ids("person_id")
590
- #
591
- # @param [ String ] key The id or foriegn key string.
592
- #
593
- # @return [ Array<String, Moped::BSON::ObjectId> ] The ids to load.
594
- #
595
- # @since 3.0.0
596
- def load_ids(key)
597
- query.select(key => 1).map do |doc|
598
- doc[key]
599
- end
600
- end
601
-
602
587
  # Apply all the optional criterion.
603
588
  #
604
589
  # @example Apply the options.
@@ -360,8 +360,9 @@ module Mongoid
360
360
  #
361
361
  # @since 2.2.0
362
362
  def includes(*relations)
363
- relations.each do |name|
363
+ relations.flatten.each do |name|
364
364
  metadata = klass.reflect_on_association(name)
365
+ raise Errors::InvalidIncludes.new(klass, relations) unless metadata
365
366
  inclusions.push(metadata) unless inclusions.include?(metadata)
366
367
  end
367
368
  clone
@@ -10,6 +10,7 @@ require "mongoid/errors/invalid_database"
10
10
  require "mongoid/errors/invalid_field"
11
11
  require "mongoid/errors/invalid_field_option"
12
12
  require "mongoid/errors/invalid_find"
13
+ require "mongoid/errors/invalid_includes"
13
14
  require "mongoid/errors/invalid_index"
14
15
  require "mongoid/errors/invalid_options"
15
16
  require "mongoid/errors/invalid_scope"
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Errors
4
+
5
+ # This error is raised when an invalid value is passed to an eager
6
+ # loading query.
7
+ class InvalidIncludes < MongoidError
8
+
9
+ # Initialize the error.
10
+ #
11
+ # @example Initialize the error.
12
+ # InvalidIncludes.new(Band, [ :members ])
13
+ #
14
+ # @param [ Class ] klass The model class.
15
+ # @param [ Array<Object> ] args The arguments passed to the includes.
16
+ #
17
+ # @since 3.0.2
18
+ def initialize(klass, args)
19
+ super(
20
+ compose_message(
21
+ "invalid_includes",
22
+ {
23
+ klass: klass.name,
24
+ args: args.map(&:inspect).join(", "),
25
+ relations: klass.relations.keys.map(&:inspect).join(", ")
26
+ }
27
+ )
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
@@ -8,7 +8,12 @@ module Mongoid
8
8
  # @example Create the error.
9
9
  # Validations.new(person.errors)
10
10
  class Validations < MongoidError
11
+ attr_reader :document
12
+ alias :record :document
13
+
11
14
  def initialize(document)
15
+ @document = document
16
+
12
17
  super(
13
18
  compose_message(
14
19
  "validations",
@@ -47,6 +47,18 @@ module Mongoid
47
47
  consolidated
48
48
  end
49
49
 
50
+ # Deletes an id value from the hash.
51
+ #
52
+ # @example Delete an id value.
53
+ # {}.delete_id
54
+ #
55
+ # @return [ Object ] The deleted value, or nil.
56
+ #
57
+ # @since 3.0.2
58
+ def delete_id
59
+ delete("_id") || delete("id") || delete(:id) || delete(:_id)
60
+ end
61
+
50
62
  # Get the id attribute from this hash, whether it's prefixed with an
51
63
  # underscore or is a symbol.
52
64
  #
@@ -100,6 +100,18 @@ module Mongoid
100
100
  metadata.polymorphic? ? true : metadata.klass.using_object_ids?
101
101
  end
102
102
 
103
+ # Returns true if an array, false if not.
104
+ #
105
+ # @example Is the field resizable?
106
+ # field.resizable?
107
+ #
108
+ # @return [ true, false ] If the field is resizable.
109
+ #
110
+ # @since 3.0.2
111
+ def resizable?
112
+ type.resizable?
113
+ end
114
+
103
115
  private
104
116
 
105
117
  # Evaluate the default proc. In some cases we need to instance exec,
@@ -72,7 +72,7 @@ module Mongoid
72
72
  # person.inc(:score, 2)
73
73
  #
74
74
  # @param [ Symbol ] field The name of the field.
75
- # @param [ Integer ] value The value to increment.
75
+ # @param [ Numeric ] value The value to increment.
76
76
  # @param [ Hash ] options The mongo persistence options.
77
77
  #
78
78
  # @return [ Array<Object> ] The new value of the field.
@@ -12,11 +12,12 @@ module Mongoid
12
12
  # @example Persist the new values.
13
13
  # inc.persist
14
14
  #
15
- # @return [ Object ] The new integer value.
15
+ # @return [ Object ] The new numeric value.
16
16
  #
17
17
  # @since 2.0.0
18
18
  def persist
19
19
  prepare do
20
+ self.value = value.to_f
20
21
  current = document[field] || 0
21
22
  document[field] = current + value
22
23
  execute("$inc")
@@ -29,6 +29,7 @@ module Mongoid
29
29
  end
30
30
  document.post_persist unless result == false
31
31
  end
32
+ document.errors.clear unless validating?
32
33
  document
33
34
  end
34
35
  end
@@ -26,6 +26,7 @@ module Mongoid
26
26
  end
27
27
  end
28
28
  document.post_persist unless result == false
29
+ document.errors.clear unless validating?
29
30
  result
30
31
  end
31
32
  end
@@ -99,6 +99,7 @@ module Mongoid
99
99
  existing.delete(doc)
100
100
  doc.destroy unless doc.embedded?
101
101
  else
102
+ attrs.delete_id
102
103
  metadata.embedded? ? doc.attributes = attrs : doc.update_attributes(attrs)
103
104
  end
104
105
  else
@@ -25,6 +25,7 @@ module Mongoid
25
25
  return if reject?(parent, attributes)
26
26
  @existing = parent.send(metadata.name)
27
27
  if update?
28
+ attributes.delete_id
28
29
  existing.attributes = attributes
29
30
  elsif replace?
30
31
  parent.send(metadata.setter, Factory.build(metadata.klass, attributes))
@@ -19,6 +19,7 @@ module Mongoid
19
19
  init(base, target, metadata) do
20
20
  characterize_one(target)
21
21
  bind_one
22
+ characterize_one(target)
22
23
  target.save if persistable?
23
24
  end
24
25
  end
@@ -38,12 +39,14 @@ module Mongoid
38
39
  if _assigning?
39
40
  base.add_atomic_unset(target)
40
41
  else
41
- destroy if persistable?
42
+ target.destroy if persistable?
42
43
  end
43
44
  unbind_one
44
45
  return nil unless replacement
45
46
  self.target = replacement
46
47
  bind_one
48
+ characterize_one(target)
49
+ target.save if persistable? && !_assigning?
47
50
  self
48
51
  end
49
52
 
@@ -151,7 +151,7 @@ module Mongoid
151
151
  def collection
152
152
  if opts = persistence_options
153
153
  coll = mongo_session.with(opts)[opts[:collection] || collection_name]
154
- clear_persistence_options
154
+ clear_persistence_options unless validating_with_query?
155
155
  coll
156
156
  else
157
157
  mongo_session[collection_name]
@@ -3,6 +3,7 @@ require "mongoid/validations/localizable"
3
3
  require "mongoid/validations/associated"
4
4
  require "mongoid/validations/format"
5
5
  require "mongoid/validations/length"
6
+ require "mongoid/validations/queryable"
6
7
  require "mongoid/validations/presence"
7
8
  require "mongoid/validations/uniqueness"
8
9
 
@@ -89,6 +90,18 @@ module Mongoid
89
90
  Threaded.validated?(self)
90
91
  end
91
92
 
93
+ # Are we currently performing a validation that has a query?
94
+ #
95
+ # @example Are we validating with a query?
96
+ # document.validating_with_query?
97
+ #
98
+ # @return [ true, false ] If we are validating with a query.
99
+ #
100
+ # @since 3.0.2
101
+ def validating_with_query?
102
+ self.class.validating_with_query?
103
+ end
104
+
92
105
  module ClassMethods
93
106
 
94
107
  # Validates whether or not an association is valid or not. Will correctly
@@ -194,13 +207,15 @@ module Mongoid
194
207
  if args.first == PresenceValidator
195
208
  args.last[:attributes].each do |name|
196
209
  metadata = relations[name.to_s]
197
- autosave(metadata.merge!(autosave: true)) if metadata
210
+ if metadata && metadata[:autosave] != false
211
+ autosave(metadata.merge!(autosave: true))
212
+ end
198
213
  end
199
214
  end
200
215
  super
201
216
  end
202
217
 
203
- protected
218
+ private
204
219
 
205
220
  # Adds an associated validator for the relation if the validate option
206
221
  # was not provided or set to true.
@@ -216,6 +231,18 @@ module Mongoid
216
231
  validates_associated(metadata.name)
217
232
  end
218
233
  end
234
+
235
+ # Are we currently performing a validation that has a query?
236
+ #
237
+ # @example Are we validating with a query?
238
+ # Model.validating_with_query?
239
+ #
240
+ # @return [ true, false ] If we are validating with a query.
241
+ #
242
+ # @since 3.0.2
243
+ def validating_with_query?
244
+ Threaded.executing?("#{name}-validate-with-query")
245
+ end
219
246
  end
220
247
  end
221
248
  end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Validations
4
+ module Queryable
5
+
6
+ # Wrap the validation inside the an execution block that alert's the
7
+ # session not to clear it's persistence options.
8
+ #
9
+ # @example Execute the validation with a query.
10
+ # with_query(document) do
11
+ # #...
12
+ # end
13
+ #
14
+ # @param [ Document ] document The document being validated.
15
+ #
16
+ # @return [ Object ] The result of the yield.
17
+ #
18
+ # @since 3.0.2
19
+ def with_query(document)
20
+ begin
21
+ Threaded.begin("#{klass.name}-validate-with-query")
22
+ yield
23
+ ensure
24
+ klass.clear_persistence_options unless document.errors.empty?
25
+ Threaded.exit("#{klass.name}-validate-with-query")
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -14,6 +14,8 @@ module Mongoid
14
14
  # validates_uniqueness_of :title
15
15
  # end
16
16
  class UniquenessValidator < ActiveModel::EachValidator
17
+ include Queryable
18
+
17
19
  attr_reader :klass
18
20
 
19
21
  # Unfortunately, we have to tie Uniqueness validators to a class.
@@ -41,12 +43,14 @@ module Mongoid
41
43
  #
42
44
  # @since 1.0.0
43
45
  def validate_each(document, attribute, value)
44
- attrib, val = to_validate(document, attribute, value)
45
- return unless validation_required?(document, attrib)
46
- if document.embedded?
47
- validate_embedded(document, attrib, val)
48
- else
49
- validate_root(document, attrib, val)
46
+ with_query(document) do
47
+ attrib, val = to_validate(document, attribute, value)
48
+ return unless validation_required?(document, attrib)
49
+ if document.embedded?
50
+ validate_embedded(document, attrib, val)
51
+ else
52
+ validate_root(document, attrib, val)
53
+ end
50
54
  end
51
55
  end
52
56
 
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid
3
- VERSION = "3.0.1"
3
+ VERSION = "3.0.2"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 3.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-12 00:00:00.000000000 Z
12
+ date: 2012-07-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - ~>
52
52
  - !ruby/object:Gem::Version
53
- version: 1.1.1
53
+ version: 1.1.3
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,7 +58,7 @@ dependencies:
58
58
  requirements:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: 1.1.1
61
+ version: 1.1.3
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: origin
64
64
  requirement: !ruby/object:Gem::Requirement
@@ -129,6 +129,7 @@ files:
129
129
  - lib/mongoid/errors/invalid_field.rb
130
130
  - lib/mongoid/errors/invalid_field_option.rb
131
131
  - lib/mongoid/errors/invalid_find.rb
132
+ - lib/mongoid/errors/invalid_includes.rb
132
133
  - lib/mongoid/errors/invalid_index.rb
133
134
  - lib/mongoid/errors/invalid_options.rb
134
135
  - lib/mongoid/errors/invalid_scope.rb
@@ -319,6 +320,7 @@ files:
319
320
  - lib/mongoid/validations/length.rb
320
321
  - lib/mongoid/validations/localizable.rb
321
322
  - lib/mongoid/validations/presence.rb
323
+ - lib/mongoid/validations/queryable.rb
322
324
  - lib/mongoid/validations/uniqueness.rb
323
325
  - lib/mongoid/validations.rb
324
326
  - lib/mongoid/version.rb
@@ -352,7 +354,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
352
354
  version: '0'
353
355
  segments:
354
356
  - 0
355
- hash: 842230721201432915
357
+ hash: -2401816609188543871
356
358
  required_rubygems_version: !ruby/object:Gem::Requirement
357
359
  none: false
358
360
  requirements: