mongoid 2.4.0 → 2.4.12

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 (107) hide show
  1. data/CHANGELOG.md +329 -1
  2. data/Rakefile +0 -9
  3. data/lib/config/locales/bg.yml +1 -0
  4. data/lib/config/locales/de.yml +1 -0
  5. data/lib/config/locales/en-GB.yml +1 -0
  6. data/lib/config/locales/en.yml +1 -0
  7. data/lib/config/locales/es.yml +1 -0
  8. data/lib/config/locales/fr.yml +1 -0
  9. data/lib/config/locales/hi.yml +1 -0
  10. data/lib/config/locales/hu.yml +1 -0
  11. data/lib/config/locales/id.yml +1 -0
  12. data/lib/config/locales/it.yml +1 -0
  13. data/lib/config/locales/ja.yml +1 -0
  14. data/lib/config/locales/kr.yml +1 -0
  15. data/lib/config/locales/nl.yml +1 -0
  16. data/lib/config/locales/pl.yml +1 -0
  17. data/lib/config/locales/pt-BR.yml +1 -0
  18. data/lib/config/locales/pt.yml +1 -0
  19. data/lib/config/locales/ro.yml +1 -0
  20. data/lib/config/locales/ru.yml +1 -0
  21. data/lib/config/locales/sv.yml +1 -0
  22. data/lib/config/locales/vi.yml +1 -0
  23. data/lib/config/locales/zh-CN.yml +1 -0
  24. data/lib/mongoid/atomic/modifiers.rb +56 -15
  25. data/lib/mongoid/atomic.rb +14 -8
  26. data/lib/mongoid/attributes.rb +1 -1
  27. data/lib/mongoid/callbacks.rb +3 -3
  28. data/lib/mongoid/collection.rb +5 -1
  29. data/lib/mongoid/collections/retry.rb +2 -1
  30. data/lib/mongoid/components.rb +2 -1
  31. data/lib/mongoid/config/database.rb +4 -7
  32. data/lib/mongoid/config/environment.rb +4 -2
  33. data/lib/mongoid/config/replset_database.rb +3 -5
  34. data/lib/mongoid/config.rb +18 -0
  35. data/lib/mongoid/contexts/enumerable.rb +26 -1
  36. data/lib/mongoid/contexts/mongo.rb +62 -14
  37. data/lib/mongoid/copyable.rb +38 -8
  38. data/lib/mongoid/criteria.rb +1 -0
  39. data/lib/mongoid/criterion/exclusion.rb +3 -1
  40. data/lib/mongoid/criterion/inclusion.rb +2 -2
  41. data/lib/mongoid/criterion/optional.rb +2 -2
  42. data/lib/mongoid/criterion/selector.rb +25 -4
  43. data/lib/mongoid/cursor.rb +4 -2
  44. data/lib/mongoid/dirty.rb +61 -18
  45. data/lib/mongoid/document.rb +6 -2
  46. data/lib/mongoid/extensions/hash/criteria_helpers.rb +3 -1
  47. data/lib/mongoid/extensions/string/inflections.rb +1 -1
  48. data/lib/mongoid/factory.rb +3 -3
  49. data/lib/mongoid/fields/internal/array.rb +4 -9
  50. data/lib/mongoid/fields/internal/big_decimal.rb +7 -1
  51. data/lib/mongoid/fields/internal/date_time.rb +1 -1
  52. data/lib/mongoid/fields/internal/false_class.rb +10 -0
  53. data/lib/mongoid/fields/internal/foreign_keys/array.rb +22 -8
  54. data/lib/mongoid/fields/internal/set.rb +1 -1
  55. data/lib/mongoid/fields/internal/timekeeping.rb +2 -1
  56. data/lib/mongoid/fields/internal/true_class.rb +10 -0
  57. data/lib/mongoid/fields/mappings.rb +14 -13
  58. data/lib/mongoid/fields/serializable.rb +41 -3
  59. data/lib/mongoid/fields.rb +154 -26
  60. data/lib/mongoid/hierarchy.rb +6 -1
  61. data/lib/mongoid/identity_map.rb +15 -0
  62. data/lib/mongoid/logger.rb +7 -1
  63. data/lib/mongoid/matchers/all.rb +8 -1
  64. data/lib/mongoid/matchers/in.rb +8 -1
  65. data/lib/mongoid/matchers/strategies.rb +6 -2
  66. data/lib/mongoid/multi_database.rb +5 -0
  67. data/lib/mongoid/named_scope.rb +17 -1
  68. data/lib/mongoid/nested_attributes.rb +1 -1
  69. data/lib/mongoid/observer.rb +7 -0
  70. data/lib/mongoid/persistence/insertion.rb +2 -1
  71. data/lib/mongoid/persistence/operations/update.rb +0 -9
  72. data/lib/mongoid/persistence.rb +5 -5
  73. data/lib/mongoid/railtie.rb +22 -4
  74. data/lib/mongoid/relations/bindings/referenced/in.rb +31 -11
  75. data/lib/mongoid/relations/bindings/referenced/many.rb +8 -8
  76. data/lib/mongoid/relations/bindings/referenced/one.rb +8 -8
  77. data/lib/mongoid/relations/builders/nested_attributes/many.rb +3 -1
  78. data/lib/mongoid/relations/cascading/delete.rb +29 -2
  79. data/lib/mongoid/relations/cascading/destroy.rb +18 -1
  80. data/lib/mongoid/relations/cascading/nullify.rb +18 -1
  81. data/lib/mongoid/relations/cascading.rb +0 -1
  82. data/lib/mongoid/relations/embedded/atomic.rb +3 -3
  83. data/lib/mongoid/relations/embedded/many.rb +58 -26
  84. data/lib/mongoid/relations/embedded/one.rb +1 -1
  85. data/lib/mongoid/relations/metadata.rb +33 -1
  86. data/lib/mongoid/relations/one.rb +12 -0
  87. data/lib/mongoid/relations/proxy.rb +6 -3
  88. data/lib/mongoid/relations/referenced/many.rb +14 -9
  89. data/lib/mongoid/relations/referenced/many_to_many.rb +3 -4
  90. data/lib/mongoid/relations/targets/enumerable.rb +9 -3
  91. data/lib/mongoid/relations.rb +3 -1
  92. data/lib/mongoid/serialization.rb +18 -6
  93. data/lib/mongoid/state.rb +1 -1
  94. data/lib/mongoid/threaded.rb +26 -0
  95. data/lib/mongoid/timestamps/updated.rb +1 -1
  96. data/lib/mongoid/validations/format.rb +20 -0
  97. data/lib/mongoid/validations/length.rb +20 -0
  98. data/lib/mongoid/validations/localizable.rb +30 -0
  99. data/lib/mongoid/validations/presence.rb +6 -2
  100. data/lib/mongoid/validations/uniqueness.rb +162 -30
  101. data/lib/mongoid/validations.rb +54 -2
  102. data/lib/mongoid/version.rb +1 -1
  103. data/lib/mongoid/versioning.rb +12 -3
  104. data/lib/mongoid.rb +1 -0
  105. data/lib/rails/mongoid.rb +18 -12
  106. metadata +58 -41
  107. data/lib/mongoid/relations/cascading/strategy.rb +0 -26
