mongoid 2.0.0.rc.6 → 2.0.0.rc.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/Rakefile +2 -2
  2. data/lib/config/locales/bg.yml +3 -6
  3. data/lib/config/locales/de.yml +2 -5
  4. data/lib/config/locales/en.yml +2 -5
  5. data/lib/config/locales/es.yml +2 -5
  6. data/lib/config/locales/fr.yml +2 -5
  7. data/lib/config/locales/hu.yml +2 -5
  8. data/lib/config/locales/it.yml +2 -5
  9. data/lib/config/locales/kr.yml +2 -5
  10. data/lib/config/locales/nl.yml +2 -5
  11. data/lib/config/locales/pl.yml +2 -5
  12. data/lib/config/locales/pt-br.yml +2 -5
  13. data/lib/config/locales/pt.yml +2 -5
  14. data/lib/config/locales/ro.yml +2 -5
  15. data/lib/config/locales/ru.yml +41 -0
  16. data/lib/config/locales/sv.yml +2 -5
  17. data/lib/config/locales/zh-CN.yml +3 -6
  18. data/lib/mongoid/atomicity.rb +2 -2
  19. data/lib/mongoid/attributes.rb +40 -69
  20. data/lib/mongoid/attributes/processing.rb +142 -0
  21. data/lib/mongoid/collections.rb +1 -0
  22. data/lib/mongoid/components.rb +1 -1
  23. data/lib/mongoid/config.rb +0 -1
  24. data/lib/mongoid/contexts/mongo.rb +13 -9
  25. data/lib/mongoid/criteria.rb +15 -1
  26. data/lib/mongoid/criterion/inclusion.rb +36 -11
  27. data/lib/mongoid/criterion/inspection.rb +3 -1
  28. data/lib/mongoid/criterion/optional.rb +2 -2
  29. data/lib/mongoid/dirty.rb +1 -0
  30. data/lib/mongoid/document.rb +11 -5
  31. data/lib/mongoid/extensions/integer/conversions.rb +2 -2
  32. data/lib/mongoid/extensions/object_id/conversions.rb +62 -32
  33. data/lib/mongoid/field.rb +5 -2
  34. data/lib/mongoid/identity.rb +2 -1
  35. data/lib/mongoid/matchers.rb +5 -32
  36. data/lib/mongoid/matchers/default.rb +53 -10
  37. data/lib/mongoid/matchers/or.rb +30 -0
  38. data/lib/mongoid/matchers/strategies.rb +63 -0
  39. data/lib/mongoid/multi_parameter_attributes.rb +4 -2
  40. data/lib/mongoid/nested_attributes.rb +8 -0
  41. data/lib/mongoid/persistence.rb +2 -1
  42. data/lib/mongoid/persistence/insert.rb +3 -3
  43. data/lib/mongoid/persistence/insert_embedded.rb +2 -1
  44. data/lib/mongoid/relations.rb +2 -1
  45. data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +4 -2
  46. data/lib/mongoid/relations/builders/nested_attributes/many.rb +2 -0
  47. data/lib/mongoid/relations/cascading.rb +1 -1
  48. data/lib/mongoid/relations/cascading/nullify.rb +1 -1
  49. data/lib/mongoid/relations/constraint.rb +42 -0
  50. data/lib/mongoid/relations/cyclic.rb +6 -0
  51. data/lib/mongoid/relations/embedded/many.rb +4 -4
  52. data/lib/mongoid/relations/macros.rb +1 -1
  53. data/lib/mongoid/relations/many.rb +1 -0
  54. data/lib/mongoid/relations/metadata.rb +12 -4
  55. data/lib/mongoid/relations/nested_builder.rb +1 -3
  56. data/lib/mongoid/relations/referenced/many.rb +11 -18
  57. data/lib/mongoid/relations/referenced/many_to_many.rb +19 -32
  58. data/lib/mongoid/relations/referenced/one.rb +3 -1
  59. data/lib/mongoid/timestamps.rb +1 -1
  60. data/lib/mongoid/validations/associated.rb +11 -9
  61. data/lib/mongoid/validations/uniqueness.rb +1 -1
  62. data/lib/mongoid/version.rb +1 -1
  63. data/lib/mongoid/versioning.rb +2 -2
  64. metadata +9 -4
