mongoid 2.2.6 → 2.3.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 (80) hide show
  1. data/CHANGELOG.md +2 -858
  2. data/Rakefile +2 -5
  3. data/lib/mongoid.rb +1 -1
  4. data/lib/mongoid/attributes.rb +68 -18
  5. data/lib/mongoid/attributes/processing.rb +4 -3
  6. data/lib/mongoid/callbacks.rb +102 -0
  7. data/lib/mongoid/collection.rb +1 -1
  8. data/lib/mongoid/components.rb +2 -1
  9. data/lib/mongoid/contexts/enumerable.rb +0 -24
  10. data/lib/mongoid/contexts/mongo.rb +2 -2
  11. data/lib/mongoid/copyable.rb +3 -1
  12. data/lib/mongoid/criteria.rb +18 -10
  13. data/lib/mongoid/criterion/complex.rb +11 -0
  14. data/lib/mongoid/criterion/inclusion.rb +38 -7
  15. data/lib/mongoid/criterion/optional.rb +2 -7
  16. data/lib/mongoid/criterion/selector.rb +1 -0
  17. data/lib/mongoid/dirty.rb +19 -0
  18. data/lib/mongoid/document.rb +16 -12
  19. data/lib/mongoid/extensions/hash/criteria_helpers.rb +1 -1
  20. data/lib/mongoid/extensions/object/checks.rb +4 -1
  21. data/lib/mongoid/extensions/object_id/conversions.rb +4 -2
  22. data/lib/mongoid/extensions/string/inflections.rb +2 -2
  23. data/lib/mongoid/factory.rb +7 -2
  24. data/lib/mongoid/fields.rb +4 -10
  25. data/lib/mongoid/fields/serializable.rb +18 -2
  26. data/lib/mongoid/fields/serializable/integer.rb +17 -5
  27. data/lib/mongoid/fields/serializable/localized.rb +41 -0
  28. data/lib/mongoid/finders.rb +5 -4
  29. data/lib/mongoid/hierarchy.rb +87 -84
  30. data/lib/mongoid/identity.rb +4 -2
  31. data/lib/mongoid/keys.rb +2 -1
  32. data/lib/mongoid/logger.rb +1 -7
  33. data/lib/mongoid/matchers/and.rb +30 -0
  34. data/lib/mongoid/matchers/in.rb +1 -1
  35. data/lib/mongoid/matchers/nin.rb +1 -1
  36. data/lib/mongoid/matchers/strategies.rb +6 -4
  37. data/lib/mongoid/multi_parameter_attributes.rb +3 -2
  38. data/lib/mongoid/named_scope.rb +3 -13
  39. data/lib/mongoid/nested_attributes.rb +1 -1
  40. data/lib/mongoid/paranoia.rb +2 -3
  41. data/lib/mongoid/persistence.rb +9 -5
  42. data/lib/mongoid/persistence/atomic/operation.rb +1 -1
  43. data/lib/mongoid/persistence/deletion.rb +1 -1
  44. data/lib/mongoid/persistence/insertion.rb +1 -1
  45. data/lib/mongoid/persistence/modification.rb +1 -1
  46. data/lib/mongoid/railtie.rb +1 -1
  47. data/lib/mongoid/railties/database.rake +9 -1
  48. data/lib/mongoid/relations.rb +1 -0
  49. data/lib/mongoid/relations/accessors.rb +1 -1
  50. data/lib/mongoid/relations/builders.rb +6 -4
  51. data/lib/mongoid/relations/builders/referenced/many.rb +1 -23
  52. data/lib/mongoid/relations/builders/referenced/one.rb +1 -1
  53. data/lib/mongoid/relations/cascading.rb +5 -3
  54. data/lib/mongoid/relations/conversions.rb +35 -0
  55. data/lib/mongoid/relations/embedded/atomic.rb +3 -3
  56. data/lib/mongoid/relations/embedded/in.rb +1 -1
  57. data/lib/mongoid/relations/embedded/many.rb +16 -13
  58. data/lib/mongoid/relations/embedded/one.rb +3 -3
  59. data/lib/mongoid/relations/metadata.rb +19 -15
  60. data/lib/mongoid/relations/proxy.rb +4 -5
  61. data/lib/mongoid/relations/referenced/in.rb +1 -1
  62. data/lib/mongoid/relations/referenced/many.rb +12 -31
  63. data/lib/mongoid/relations/referenced/many_to_many.rb +4 -5
  64. data/lib/mongoid/relations/referenced/one.rb +6 -8
  65. data/lib/mongoid/relations/synchronization.rb +3 -5
  66. data/lib/mongoid/safety.rb +34 -4
  67. data/lib/mongoid/serialization.rb +20 -6
  68. data/lib/mongoid/threaded.rb +47 -0
  69. data/lib/mongoid/timestamps.rb +1 -0
  70. data/lib/mongoid/timestamps/created.rb +1 -8
  71. data/lib/mongoid/timestamps/timeless.rb +50 -0
  72. data/lib/mongoid/timestamps/updated.rb +2 -9
  73. data/lib/mongoid/validations.rb +0 -2
  74. data/lib/mongoid/validations/associated.rb +1 -2
  75. data/lib/mongoid/validations/uniqueness.rb +89 -36
  76. data/lib/mongoid/version.rb +1 -1
  77. data/lib/mongoid/versioning.rb +5 -6
  78. data/lib/rails/generators/mongoid_generator.rb +1 -1
  79. data/lib/rails/mongoid.rb +14 -5
  80. metadata +27 -23