@@ -5,19 +5,22 @@ module Mongoid # :nodoc:
5
5
  # This class is the superclass for all relation proxy objects, and contains
6
6
  # common behaviour for all of them.
7
7
  class Proxy
8
- include Threaded::Lifecycle
8
+ alias :extend_proxy :extend
9
9
 
10
10
  # We undefine most methods to get them sent through to the target.
11
11
  instance_methods.each do |method|
12
12
  undef_method(method) unless
13
- method =~ /(^__|^send$|^object_id$|^extend$|^respond_to\?$|^tap$)/
13
+ method =~ /(^__|^send|^object_id|^respond_to|^tap|extend_proxy)/
14
14
  end
15
15
 
16
+ include Threaded::Lifecycle
17
+
16
18
  attr_accessor :base, :loaded, :metadata, :target
17
19
 
18
20
  # Backwards compatibility with Mongoid beta releases.
19
21
  delegate :klass, :to => :metadata
20
22
  delegate :bind_one, :unbind_one, :to => :binding
23
+ delegate :collection_name, :to => :base
21
24
 
22
25
  # Convenience for setting the target and the metadata properties since
23
26
  # all proxies will need to do this.
@@ -33,7 +36,7 @@ module Mongoid # :nodoc:
33
36
  def init(base, target, metadata)