@@ -0,0 +1,142 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Attributes #:nodoc:
4
+
5
+ # This module contains the behavior for processing attributes.
6
+ module Processing
7
+
8
+ # Process the provided attributes casting them to their proper values if a
9
+ # field exists for them on the document. This will be limited to only the
10
+ # attributes provided in the suppied +Hash+ so that no extra nil values get
11
+ # put into the document's attributes.
12
+ #
13
+ # @example Process the attributes.
14
+ # person.process(:title => "sir", :age => 40)
15
+ #
16
+ # @param [ Hash ] attrs The attributes to set.
17
+ #
18
+ # @since 2.0.0.rc.7
19
+ def process(attrs = nil)
20
+ sanitize_for_mass_assignment(attrs || {}).each_pair do |key, value|
21
+ next if pending_attribute?(key, value)
22
+ process_attribute(key, value)
23
+ end
24
+ yield self if block_given?
25
+ process_pending and setup_modifications
26
+ end
27
+
28
+ protected
29
+
30
+ # If the key provided a relation or a nested attribute, where we have to
31
+ # hold off on the setting of the attribute until everything else has been
32
+ # set?
33
+ #
34
+ # @example Is the attribute pending?
35
+ # document.pending_attribute?(:name, "Durran")
36
+ #
37
+ # @param [ Synbol ] key The name of the attribute.
38
+ # @param [ Object ] value The value of the attribute.
39
+ #
40
+ # @return [ true, false ] True if pending, false if not.
41
+ #
42
+ # @since 2.0.0.rc.7
43
+ def pending_attribute?(key, value)
44
+ name = key.to_s
45
+ if relations.has_key?(name)
46
+ pending_relations[name] = value
47
+ return true
48
+ end
49
+ if nested_attributes.include?("#{name}=")
50
+ pending_nested[name] = value
51
+ return true
52
+ end
53
+ return false
54
+ end
55
+
56
+ # Get all the pending relations that need to be set.
57
+ #
58
+ # @example Get the pending relations.
59
+ # document.pending_relations
60
+ #
61
+ # @return [ Hash ] The pending relations in key/value pairs.
62
+ #
63
+ # @since 2.0.0.rc.7
64
+ def pending_relations
65
+ @pending_relations ||= {}
66
+ end
67
+
68
+ # Get all the pending nested attributes that need to be set.
69
+ #
70
+ # @example Get the pending nested attributes.
71
+ # document.pending_nested
72
+ #
73
+ # @return [ Hash ] The pending nested attributes in key/value pairs.
74
+ #
75
+ # @since 2.0.0.rc.7
76
+ def pending_nested
77
+ @pending_nested ||= {}
78
+ end
79
+
80
+ # If the attribute is dynamic, add a field for it with a type of object
81
+ # and then either way set the value.
82
+ #
83
+ # @example Process the attribute.
84
+ # document.process_attribute(name, value)
85
+ #
86
+ # @param [ Symbol ] name The name of the field.
87
+ # @param [ Object ] value The value of the field.
88
+ #
89
+ # @since 2.0.0.rc.7
90
+ def process_attribute(name, value)
91
+ if Mongoid.allow_dynamic_fields && !respond_to?("#{name}=")
92
+ write_attribute(name, value)
93
+ else
94
+ send("#{name}=", value)
95
+ end
96
+ end
97
+
98
+ # Process all the pending nested attributes that needed to wait until
99
+ # ids were set to fire off.
100
+ #
101
+ # @example Process the nested attributes.
102
+ # document.process_nested
103
+ #
104
+ # @since 2.0.0.rc.7
105
+ def process_nested
106
+ pending_nested.each_pair do |name, value|
107
+ send("#{name}=", value)
108
+ end
109
+ end
110
+
111
+ # Process all the pending items, then clear them out.
112
+ #
113
+ # @example Process the pending items.
114
+ # document.process_pending
115
+ #
116
+ # @since 2.0.0.rc.7
117
+ def process_pending
118
+ process_nested and process_relations
119
+ pending_nested.clear and pending_relations.clear
120
+ end
121
+
122
+ # Process all the pending relations that needed to wait until ids were set
123
+ # to fire off.
124
+ #
125
+ # @example Process the relations.
126
+ # document.process_relations
127
+ #
128
+ # @since 2.0.0.rc.7
129
+ def process_relations
130
+ pending_relations.each_pair do |name, value|
131
+ metadata = relations[name]
132
+ if value.is_a?(Hash)
133
+ metadata.nested_builder(value, {}).build(self)
134
+ else
135
+ send("#{name}=", value, :binding => true)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+
@@ -18,6 +18,7 @@ module Mongoid #:nodoc
18
18
  #