@@ -32,6 +32,17 @@ module Mongoid #:nodoc:
32
32
  [@key, @operator].hash
33
33
  end
34
34
 
35
+ # Create a mongo query with given value
36
+ #
37
+ # @example Create query
38
+ # criterion.to_mongo_hash(value)
39
+ #
40
+ # @params [] Whatever is a valid input for given operator
41
+ # @return [ Hash ] The query
42
+ def to_mongo_query(v)
43
+ {"$#{self.operator}" => v}
44
+ end
45
+
35
46
  # Is the criterion equal to the other?
36
47
  #
37
48
  # @example Check equality.
@@ -20,6 +20,27 @@ module Mongoid #:nodoc:
20
20
  end
21
21
  alias :all_in :all
22
22
 
23
+ # Adds a criterion to the criteria that specifies multiple expressions
24
+ # that *all* must match. This uses MongoDB's $and operator under the
25
+ # covers.
26
+ #
27
+ # @example Match all provided expressions.
28
+ # criteria.all_of(:name => value, :age.gt => 18)
29
+ #
30
+ # @param [ Array<Hash> ] Multiple hash expressions.
31
+ #
32
+ # @return [ Criteria ] The criteria object.
33
+ #
34
+ # @since 2.3.0
35
+ def all_of(*args)
36
+ clone.tap do |crit|
37
+ criterion = @selector["$and"] || []
38
+ converted = BSON::ObjectId.convert(klass, args.flatten)
39
+ expanded = converted.collect { |hash| hash.expand_complex_criteria }
40
+ crit.selector["$and"] = criterion.concat(expanded)
41
+ end
42
+ end
43
+
23
44
  # Adds a criterion to the +Criteria+ that specifies values where any can
24
45
  # be matched in order to return results. This is similar to an SQL "IN"
25
46
  # clause. The MongoDB conditional operator that will be used is "$in".
@@ -216,14 +237,24 @@ module Mongoid #:nodoc:
216
237
  BSON::ObjectId.convert(klass, selector || {}, false).expand_complex_criteria
217
238
  end
218
239
 
240
+ # @todo: Durran: 3.0.0: refactor the merging into separate strategies
241
+ # to clean this funkiness up.
219
242
  selector.each_pair do |key, value|