34
37
  @base, @target, @metadata = base, target, metadata
35
38
  yield(self) if block_given?
36
- extend metadata.extension if metadata.extension?
39
+ extend_proxy(metadata.extension) if metadata.extension?
37
40
  end
38
41
 
39
42
  # The default substitutable object for a relation proxy is the clone of
@@ -89,6 +89,7 @@ module Mongoid #:nodoc:
89
89
 
90
90
  Factory.build(type || klass, attributes, options).tap do |doc|
91
91
  append(doc)
92
+ doc.apply_proc_defaults
92
93
  yield(doc) if block_given?
93
94
  doc.run_callbacks(:build) { doc }
94
95
  end
@@ -319,15 +320,13 @@ module Mongoid #:nodoc:
319
320
  def substitute(replacement)
320
321
  tap do |proxy|
321
322
  if replacement
322
- if replacement != proxy.in_memory
323
- new_docs, docs = replacement.compact.uniq, []
324
- new_ids = new_docs.map { |doc| doc.id }
325
- remove_not_in(new_ids)
326
- new_docs.each do |doc|
327
- docs.push(doc) if doc.send(metadata.foreign_key) != base.id
328
- end
329
- proxy.concat(docs)
323
+ new_docs, docs = replacement.compact.uniq, []
324
+ new_ids = new_docs.map { |doc| doc.id }
325
+ remove_not_in(new_ids)
326
+ new_docs.each do |doc|
327
+ docs.push(doc) if doc.send(metadata.foreign_key) != base.id
330
328
  end
329
+ proxy.concat(docs)
331
330
  else
332
331
  proxy.purge
333
332
  end
@@ -564,9 +563,15 @@ module Mongoid #:nodoc:
564
563
  #
565
564
  # @since 2.2.0
566
565
  def eager_load(metadata, ids)
566
+ cleared = false
567
567
  klass, foreign_key = metadata.klass, metadata.foreign_key
568
568
  klass.any_in(foreign_key => ids).each do |doc|
569
- IdentityMap.set_many(doc, foreign_key => doc.send(foreign_key))
569
+ base_id = doc.send(foreign_key)
570
+ unless cleared
571
+ IdentityMap.clear_many(klass, foreign_key => base_id)
572
+ cleared = true
573
+ end
574
+ IdentityMap.set_many(doc, foreign_key => base_id)
570
575
  end
571
576
  end
572
577
 
@@ -75,6 +75,7 @@ module Mongoid # :nodoc:
75
75
  Factory.build(type || klass, attributes, options).tap do |doc|
76
76
  base.send(metadata.foreign_key).push(doc.id)
77
77
  append(doc)
78
+ doc.apply_proc_defaults
78
79
  doc.synced[metadata.inverse_foreign_key] = false
79
80
  yield(doc) if block_given?
80
81
  end
@@ -142,10 +143,8 @@ module Mongoid # :nodoc:
142
143
  # @since 2.0.0.rc.1
143
144
  def substitute(replacement)
144
145
  tap do |proxy|
145
- if replacement != proxy.in_memory
146
- proxy.purge
147
- proxy.push(replacement.compact.uniq) if replacement
148
- end
146
+ proxy.purge
147
+ proxy.push(replacement.compact.uniq) unless replacement.blank?
149
148
  end
150
149
  end
151
150
 
@@ -151,8 +151,9 @@ module Mongoid #:nodoc:
151
151
  end
152
152
  else
153
153
  unloaded.each do |doc|