19
19
  # Returns: <tt>Mongo::Collection</tt>
20
20
  def collection
21
+ raise Errors::InvalidCollection.new(self) if embedded? && !cyclic
21
22
  self._collection || set_collection
22
23
  add_indexes; self._collection
23
24
  end
@@ -13,8 +13,8 @@ module Mongoid #:nodoc
13
13
  end
14
14
 
15
15
  include ActiveModel::Conversion
16
- include ActiveModel::Naming
17
16
  include ActiveModel::MassAssignmentSecurity
17
+ include ActiveModel::Naming
18
18
  include ActiveModel::Serializers::JSON
19
19
  include ActiveModel::Serializers::Xml
20
20
  include Mongoid::Atomicity
@@ -129,7 +129,6 @@ module Mongoid #:nodoc
129
129
  # @return [ Logger ] The newly set logger.
130
130
  def logger=(logger)
131
131
  @logger = logger
132
- @master.connection.instance_variable_set(:@logger, logger)
133
132
  end
134
133
 
135
134
  # Sets whether the times returned from the database use the ruby or
@@ -20,7 +20,12 @@ module Mongoid #:nodoc:
20
20
  #
21
21
  # A +Hash+ with field values as keys, counts as values
22
22
  def aggregate
23
- klass.collection.group(options[:fields], selector, { :count => 0 }, Javascript.aggregate, true)
23
+ klass.collection.group(
24
+ :key => options[:fields],
25
+ :cond => selector,
26
+ :initial => { :count => 0 },
27
+ :reduce => Javascript.aggregate
28
+ )
24
29
  end
25
30
 
26
31
  # Get the average value for the supplied field.
@@ -141,10 +146,10 @@ module Mongoid #:nodoc:
141
146
  # A +Hash+ with field values as keys, arrays of documents as values.
142
147
  def group
143
148
  klass.collection.group(
144
- options[:fields],
145
- selector,
146
- { :group => [] },
147
- Javascript.group
149
+ :key => options[:fields],
150
+ :cond => selector,
151
+ :initial => { :group => [] },
152
+ :reduce => Javascript.group
148
153
  ).collect do |docs|
149
154
  docs["group"] = docs["group"].collect do |attrs|
150
155
  Mongoid::Factory.build(klass, attrs)
@@ -287,10 +292,9 @@ module Mongoid #:nodoc:
287
292
  # and sum. Will gsub the field name in the supplied reduce function.
288
293
  def grouped(start, field, reduce)
289
294
  collection = klass.collection.group(
290
- nil,
291
- selector,
292
- { start => "start" },
293
- reduce.gsub("[field]", field)
295
+ :cond => selector,
296
+ :initial => { start => "start" },
297
+ :reduce => reduce.gsub("[field]", field)
294
298
  )
295
299
  collection.empty? ? nil : collection.first[start.to_s]
296
300
  end
@@ -195,6 +195,18 @@ module Mongoid #:nodoc:
195
195
  end
196
196
  alias :to_ary :to_a
197
197
 
198
+ # Needed to properly get a criteria back as json
199
+ #
200
+ # @example Get the criteria as json.
201
+ # Person.where(:title => "Sir").as_json
202
+ #
203
+ # @param [ Hash ] options Options to pass through to the serializer.
204
+ #
205
+ # @return [ String ] The JSON string.
206
+ def as_json(options = nil)
207
+ entries.as_json(options)
208
+ end
209
+
198
210
  class << self
199
211
 
200
212
  # Encaspulates the behavior of taking arguments and parsing them into a