220
- if crit.selector.has_key?(key) &&
221
- crit.selector[key].respond_to?(:merge!) &&
222
- value.respond_to?(:merge!)
223
- crit.selector[key] =
224
- crit.selector[key].merge!(value) do |key, old, new|
225
- key == '$in' ? old & new : new
243
+ if crit.selector.has_key?(key)
244
+ if key.to_s =~ /^(|_)id$/
245
+ if crit.selector.has_key?("$and")
246
+ crit.selector["$and"] << { key => value }
247
+ else
248
+ crit.selector["$and"] = [{ key => crit.selector.delete(key) }, { key => value }]
226
249
  end
250
+ elsif crit.selector[key].respond_to?(:merge) && value.respond_to?(:merge)
251
+ crit.selector[key] =
252
+ crit.selector[key].merge(value) do |key, old, new|
253
+ key == '$in' ? old & new : new
254
+ end
255
+ else
256
+ crit.selector[key] = value
257
+ end
227
258
  else
228
259
  crit.selector[key] = value
229
260
  end
@@ -266,7 +297,7 @@ module Mongoid #:nodoc:
266
297
  #
267
298
  # @since 2.2.1
268
299
  def from_map_or_db(criteria)
269
- IdentityMap.get(klass, criteria.selector[:_id]) || criteria.one
300
+ IdentityMap.get(klass, criteria.extract_id) || criteria.one
270
301
  end
271
302
  end
272
303
  end
@@ -91,14 +91,9 @@ module Mongoid #:nodoc:
91
91
  def for_ids(*ids)
92
92
  ids.flatten!
93
93
  if ids.size > 1
94
- any_in(
95
- :_id => ::BSON::ObjectId.convert(klass, ids)
96
- )
94
+ where(:_id.in => ::BSON::ObjectId.convert(klass, ids))
97
95
  else
98
- clone.tap do |crit|
99
- crit.selector[:_id] =
100
- ::BSON::ObjectId.convert(klass, ids.first)
101
- end
96
+ where(:_id => ids.first)
102
97
  end
103
98
  end
104
99
 
@@ -30,6 +30,7 @@ module Mongoid #:nodoc:
30
30
  #
31
31
  # @since 2.0.0
32
32
  def []=(key, value)
33
+ key = "#{key}.#{::I18n.locale}" if klass.fields[key.to_s].try(:localized?)
33
34
  super(key, try_to_typecast(key, value))
34
35
  end
35
36
 
@@ -108,5 +108,24 @@ module Mongoid #:nodoc:
108
108
  return false unless changed_attributes.include?(attr)
109
109
  changed_attributes[attr] != attributes[attr]
110
110
  end
111
+
112
+ # Override Active Model's behaviour here in order to stay away from
113
+ # infinite loops on getter/setter overrides.
114
+ #
115
+ # @example Flag an attribute as changing.
116
+ # document.attribute_will_change!(:name)
117
+ #
118
+ # @param [ Symbol ] attr The attribute.
119
+ #
120
+ # @return [ Object ] The value of the attribute.
121
+ #
122
+ # @since 2.3.0
123
+ def attribute_will_change!(attr)
124
+ unless changed_attributes.include?(attr)
125
+ value = read_attribute(attr)
126
+ value = value.duplicable? ? value.clone : value
127
+ changed_attributes[attr] = value
128
+ end
129
+ end
111
130
  end
112
131
  end
@@ -119,15 +119,19 @@ module Mongoid #:nodoc:
119
119
  # Person.new(:title => "Sir")
120
120
  #
121
121
  # @param [ Hash ] attrs The attributes to set up the document with.
122
+ # @param [ Hash ] options A mass-assignment protection options. Supports
123
+ # :as and :without_protection
122
124
  #
123
125
  # @return [ Document ] A new document.
124
- def initialize(attrs = nil)
125
- building do
126
+ def initialize(attrs = nil, options = nil)
127
+ _building do
126
128
  @new_record = true
127
- @attributes = apply_default_attributes
128
- process(attrs) do
129
+ @attributes ||= {}
130
+ options ||= {}
131
+ process(attrs, options[:as] || :default, !options[:without_protection]) do
129
132
  yield self if block_given?