154
- yield(doc)
155
- loaded.push(added.delete_one(doc) || loaded.delete_one(doc) || doc)
154
+ document = added.delete_one(doc) || loaded.delete_one(doc) || doc
155
+ yield(document)
156
+ loaded.push(document)
156
157
  end
157
158
  end
158
159
  added.each do |doc|
@@ -315,7 +316,12 @@ module Mongoid #:nodoc:
315
316
  #
316
317
  # @since 2.1.0
317
318
  def size
318
- (unloaded ? unloaded.count : loaded.count) + added.count{ |d| d.new? }
319
+ count = (unloaded ? unloaded.count : loaded.count)
320
+ if count.zero?
321
+ count + added.count
322
+ else
323
+ count + added.count{ |d| d.new_record? }
324
+ end
319
325
  end
320
326
  alias :length :size
321
327
 
@@ -119,7 +119,9 @@ module Mongoid # :nodoc:
119
119
  def reload_relations
120
120
  relations.each_pair do |name, meta|
121
121
  if instance_variable_defined?("@#{name}")
122
- remove_instance_variable("@#{name}")
122
+ if _parent.nil? || instance_variable_get("@#{name}") != _parent
123
+ remove_instance_variable("@#{name}")
124
+ end
123
125
  end
124
126
  end
125
127
  end
@@ -31,18 +31,31 @@ module Mongoid # :nodoc:
31
31
  only = Array.wrap(options[:only]).map(&:to_s)
32
32
  except = Array.wrap(options[:except]).map(&:to_s)
33
33
 
34
- except |= ['_type']
34
+ except |= ['_type'] unless Mongoid.include_type_for_serialization
35
35
 
36
36
  field_names = fields.keys.map { |field| field.to_s }
37
- attribute_names = (attributes.keys + field_names).sort
37
+ attribute_names = (as_document.keys + field_names).sort
38
38
  if only.any?
39
39
  attribute_names &= only
40
40
  elsif except.any?
41
41
  attribute_names -= except
42
42
  end
43
43
 
44
- method_names = Array.wrap(options[:methods]).map { |n| n.to_s if respond_to?(n.to_s) }.compact
45
- Hash[(attribute_names + method_names).map { |n| [n, send(n)] }].tap do |attrs|
44
+ method_names = Array.wrap(options[:methods]).map do |name|
45
+ name.to_s if respond_to?(name)
46
+ end.compact
47
+
48
+ {}.tap do |attrs|
49
+ (attribute_names + method_names).each do |name|
50
+ if relations.has_key?(name)
51
+ value = send(name)
52
+ attrs[name] = value ? value.serializable_hash(options) : nil
53
+ elsif attribute_names.include?(name) && !fields.has_key?(name)
54
+ attrs[name] = read_attribute(name)
55
+ else
56
+ attrs[name] = send(name)
57
+ end
58
+ end
46
59
  serialize_relations(attrs, options) if options[:include]
47
60
  end
48
61
  end
@@ -88,8 +101,7 @@ module Mongoid # :nodoc:
88
101
  inclusions = options[:include]
89
102
  relation_names(inclusions).each do |name|
90
103
  metadata = relations[name.to_s]
91
- relation = send(metadata.name)
92
- if relation
104
+ if metadata && relation = send(metadata.name)
93
105
  attributes[metadata.name.to_s] =
94
106
  relation.serializable_hash(relation_options(inclusions, options, name))
95
107
  end
data/lib/mongoid/state.rb CHANGED
@@ -67,7 +67,7 @@ module Mongoid #:nodoc:
67
67
  new? &&
68
68
  embedded_many? &&
69
69
  _parent.persisted? &&
70
- !_parent.delayed_atomic_sets[metadata.name.to_s]
70
+ !_parent.delayed_atomic_sets[atomic_path]
71
71
  end
72
72
 
73
73
  # Determine if the document can be set.
@@ -175,6 +175,32 @@ module Mongoid #:nodoc:
175
175
  Thread.current[:"[mongoid]:safety-options"] = options
176
176
  end
177
177
 