@@ -297,6 +309,7 @@ module Mongoid #:nodoc:
297
309
  def initialize_copy(other)
298
310
  @selector = other.selector.dup
299
311
  @options = other.options.dup
312
+ @context = nil
300
313
  end
301
314
 
302
315
  # Update the selector setting the operator on the value for each key in the
@@ -307,7 +320,8 @@ module Mongoid #:nodoc:
307
320
  # <tt>criteria.update_selector({ :field => "value" }, "$in")</tt>
308
321
  def update_selector(attributes, operator)
309
322
  clone.tap do |crit|
310
- attributes.each do |key, value|
323
+ converted = BSON::ObjectId.convert(klass, attributes || {})
324
+ converted.each do |key, value|
311
325
  unless crit.selector[key]
312
326
  crit.selector[key] = { operator => value }
313
327
  else
@@ -50,26 +50,50 @@ module Mongoid #:nodoc:
50
50
  def any_of(*args)
51
51
  clone.tap do |crit|
52
52
  criterion = @selector["$or"] || []
53
- expanded = args.collect(&:expand_complex_criteria)
53
+ converted = BSON::ObjectId.convert(klass, args.flatten)
54
+ expanded = converted.collect(&:expand_complex_criteria)
54
55
  crit.selector["$or"] = criterion.concat(expanded)
55
56
  end
56
57
  end
57
58
  alias :or :any_of
58
59
 
59
- # Using the existing criteria, find a document by a single id, multiple
60
- # ids, or using a conditions hash.
60
+ # Find the matchind document in the criteria, either based on id or
61
+ # conditions.
61
62
  #
62
- # @example Find a single document by id.
63
- # Person.where(:title => "Sir").find(id)
63
+ # @todo Durran: DRY up duplicated code in a few places.
64
64
  #
65
- # @example Find multiple documents by ids.
66
- # Person.where(:title => "Sir").find([ id_one, id_two ])
65
+ # @example Find by an id.
66
+ # criteria.find(BSON::ObjectId.new)
67
67
  #
68
- # @return [ Document, Array<Document> ] The matching document(s).
68
+ # @example Find by multiple ids.
69
+ # criteria.find([ BSON::ObjectId.new, BSON::ObjectId.new ])
69
70
  #
70
- # @since 2.0.0.rc.1
71
+ # @example Conditionally find all matching documents.
72
+ # criteria.find(:all, :conditions => { :title => "Sir" })
73
+ #
74
+ # @example Conditionally find the first document.
75
+ # criteria.find(:first, :conditions => { :title => "Sir" })
76
+ #
77
+ # @example Conditionally find the last document.
78
+ # criteria.find(:last, :conditions => { :title => "Sir" })
79
+ #
80
+ # @param [ Symbol, BSON::ObjectId, Array<BSON::ObjectId> ] arg The
81
+ # argument to search with.
82
+ # @param [ Hash ] options The options to search with.
83
+ #
84
+ # @return [ Document, Criteria ] The matching document(s).
71
85
  def find(*args)
72
- id_criteria(*args)
86
+ raise Errors::InvalidOptions.new(
87
+ :calling_document_find_with_nil_is_invalid, {}
88
+ ) if args[0].nil?
89
+ type, criteria = Criteria.parse!(klass, embedded, *args)
90
+ criteria.merge(self) if criteria.is_a?(Criteria)
91
+ case type
92
+ when :first then return criteria.one
93
+ when :last then return criteria.last
94
+ else
95
+ return criteria
96
+ end
73
97
  end
74
98
 
75
99
  # Adds a criterion to the +Criteria+ that specifies values where any can
@@ -117,7 +141,8 @@ module Mongoid #:nodoc:
117
141
  clone.tap do |crit|
118
142
  selector = case selector
119
143
  when String then {"$where" => selector}
120
- else selector ? selector.expand_complex_criteria : {}
144
+ else
145
+ BSON::ObjectId.convert(klass, selector || {}).expand_complex_criteria
121
146
  end
122
147
 
123
148
  selector.each_pair do |key, value|
@@ -13,7 +13,9 @@ module Mongoid #:nodoc:
13
13
  def inspect
14
14
  "#<Mongoid::Criteria\n" <<