130
133
  identify
134
+ apply_defaults
131
135
  end
132
136
  run_callbacks(:initialize) { self }
133
137
  end
@@ -150,7 +154,7 @@ module Mongoid #:nodoc:
150
154
  end
151
155
  @attributes = {}.merge(reloaded || {})
152
156
  changed_attributes.clear
153
- apply_default_attributes
157
+ apply_defaults
154
158
  tap do
155
159
  reload_relations
156
160
  run_callbacks(:initialize)
@@ -199,13 +203,13 @@ module Mongoid #:nodoc:
199
203
  # @return [ Document ] An instance of the specified class.
200
204
  def becomes(klass)
201
205
  unless klass.include?(Mongoid::Document)
202
- raise ArgumentError, 'A class which includes Mongoid::Document is expected'
206
+ raise ArgumentError, "A class which includes Mongoid::Document is expected"
203
207
  end
204
- klass.new.tap do |became|
205
- became.instance_variable_set('@attributes', @attributes)
206
- became.instance_variable_set('@errors', @errors)
207
- became.instance_variable_set('@new_record', new_record?)
208
- became.instance_variable_set('@destroyed', destroyed?)
208
+ klass.instantiate(frozen? ? attributes.dup : attributes).tap do |became|
209
+ became.instance_variable_set(:@errors, errors)
210
+ became.instance_variable_set(:@new_record, new_record?)
211
+ became.instance_variable_set(:@destroyed, destroyed?)
212
+ became._type = klass.to_s
209
213
  end
210
214
  end
211
215
 
@@ -261,7 +265,7 @@ module Mongoid #:nodoc:
261
265
  attributes = attrs || {}
262
266
  allocate.tap do |doc|
263
267
  doc.instance_variable_set(:@attributes, attributes)
264
- doc.send(:apply_default_attributes)
268
+ doc.send(:apply_defaults)
265
269
  IdentityMap.set(doc)
266
270
  doc.run_callbacks(:initialize) { doc }
267
271
  end
@@ -20,7 +20,7 @@ module Mongoid #:nodoc:
20
20
  case k
21
21
  when Mongoid::Criterion::Complex
22
22
  hsh[k.key] ||= {}
23
- hsh[k.key].merge!({"$#{k.operator}" => v})
23
+ hsh[k.key].merge!(k.to_mongo_query(v))
24
24
  else
25
25
  hsh[k] = v
26
26
  end
@@ -14,6 +14,9 @@ module Mongoid #:nodoc:
14
14
  # @example Is the array vacant?
15
15
  # [].vacant?
16
16
  #
17
+ # @example Is the hash vacant?
18
+ # {}.vacant?
19
+ #
17
20
  # @example Is the object vacant?
18
21
  # nil.vacant?
19
22
  #
@@ -21,7 +24,7 @@ module Mongoid #:nodoc:
21
24
  #
22
25
  # @since 2.0.2
23
26
  def _vacant?
24
- is_a?(::Array) || is_a?(::String) ? empty? : !self
27
+ is_a?(::Enumerable) || is_a?(::String) ? empty? : !self
25
28
  end
26
29
  end
27
30
  end
@@ -38,7 +38,7 @@ module Mongoid #:nodoc:
38
38
  if args.unconvertable_to_bson?
39
39
  args
40
40
  else
41
- BSON::ObjectId.legal?(args) ? BSON::ObjectId.from_string(args) : args
41
+ BSON::ObjectId.from_string(args)
42
42
  end
43
43
  when ::Array
44
44
  args.delete_if { |arg| arg.blank? } if reject_blank
@@ -47,7 +47,9 @@ module Mongoid #:nodoc:
47
47
  args.tap do |hash|
48
48
  hash.each_pair do |key, value|
49
49
  next unless klass.object_id_field?(key)
50
- hash[key] = convert(klass, value, reject_blank)
50
+ begin
51
+ hash[key] = convert(klass, value, reject_blank)
52
+ rescue BSON::InvalidObjectId; end
51
53
  end