178
+ # Get the field selection options from the current thread.
179
+ #
180
+ # @example Get the field selection options.
181
+ # Threaded.selection
182
+ #
183
+ # @return [ Hash ] The field selection.
184
+ #
185
+ # @since 2.4.4
186
+ def selection
187
+ Thread.current[:"[mongoid]:selection"]
188
+ end
189
+
190
+ # Set the field selection on the current thread.
191
+ #
192
+ # @example Set the field selection.
193
+ # Threaded.selection = { field: 1 }
194
+ #
195
+ # @param [ Hash ] value The current field selection.
196
+ #
197
+ # @return [ Hash ] The field selection.
198
+ #
199
+ # @since 2.4.4
200
+ def selection=(value)
201
+ Thread.current[:"[mongoid]:selection"] = value
202
+ end
203
+
178
204
  # Get the mongoid scope stack for chained criteria.
179
205
  #
180
206
  # @example Get the scope stack.
@@ -7,7 +7,7 @@ module Mongoid #:nodoc:
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- field :updated_at, :type => Time, :versioned => false
10
+ field :updated_at, :type => Time
11
11
  set_callback :save, :before, :set_updated_at, :if => :able_to_set_updated_at?
12
12
  end
13
13
 
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Validations #:nodoc:
4
+
5
+ # Validates that the specified attributes do or do not match a certain
6
+ # regular expression.
7
+ #
8
+ # @example Set up the format validator.
9
+ #
10
+ # class Person
11
+ # include Mongoid::Document
12
+ # field :website
13
+ #
14
+ # validates_format_of :website, :with => URI.regexp
15
+ # end
16
+ class FormatValidator < ActiveModel::Validations::FormatValidator
17
+ include Localizable
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Validations
4
+
5
+ # Validates that the specified attributes do or do not match a certain
6
+ # length.
7
+ #
8
+ # @example Set up the length validator.
9
+ #
10
+ # class Person
11
+ # include Mongoid::Document
12
+ # field :website
13
+ #
14
+ # validates_length_of :website, in: 1..10
15
+ # end
16
+ class LengthValidator < ActiveModel::Validations::LengthValidator
17
+ include Localizable
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Validations
4
+
5
+ # Adds localization support to validations.
6
+ module Localizable
7
+
8
+ # Validates each for localized fields.
9
+ #
10
+ # @example Validate localized fields.
11
+ # validator.validate_each(model, :name, "value")
12
+ #
13
+ # @param [ Document ] document The document.
14
+ # @param [ Symbol, String ] attribute The attribute to validate.
15
+ # @param [ Object ] value The attribute value.
16
+ #
17
+ # @since 2.4.2
18
+ def validate_each(document, attribute, value)
19
+ field = document.fields[attribute.to_s]
20
+ if field.try(:localized?) && !value.blank?
21
+ value.values.each do |_value|
22
+ super(document, attribute, _value)
23
+ end
24
+ else
25
+ super
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -27,9 +27,13 @@ module Mongoid #:nodoc:
27
27
  # @since 2.4.0
28
28
  def validate_each(document, attribute, value)
29
29
  field = document.fields[attribute.to_s]
30
- if field && field.localized? && !value.blank?
30
+ if field.try(:localized?) && !value.blank?
31
31
  value.each_pair do |locale, value|
32
- document.errors.add(attribute, :blank, options) if value.blank?
32
+ document.errors.add(attribute, :blank_on_locale, options.merge(:location => locale)) if value.blank?
33
+ end
34
+ elsif document.relations.has_key?(attribute.to_s)
35
+ if value.blank? && document.send(attribute).blank?
36
+ document.errors.add(attribute, :blank, options)
33
37
  end
34
38
  else
35
39
  document.errors.add(attribute, :blank, options) if value.blank?
@@ -41,38 +41,39 @@ module Mongoid #:nodoc:
41
41
  #
42
42
  # @since 1.0.0
43
43
  def validate_each(document, attribute, value)
44
+ attrib, val = to_validate(document, attribute, value)
45
+ return unless validation_required?(document, attrib)
44
46
  if document.embedded?