15
15
  " selector: #{selector.inspect},\n" <<
16
- " options: #{options.inspect}>\n"
16
+ " options: #{options.inspect},\n" <<
17
+ " class: #{klass},\n" <<
18
+ " embedded: #{embedded}>\n"
17
19
  end
18
20
  end
19
21
  end
@@ -106,12 +106,12 @@ module Mongoid #:nodoc:
106
106
  ids.flatten!
107
107
  if ids.size > 1
108
108
  any_in(
109
- :_id => ::BSON::ObjectId.cast!(klass, ids, klass.primary_key.nil?)
109
+ :_id => ::BSON::ObjectId.convert(klass, ids)
110
110
  )
111
111
  else
112
112
  clone.tap do |crit|
113
113
  crit.selector[:_id] =
114
- ::BSON::ObjectId.cast!(klass, ids.first, klass.primary_key.nil?)
114
+ ::BSON::ObjectId.convert(klass, ids.first)
115
115
  end
116
116
  end
117
117
  end
@@ -102,6 +102,7 @@ module Mongoid #:nodoc:
102
102
  #
103
103
  # <tt>person.move_changes</tt>
104
104
  def move_changes
105
+ @validated = false
105
106
  @previous_modifications = modifications.dup
106
107
  @modifications = {}
107
108
  end
@@ -117,8 +117,8 @@ module Mongoid #:nodoc:
117
117
  process(attrs) do |document|
118
118
  yield self if block_given?
119
119
  identify
120
- run_callbacks(:initialize) { document }
121
120
  end
121
+ run_callbacks(:initialize) { self }
122
122
  end
123
123
 
124
124
  # Return the attributes hash.
@@ -147,9 +147,10 @@ module Mongoid #:nodoc:
147
147
  raise Errors::DocumentNotFound.new(self.class, id) if reloaded.nil?
148
148
  end
149
149
  @attributes = {}.merge(reloaded || {})
150
+ reset_modifications
150
151
  tap do
151
152
  relations.keys.each do |name|
152
- if relation_exists?(name)
153
+ if instance_variable_defined?("@#{name}")
153
154
  remove_instance_variable("@#{name}")
154
155
  end
155
156
  end
@@ -189,15 +190,15 @@ module Mongoid #:nodoc:
189
190
  # the current document.
190
191
  #
191
192
  # @example Get the full hierarchy.
192
- # person.to_hash
193
+ # person.as_document
193
194
  #
194
195
  # @return [ Hash ] A hash of all attributes in the hierarchy.
195
- def to_hash
196
+ def as_document
196
197
  attributes = @attributes
197
198
  attributes.tap do |attrs|
198
199
  relations.select { |name, meta| meta.embedded? }.each do |name, meta|
199
200
  relation = send(name, false, :continue => false)
200
- attrs[name] = relation.to_hash unless relation.blank?
201
+ attrs[name] = relation.as_document unless relation.blank?
201
202
  end
202
203
  end
203
204
  end
@@ -248,6 +249,11 @@ module Mongoid #:nodoc:
248
249
  def _types
249
250
  @_type ||= [descendants + [self]].flatten.uniq.map(&:to_s)
250
251
  end
252
+
253
+ # Set the i18n scope to overwrite ActiveModel.
254
+ def i18n_scope
255
+ :mongoid
256
+ end
251
257
  end
252
258
  end
253
259
  end
@@ -6,8 +6,8 @@ module Mongoid #:nodoc:
6
6
  def set(value)
7
7
  return nil if value.blank?
8
8
  begin
9
- Integer(value)
10
- rescue ArgumentError => e
9
+ value.to_s =~ /\./ ? Float(value) : Integer(value)
10
+ rescue
11
11
  value
12
12
  end
13
13
  end
@@ -2,8 +2,21 @@
2
2
  module Mongoid #:nodoc:
3
3
  module Extensions #:nodoc:
4
4
  module ObjectId #:nodoc:
5
- module Conversions #:nodoc:
6
5
 