52
54
  end
53
55
  else
@@ -18,7 +18,7 @@ module Mongoid #:nodoc:
18
18
  CHAR_CONV = {
19
19
  " " => "-",
20
20
  "!" => "-excl-",
21
- "\"" => "-dblquo-",
21
+ "\"" => "-bckslsh-",
22
22
  "#" => "-hash-",
23
23
  "$" => "-dol-",
24
24
  "%" => "-perc-",
@@ -48,7 +48,7 @@ module Mongoid #:nodoc:
48
48
  "{" => "-ocurly-",
49
49
  "|" => "-pipe-",
50
50
  "}" => "-clcurly-",
51
- "~" => "-tilde-"
51
+ "~" => "-tilda-"
52
52
  }
53
53
 
54
54
  REVERSALS = {
@@ -12,11 +12,16 @@ module Mongoid #:nodoc:
12
12
  #
13
13
  # @param [ Class ] klass The class to instantiate from if _type is not present.
14
14
  # @param [ Hash ] attributes The document attributes.
15
+ # @param [ Hash ] optiosn The mass assignment scoping options.
15
16
  #
16
17
  # @return [ Document ] The instantiated document.
17
- def build(klass, attributes = {})
18
+ def build(klass, attributes = {}, options = {})
18
19
  type = (attributes || {})["_type"]
19
- (type && klass._types.include?(type)) ? type.constantize.new(attributes) : klass.new(attributes)
20
+ if type && klass._types.include?(type)
21
+ type.constantize.new(attributes, options)
22
+ else
23
+ klass.new(attributes, options)
24
+ end
20
25
  end
21
26
 
22
27
  # Builds a new +Document+ from the supplied attributes loaded from the
@@ -13,6 +13,7 @@ require "mongoid/fields/serializable/hash"
13
13
  require "mongoid/fields/serializable/integer"
14
14
  require "mongoid/fields/serializable/bignum"
15
15
  require "mongoid/fields/serializable/fixnum"
16
+ require "mongoid/fields/serializable/localized"
16
17
  require "mongoid/fields/serializable/nil_class"
17
18
  require "mongoid/fields/serializable/object"
18
19
  require "mongoid/fields/serializable/object_id"
@@ -227,20 +228,13 @@ module Mongoid #:nodoc
227
228
  # @param [ Hash ] options The hash of options.
228
229
  def add_field(name, options = {})
229
230
  meth = options.delete(:as) || name
230
- Mappings.for(
231
- options[:type], options[:identity]
232
- ).instantiate(name, options).tap do |field|
231
+ type = options[:localize] ? Fields::Serializable::Localized : options[:type]
232
+ Mappings.for(type, options[:identity]).instantiate(name, options).tap do |field|
233
233
  fields[name] = field
234
234
  defaults << name unless field.default.nil?
235
235
  create_accessors(name, meth, options)
236
236
  process_options(field)
237
-
238
- # @todo Durran: Refactor this once we can depend on at least
239
- # ActiveModel greater than 3.0.9. They finally have the ability then
240
- # to add attribute methods one at a time. This code will make class
241
- # load times extremely slow.
242
- undefine_attribute_methods
243
- define_attribute_methods(fields.keys)
237
+ define_attribute_method(name)
244
238
  end
245
239
  end
246
240
 
@@ -26,7 +26,7 @@ module Mongoid #:nodoc:
26
26
  extend ActiveSupport::Concern
27
27
 
28
28
  # Set readers for the instance variables.
29
- attr_accessor :default, :label, :name, :options
29
+ attr_accessor :default, :label, :localize, :name, :options
30
30
 
31
31
  # When reading the field do we need to cast the value? This holds true when
32
32
  # times are stored or for big decimals which are stored as strings.
@@ -89,6 +89,18 @@ module Mongoid #:nodoc:
89
89
  end
90
90
  end
91
91
 
92
+ # Is the field localized or not?
93
+ #
94
+ # @example Is the field localized?
95
+ # field.localized?
96
+ #
97
+ # @return [ true, false ] If the field is localized.
98
+ #
99
+ # @since 2.3.0
100
+ def localized?
101
+ !!@localize
102
+ end
103
+
92
104
  # Get the metadata for the field if its a foreign key.
93
105
  #
94
106
  # @example Get the metadata.
@@ -168,8 +180,12 @@ module Mongoid #:nodoc:
168
180
  allocate.tap do |field|
169
181
  field.name = name
170
182
  field.options = options
171
- field.default = options[:default]
172
183
  field.label = options[:label]
184
+ field.localize = options[:localize]
185
+ field.default = options[:default]
186
+ unless field.default
187
+ field.default = {} if field.localized?
188
+ end
173
189
  end
174
190
  end
175
191
  end
@@ -20,11 +20,23 @@ module Mongoid #:nodoc:
20
20
  # @since 2.1.0
21
21
  def serialize(object)
22
22
  return nil if object.blank?
23
- begin
24
- object.to_s =~ /(^[-+]?[0-9]+$)|(\.0+)$/ ? Integer(object) : Float(object)
25
- rescue
26
- object
27
- end
23
+ numeric(object) rescue object
24
+ end
25
+
26
+ private
27
+
28
+ # Get the numeric value for the provided object.
29
+ #
30
+ # @example Get the numeric value.
31
+ # field.numeric("1120")
32
+ #
33
+ # @param [ Object ] object The object to convert.
34
+ #
35
+ # @return [ Integer, Float ] The number.
36
+ #
37
+ # @since 2.3.0
38
+ def numeric(object)
39
+ object.to_s =~ /(^[-+]?[0-9]+$)|(\.0+)$/ ? object.to_i : Float(object)
28
40
  end
29
41
  end
30
42
  end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Fields #:nodoc:
4
+ module Serializable #:nodoc:
5
+
6
+ # Defines the behaviour for localized string fields.
7
+ class Localized
8
+ include Serializable
9
+
10
+ # Deserialize the object based on the current locale. Will look in the
11
+ # hash for the current locale.
12
+ #
13
+ # @example Get the deserialized value.
14
+ # field.deserialize({ "en" => "testing" })
15
+ #
16
+ # @param [ Hash ] object The hash of translations.
17
+ #
18
+ # @return [ String ] The value for the current locale.
19
+ #
20
+ # @since 2.3.0
21
+ def deserialize(object)
22
+ object[::I18n.locale.to_s]
23
+ end
24
+
25
+ # Convert the provided string into a hash for the locale.
26
+ #
27
+ # @example Serialize the value.
28
+ # field.serialize("testing")
29
+ #
30
+ # @param [ String ] object The string to convert.
31
+ #
32
+ # @return [ Hash ] The locale with string translation.
33
+ #
34
+ # @since 2.3.0
35
+ def serialize(object)
36
+ { ::I18n.locale.to_s => object.try(:to_s) }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -7,10 +7,11 @@ module Mongoid #:nodoc:
7
7
 
8
8
  # Delegate to the criteria methods that are natural for creating a new
9
9
  # criteria.
10
- critera_methods = [ :all_in, :any_in, :any_of, :asc, :ascending, :avg,
11
- :desc, :descending, :excludes, :includes, :limit,
12
- :max, :min, :not_in, :only, :order_by, :skip, :sum,
13
- :without, :where, :update, :update_all, :near ]
10
+ critera_methods = [ :all_in, :all_of, :any_in, :any_of, :asc, :ascending,
11
+ :avg, :desc, :descending, :excludes,
12
+ :includes, :limit, :max, :min, :not_in, :only,
13
+ :order_by, :search, :skip, :sum, :without, :where,
14
+ :update, :update_all, :near ]
14
15
  delegate *(critera_methods.dup << { :to => :criteria })
15
16
 
16
17
  # Find all documents that match the given conditions.