45
- return if skip_validation?(document)
46
- relation = document._parent.send(document.metadata.name)
47
- criteria = relation.where(criterion(document, attribute, value))
48
- criteria = scope(criteria, document, attribute)
49
- if document.primary_key == Array.wrap(attribute)
50
- if criteria.count > 1
51
- document.errors.add(
52
- attribute,
53
- :taken, options.except(:case_sensitive, :scope).merge(:value => value)
54
- )
55
- end
56
- else
57
- if criteria.exists?
58
- document.errors.add(
59
- attribute,
60
- :taken,
61
- options.except(:case_sensitive, :scope).merge(:value => value)
62
- )
63
- end
64
- end
47
+ validate_embedded(document, attrib, val)
65
48
  else
66
- criteria = klass.where(criterion(document, attribute, value))
67
- criteria = scope(criteria, document, attribute)
68
- document.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value)) if criteria.exists?
49
+ validate_root(document, attrib, val)
69
50
  end
70
51
  end
71
52
 
72
- protected
53
+ private
54
+
55
+ # Add the error to the document.
56
+ #
57
+ # @api private
58
+ #
59
+ # @example Add the error.
60
+ # validator.add_error(doc, :name, "test")
61
+ #
62
+ # @param [ Document ] document The document to validate.
63
+ # @param [ Symbol ] attribute The name of the attribute.
64
+ # @param [ Object ] value The value of the object.
65
+ #
66
+ # @since 2.4.10
67
+ def add_error(document, attribute, value)
68
+ document.errors.add(
69
+ attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value)
70
+ )
71
+ end
73
72
 
74
73
  # Should the uniqueness validation be case sensitive?
75
74
  #
75
+ # @api private
76
+ #
76
77
  # @example Is the validation case sensitive?
77
78
  # validator.case_sensitive?
78
79
  #
@@ -83,8 +84,38 @@ module Mongoid #:nodoc:
83
84
  !(options[:case_sensitive] == false)
84
85
  end
85
86
 
87
+ # Create the validation criteria.
88
+ #
89
+ # @api private
90
+ #
91
+ # @example Create the criteria.
92
+ # validator.create_criteria(User, user, :name, "syd")
93
+ #
94
+ # @param [ Class, Proxy ] base The base to execute the criteria from.
95
+ # @param [ Document ] document The document to validate.
96
+ # @param [ Symbol ] attribute The name of the attribute.
97
+ # @param [ Object ] value The value of the object.
98
+ #
99
+ # @return [ Criteria ] The criteria.
100
+ #
101
+ # @since 2.4.10
102
+ def create_criteria(base, document, attribute, value)
103
+ field = document.fields[attribute.to_s]
104
+ criteria = base.paranoid ? base.unscoped.where(:deleted_at => nil) : base.unscoped
105
+ if field.try(:localized?)
106
+ criterion(document, attribute, value).each_pair do |key, value|
107
+ criteria.selector.store(key, value)
108
+ end
109
+ else
110
+ criteria = criteria.where(criterion(document, attribute, value))
111
+ end
112
+ scope(criteria, document, attribute)
113
+ end
114
+
86
115
  # Get the default criteria for checking uniqueness.
87
116
  #
117
+ # @api private
118
+ #
88
119
  # @example Get the criteria.
89
120
  # validator.criterion(person, :title, "Sir")
90
121
  #
@@ -97,8 +128,7 @@ module Mongoid #:nodoc:
97
128
  # @since 2.3.0
98
129
  def criterion(document, attribute, value)
99
130
  { attribute => filter(value) }.tap do |selector|
100
- if document.persisted? ||
101
- (document.embedded? && (document.primary_key != Array.wrap(attribute)))
131
+ if document.persisted? && !document.embedded?
102
132
  selector.merge!(:_id => { "$ne" => document.id })
103
133
  end
104
134
  end
@@ -106,6 +136,8 @@ module Mongoid #:nodoc:
106
136
 
107
137
  # Filter the value based on whether the check is case sensitive or not.
108
138
  #
139
+ # @api private
140
+ #
109
141
  # @example Filter the value.
110
142
  # validator.filter("testing")