6
+ # Provides conversions to and from BSON::ObjectIds and Strings, Arrays,
7
+ # and Hashes.
8
+ module Conversions
9
+
10
+ # Set the BSON::ObjectId value.
11
+ #
12
+ # @example Set the value.
13
+ # BSON::ObjectId.set("4c52c439931a90ab29000003")
14
+ #
15
+ # @param [ String, BSON::ObjectId ] value The value to set.
16
+ #
17
+ # @return [ BSON::ObjectId ] The set value.
18
+ #
19
+ # @since 1.0
7
20
  def set(value)
8
21
  if value.is_a?(::String)
9
22
  BSON::ObjectId.from_string(value) unless value.blank?
@@ -12,41 +25,58 @@ module Mongoid #:nodoc:
12
25
  end
13
26
  end
14
27
 
28
+ # Get the BSON::ObjectId value.
29
+ #
30
+ # @example Get the value.
31
+ # BSON::ObjectId.set(BSON::ObjectId.new)
32
+ #
33
+ # @param [ BSON::ObjectId ] value The value to get.
34
+ #
35
+ # @return [ BSON::ObjectId ] The value.
36
+ #
37
+ # @since 1.0
15
38
  def get(value)
16
39
  value
17
40
  end
18
41
 
19
- # If the document is using BSON::ObjectIds the convert the argument to
20
- # either an object id or an array of them if the supplied argument is an
21
- # Array. Otherwise just return.
22
- #
23
- # Options:
24
- # args: A +String+ or an +Array+ convert to +BSON::ObjectId+
25
- # cast: A +Boolean+ define if we can or not cast to BSON::ObjectId.
26
- # If false, we use the default type of args
27
- #
28
- # Example:
29
- #
30
- # <tt>Mongoid.cast_ids!("4ab2bc4b8ad548971900005c", true)</tt>
31
- # <tt>Mongoid.cast_ids!(["4ab2bc4b8ad548971900005c"])</tt>
32
- #
33
- # Returns:
34
- #
35
- # If using object ids:
36
- # An +Array+ of +BSON::ObjectId+ of each element if params is an +Array+
37
- # A +BSON::ObjectId+ from params if params is +String+
38
- # Otherwise:
39
- # <tt>args</tt>
40
- def cast!(klass, args, cast = true)
41
- if !klass.using_object_ids? || args.is_a?(::BSON::ObjectId) || !cast
42
- return args
43
- end
44
- if args.is_a?(::String)
45
- ::BSON::ObjectId(args)
46
- elsif args.is_a?(::Array)
47
- args.map{ |a|
48
- a.is_a?(::BSON::ObjectId) ? a : ::BSON::ObjectId(a)
49
- }
42
+ # Convert the supplied arguments to object ids based on the class
43
+ # settings.
44
+ #
45
+ # @example Convert a string to an object id
46
+ # BSON::ObjectId.convert(Person, "4c52c439931a90ab29000003")
47
+ #
48
+ # @example Convert an array of strings to object ids.
49
+ # BSON::ObjectId.convert(Person, [ "4c52c439931a90ab29000003" ])
50
+ #
51
+ # @example Convert a hash of id strings to object ids.
52
+ # BSON::ObjectId.convert(Person, { :_id => "4c52c439931a90ab29000003" })
53
+ #
54
+ # @param [ Class ] klass The class to convert the ids for.
55
+ # @param [ Object, Array, Hash ] args The object to convert.
56
+ #
57
+ # @raise BSON::InvalidObjectId If using object ids and passed bad
58
+ # strings.
59
+ #
60
+ # @return [ BSON::ObjectId, Array, Hash ] The converted object ids.
61
+ #
62
+ # @since 2.0.0.rc.7
63
+ def convert(klass, args)
64
+ return args if args.is_a?(BSON::ObjectId) || !klass.using_object_ids?
65
+ case args
66
+ when ::String
67
+ BSON::ObjectId.from_string(args)
68
+ when ::Array
69
+ args.map do |arg|
70
+ convert(klass, arg)
71
+ end
72
+ when ::Hash
73
+ args.tap do |hash|
74
+ args.each_pair do |key, value|
75
+ begin
76
+ args[key] = convert(klass, value)
77
+ rescue BSON::InvalidObjectId; end
78
+ end
79
+ end
50
80
  else
51
81
  args
52
82
  end