111
143
  #
@@ -120,6 +152,8 @@ module Mongoid #:nodoc:
120
152
 
121
153
  # Scope the criteria to the scope options provided.
122
154
  #
155
+ # @api private
156
+ #
123
157
  # @example Scope the criteria.
124
158
  # validator.scope(criteria, document)
125
159
  #
@@ -130,16 +164,17 @@ module Mongoid #:nodoc:
130
164
  #
131
165
  # @since 2.3.0
132
166
  def scope(criteria, document, attribute)
133
- unless document.primary_key == Array.wrap(attribute)
134
- Array.wrap(options[:scope]).each do |item|
135
- criteria = criteria.where(item => document.attributes[item.to_s])
136
- end
167
+ Array.wrap(options[:scope]).each do |item|
168
+ criteria = criteria.where(item => document.attributes[item.to_s])
137
169
  end
170
+ criteria = criteria.where(:deleted_at => nil) if document.paranoid?
138
171
  criteria
139
172
  end
140
173
 
141
174
  # Should validation be skipped?
142
175
  #
176
+ # @api private
177
+ #
143
178
  # @example Should the validation be skipped?
144
179
  # validator.skip_validation?(doc)
145
180
  #
@@ -151,6 +186,103 @@ module Mongoid #:nodoc:
151
186
  def skip_validation?(document)
152
187
  !document._parent || document.embedded_one?
153
188
  end
189
+
190
+ # Scope reference has changed?
191
+ #
192
+ # @api private
193
+ #
194
+ # @example Has scope reference changed?
195
+ # validator.scope_value_changed?(doc)
196
+ #
197
+ # @param [ Document ] document The embedded document.
198
+ #
199
+ # @return [ true, false ] If the scope reference has changed.
200
+ #
201
+ # @since
202
+ def scope_value_changed?(document)
203
+ Array.wrap(options[:scope]).any? do |item|
204
+ document.send("attribute_changed?", item.to_s)
205
+ end
206
+ end
207
+
208
+ # Get the name of the field and the value to validate. This is for the
209
+ # case when we validate a relation via the relation name and not the key,
210
+ # we need to send the key name and value to the db, not the relation
211
+ # object.
212
+ #
213
+ # @api private
214
+ #
215
+ # @example Get the name and key to validate.
216
+ # validator.to_validate(doc, :parent, Parent.new)
217
+ #
218
+ # @param [ Document ] document The doc getting validated.
219
+ # @param [ Symbol ] attribute The attribute getting validated.
220
+ # @param [ Object ] value The value of the attribute.
221
+ #
222
+ # @return [ Array<Object, Object> ] The field and value.
223
+ #
224
+ # @since 2.4.4
225
+ def to_validate(document, attribute, value)
226
+ metadata = document.relations[attribute.to_s]
227
+ if metadata && metadata.stores_foreign_key?
228
+ [ metadata.foreign_key, value.id ]
229
+ else
230
+ [ attribute, value ]
231
+ end
232
+ end
233
+
234
+ # Validate an embedded document.
235
+ #
236
+ # @api private
237
+ #
238
+ # @example Validate the embedded document.
239
+ # validator.validate_embedded(doc, :name, "test")
240
+ #
241
+ # @param [ Document ] document The document.
242
+ # @param [ Symbol ] attribute The attribute name.
243
+ # @param [ Object ] value The value.
244
+ #
245
+ # @since 2.4.10
246
+ def validate_embedded(document, attribute, value)
247
+ return if skip_validation?(document)
248
+ relation = document._parent.send(document.metadata.name)
249
+ criteria = create_criteria(relation, document, attribute, value)
250
+ add_error(document, attribute, value) if criteria.count > 1
251
+ end
252
+
253
+ # Validate a root document.
254
+ #
255
+ # @api private
256
+ #
257
+ # @example Validate the root document.
258
+ # validator.validate_root(doc, :name, "test")
259
+ #
260
+ # @param [ Document ] document The document.
261
+ # @param [ Symbol ] attribute The attribute name.
262
+ # @param [ Object ] value The value.
263
+ #
264
+ # @since 2.4.10
265
+ def validate_root(document, attribute, value)
266
+ criteria = create_criteria(klass, document, attribute, value)
267
+ add_error(document, attribute, value) if criteria.exists?
268
+ end
269
+
270
+ # Are we required to validate the document?
271
+ #
272
+ # @example Is validation needed?
273
+ # validator.validation_required?(doc, :field)
274
+ #
275
+ # @param [ Document ] document The document getting validated.
276
+ # @param [ Symbol ] attribute The attribute to validate.
277
+ #
278
+ # @return [ true, false ] If we need to validate.
279
+ #
280
+ # @since 2.4.4
281
+ def validation_required?(document, attribute)
282
+ document.new_record? ||
283
+ document.send("attribute_changed?", attribute.to_s) ||
284
+ scope_value_changed?(document)
285
+ end
154
286
  end
155
287
  end
156
288
  end
@@ -1,7 +1,10 @@
1
1
  # encoding: utf-8
2
+ require "mongoid/validations/localizable"
2
3
  require "mongoid/validations/associated"
3
- require "mongoid/validations/uniqueness"
4
+ require "mongoid/validations/format"
5
+ require "mongoid/validations/length"
4
6
  require "mongoid/validations/presence"
7
+ require "mongoid/validations/uniqueness"
5
8
 
6
9
  module Mongoid #:nodoc:
7
10
 
@@ -45,7 +48,7 @@ module Mongoid #:nodoc:
45
48
  # @since 2.0.0.rc.1
46
49
  def read_attribute_for_validation(attr)
47
50
  attribute = attr.to_s
48
- if relations[attribute]
51
+ if relations.has_key?(attribute)
49
52
  begin_validate
50
53
  relation = send(attr)
51
54
  exit_validate
@@ -88,6 +91,21 @@ module Mongoid #:nodoc:
88
91
 
89
92
  module ClassMethods #:nodoc:
90
93
 
94
+ # Validates whether or not a field matches a certain regular expression.
95
+ #
96
+ # @example
97
+ # class Person
98
+ # include Mongoid::Document
99
+ # field :website
100
+ #
101
+ # validates_format_of :website, :with => URI.regexp
102
+ # end
103
+ #
104
+ # @param [ Array ] *args The arguments to pass to the validator.
105
+ def validates_format_of(*args)
106
+ validates_with(FormatValidator, _merge_attributes(args))
107
+ end
108
+
91
109
  # Validates whether or not an association is valid or not. Will correctly
92
110
  # handle has one and has many associations.
93
111
  #
@@ -123,6 +141,40 @@ module Mongoid #:nodoc:
123
141
  validates_with(UniquenessValidator, _merge_attributes(args))
124
142
  end
125
143
 
144
+ # Validates the format of a field.
145
+ #
146
+ # @example
147
+ # class Person
148
+ # include Mongoid::Document
149
+ # field :title
150
+ #
151
+ # validates_format_of :title, with: /^[a-z0-9 \-_]*$/i
152
+ # end
153
+ #
154
+ # @param [ Array ] args The names of the fields to validate.
155
+ #
156
+ # @since 2.4.0
157
+ def validates_format_of(*args)
158
+ validates_with(Mongoid::Validations::FormatValidator, _merge_attributes(args))
159
+ end
160
+
161
+ # Validates the length of a field.
162
+ #
163
+ # @example
164
+ # class Person
165
+ # include Mongoid::Document
166
+ # field :title
167
+ #
168
+ # validates_length_of :title, minimum: 100
169
+ # end
170
+ #
171
+ # @param [ Array ] args The names of the fields to validate.
172
+ #
173
+ # @since 2.4.0
174
+ def validates_length_of(*args)
175
+ validates_with(Mongoid::Validations::LengthValidator, _merge_attributes(args))
176
+ end
177
+
126
178
  # Validates whether or not a field is present - meaning nil or empty.
127
179
  #
128
180
  # @example
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc
3
- VERSION = "2.4.0"
3
+ VERSION = "2.4.12"
4
4
  end