mongoid 2.0.0.beta.20 → 2.0.0.rc.1

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 (134) hide show
  1. data/README.rdoc +8 -0
  2. data/Rakefile +51 -0
  3. data/lib/config/locales/nl.yml +39 -0
  4. data/lib/config/locales/ro.yml +1 -1
  5. data/lib/mongoid.rb +17 -17
  6. data/lib/mongoid/atomicity.rb +54 -22
  7. data/lib/mongoid/attributes.rb +145 -125
  8. data/lib/mongoid/callbacks.rb +7 -2
  9. data/lib/mongoid/collection.rb +49 -32
  10. data/lib/mongoid/collections.rb +0 -1
  11. data/lib/mongoid/components.rb +34 -29
  12. data/lib/mongoid/config.rb +207 -193
  13. data/lib/mongoid/config/database.rb +167 -0
  14. data/lib/mongoid/contexts.rb +2 -5
  15. data/lib/mongoid/contexts/enumerable.rb +30 -4
  16. data/lib/mongoid/contexts/ids.rb +2 -2
  17. data/lib/mongoid/contexts/mongo.rb +30 -5
  18. data/lib/mongoid/copyable.rb +44 -0
  19. data/lib/mongoid/criteria.rb +110 -56
  20. data/lib/mongoid/criterion/creational.rb +34 -0
  21. data/lib/mongoid/criterion/destructive.rb +37 -0
  22. data/lib/mongoid/criterion/exclusion.rb +3 -1
  23. data/lib/mongoid/criterion/inclusion.rb +59 -64
  24. data/lib/mongoid/criterion/inspection.rb +22 -0
  25. data/lib/mongoid/criterion/optional.rb +42 -54
  26. data/lib/mongoid/criterion/selector.rb +9 -0
  27. data/lib/mongoid/default_scope.rb +28 -0
  28. data/lib/mongoid/deprecation.rb +5 -5
  29. data/lib/mongoid/dirty.rb +4 -5
  30. data/lib/mongoid/document.rb +161 -114
  31. data/lib/mongoid/extensions.rb +7 -11
  32. data/lib/mongoid/extensions/array/parentization.rb +2 -2
  33. data/lib/mongoid/extensions/date/conversions.rb +1 -1
  34. data/lib/mongoid/extensions/hash/conversions.rb +0 -23
  35. data/lib/mongoid/extensions/nil/collectionization.rb +12 -0
  36. data/lib/mongoid/extensions/object/reflections.rb +17 -0
  37. data/lib/mongoid/extensions/object/yoda.rb +27 -0
  38. data/lib/mongoid/extensions/string/conversions.rb +23 -4
  39. data/lib/mongoid/extensions/time_conversions.rb +4 -4
  40. data/lib/mongoid/field.rb +30 -19
  41. data/lib/mongoid/fields.rb +15 -5
  42. data/lib/mongoid/finders.rb +19 -11
  43. data/lib/mongoid/hierarchy.rb +34 -28
  44. data/lib/mongoid/identity.rb +62 -20
  45. data/lib/mongoid/inspection.rb +58 -0
  46. data/lib/mongoid/matchers.rb +20 -0
  47. data/lib/mongoid/multi_database.rb +11 -0
  48. data/lib/mongoid/nested_attributes.rb +41 -0
  49. data/lib/mongoid/paranoia.rb +3 -4
  50. data/lib/mongoid/paths.rb +1 -1
  51. data/lib/mongoid/persistence.rb +89 -90
  52. data/lib/mongoid/persistence/command.rb +20 -4
  53. data/lib/mongoid/persistence/insert.rb +13 -11
  54. data/lib/mongoid/persistence/insert_embedded.rb +8 -6
  55. data/lib/mongoid/persistence/remove.rb +6 -4
  56. data/lib/mongoid/persistence/remove_all.rb +6 -4
  57. data/lib/mongoid/persistence/remove_embedded.rb +8 -6
  58. data/lib/mongoid/persistence/update.rb +12 -10
  59. data/lib/mongoid/railtie.rb +2 -2
  60. data/lib/mongoid/railties/database.rake +10 -9
  61. data/lib/mongoid/relations.rb +104 -0
  62. data/lib/mongoid/relations/accessors.rb +154 -0
  63. data/lib/mongoid/relations/auto_save.rb +34 -0
  64. data/lib/mongoid/relations/binding.rb +24 -0
  65. data/lib/mongoid/relations/bindings.rb +9 -0
  66. data/lib/mongoid/relations/bindings/embedded/in.rb +77 -0
  67. data/lib/mongoid/relations/bindings/embedded/many.rb +93 -0
  68. data/lib/mongoid/relations/bindings/embedded/one.rb +65 -0
  69. data/lib/mongoid/relations/bindings/referenced/in.rb +78 -0
  70. data/lib/mongoid/relations/bindings/referenced/many.rb +93 -0
  71. data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +94 -0
  72. data/lib/mongoid/relations/bindings/referenced/one.rb +63 -0
  73. data/lib/mongoid/relations/builder.rb +41 -0
  74. data/lib/mongoid/relations/builders.rb +79 -0
  75. data/lib/mongoid/relations/builders/embedded/in.rb +25 -0
  76. data/lib/mongoid/relations/builders/embedded/many.rb +32 -0
  77. data/lib/mongoid/relations/builders/embedded/one.rb +26 -0
  78. data/lib/mongoid/relations/builders/nested_attributes/many.rb +116 -0
  79. data/lib/mongoid/relations/builders/nested_attributes/one.rb +135 -0
  80. data/lib/mongoid/relations/builders/referenced/in.rb +32 -0
  81. data/lib/mongoid/relations/builders/referenced/many.rb +26 -0
  82. data/lib/mongoid/relations/builders/referenced/many_to_many.rb +29 -0
  83. data/lib/mongoid/relations/builders/referenced/one.rb +30 -0
  84. data/lib/mongoid/relations/cascading.rb +55 -0
  85. data/lib/mongoid/relations/cascading/delete.rb +19 -0
  86. data/lib/mongoid/relations/cascading/destroy.rb +19 -0
  87. data/lib/mongoid/relations/cascading/nullify.rb +18 -0
  88. data/lib/mongoid/relations/cascading/strategy.rb +26 -0
  89. data/lib/mongoid/relations/cyclic.rb +97 -0
  90. data/lib/mongoid/relations/embedded/in.rb +172 -0
  91. data/lib/mongoid/relations/embedded/many.rb +450 -0
  92. data/lib/mongoid/relations/embedded/one.rb +169 -0
  93. data/lib/mongoid/relations/macros.rb +302 -0
  94. data/lib/mongoid/relations/many.rb +185 -0
  95. data/lib/mongoid/relations/metadata.rb +529 -0
  96. data/lib/mongoid/relations/nested_builder.rb +52 -0
  97. data/lib/mongoid/relations/one.rb +29 -0
  98. data/lib/mongoid/relations/polymorphic.rb +54 -0
  99. data/lib/mongoid/relations/proxy.rb +122 -0
  100. data/lib/mongoid/relations/referenced/in.rb +214 -0
  101. data/lib/mongoid/relations/referenced/many.rb +358 -0
  102. data/lib/mongoid/relations/referenced/many_to_many.rb +379 -0
  103. data/lib/mongoid/relations/referenced/one.rb +204 -0
  104. data/lib/mongoid/relations/reflections.rb +45 -0
  105. data/lib/mongoid/safe.rb +11 -1
  106. data/lib/mongoid/safety.rb +122 -97
  107. data/lib/mongoid/scope.rb +14 -9
  108. data/lib/mongoid/state.rb +37 -3
  109. data/lib/mongoid/timestamps.rb +11 -0
  110. data/lib/mongoid/validations.rb +42 -3
  111. data/lib/mongoid/validations/associated.rb +8 -5
  112. data/lib/mongoid/validations/uniqueness.rb +23 -2
  113. data/lib/mongoid/version.rb +1 -1
  114. data/lib/mongoid/versioning.rb +25 -16
  115. data/lib/rails/generators/mongoid/model/templates/model.rb +3 -1
  116. metadata +95 -80
  117. data/lib/mongoid/associations.rb +0 -364
  118. data/lib/mongoid/associations/embedded_in.rb +0 -74
  119. data/lib/mongoid/associations/embeds_many.rb +0 -299
  120. data/lib/mongoid/associations/embeds_one.rb +0 -111
  121. data/lib/mongoid/associations/foreign_key.rb +0 -35
  122. data/lib/mongoid/associations/meta_data.rb +0 -38
  123. data/lib/mongoid/associations/options.rb +0 -78
  124. data/lib/mongoid/associations/proxy.rb +0 -60
  125. data/lib/mongoid/associations/referenced_in.rb +0 -70
  126. data/lib/mongoid/associations/references_many.rb +0 -254
  127. data/lib/mongoid/associations/references_many_as_array.rb +0 -128
  128. data/lib/mongoid/associations/references_one.rb +0 -104
  129. data/lib/mongoid/extensions/array/accessors.rb +0 -17
  130. data/lib/mongoid/extensions/array/assimilation.rb +0 -26
  131. data/lib/mongoid/extensions/hash/accessors.rb +0 -42
  132. data/lib/mongoid/extensions/hash/assimilation.rb +0 -40
  133. data/lib/mongoid/extensions/nil/assimilation.rb +0 -17
  134. data/lib/mongoid/memoization.rb +0 -33
@@ -0,0 +1,167 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Config #:nodoc:
4
+
5
+ # This class handles the configuration and initialization of a mongodb
6
+ # database from options.
7
+ class Database < Hash
8
+
9
+ # Configure the database connections. This will return an array
10
+ # containing the master and an array of slaves.
11
+ #
12
+ # @example Configure the connection.
13
+ # db.configure
14
+ #
15
+ # @return [ Array<Mongo::DB, Array<Mongo:DB>> ] The Mongo databases.
16
+ #
17
+ # @since 2.0.0.rc.1
18
+ def configure
19
+ [ master.db(name), slaves.map { |slave| slave.db(name) } ]
20
+ end
21
+
22
+ # Create the new db configuration class.
23
+ #
24
+ # @example Initialize the class.
25
+ # Config::Database.new(
26
+ # false, "uri" => { "mongodb://durran:password@localhost:27017/mongoid" }
27
+ # )
28
+ #
29
+ # @param [ Hash ] options The configuration options.
30
+ #
31
+ # @option options [ String ] :database The database name.
32
+ # @option options [ String ] :host The database host.
33
+ # @option options [ String ] :password The password for authentication.
34
+ # @option options [ Integer ] :port The port for the database.
35
+ # @option options [ String ] :uri The uri for the database.
36
+ # @option options [ String ] :username The user for authentication.
37
+ #
38
+ # @since 2.0.0.rc.1
39
+ def initialize(options = {})
40
+ merge!(options)
41
+ end
42
+
43
+ private
44
+
45
+ # Do we need to authenticate against the database?
46
+ #
47
+ # @example Are we authenticating?
48
+ # db.authenticating?
49
+ #
50
+ # @return [ true, false ] True if auth is needed, false if not.
51
+ #
52
+ # @since 2.0.0.rc.1
53
+ def authenticating?
54
+ username || password
55
+ end
56
+
57
+ # Takes the supplied options in the hash and created a URI from them to
58
+ # pass to the Mongo connection object.
59
+ #
60
+ # @example Build the URI.
61
+ # db.build_uri
62
+ #
63
+ # @param [ Hash ] options The options to build with.
64
+ #
65
+ # @return [ String ] A mongo compliant URI string.
66
+ #
67
+ # @since 2.0.0.rc.1
68
+ def build_uri(options = {})
69
+ "mongodb://".tap do |base|
70
+ base << "#{username}:#{password}@" if authenticating?
71
+ base << "#{options["host"] || "localhost"}:#{options["port"] || 27017}"
72
+ base << "/#{self["database"]}" if authenticating?
73
+ end
74
+ end
75
+
76
+ # Create the mongo master connection from either the supplied URI
77
+ # or a generated one, while setting pool size and logging.
78
+ #
79
+ # @example Create the connection.
80
+ # db.connection
81
+ #
82
+ # @return [ Mongo::Connection ] The mongo connection.
83
+ #
84
+ # @since 2.0.0.rc.1
85
+ def master
86
+ Mongo::Connection.from_uri(uri(self), optional).tap do |conn|
87
+ conn.apply_saved_authentication
88
+ end
89
+ end
90
+
91
+ # Create the mongo slave connections from either the supplied URI
92
+ # or a generated one, while setting pool size and logging.
93
+ #
94
+ # @example Create the connection.
95
+ # db.connection
96
+ #
97
+ # @return [ Array<Mongo::Connection> ] The mongo slave connections.
98
+ #
99
+ # @since 2.0.0.rc.1
100
+ def slaves
101
+ (self["slaves"] || []).map do |options|
102
+ Mongo::Connection.from_uri(uri(options), optional(true)).tap do |conn|
103
+ conn.apply_saved_authentication
104
+ end
105
+ end
106
+ end
107
+
108
+ # Convenience for accessing the hash via dot notation.
109
+ #
110
+ # @example Access a value in alternate syntax.
111
+ # db.host
112
+ #
113
+ # @return [ Object ] The value in the hash.
114
+ #
115
+ # @since 2.0.0.rc.1
116
+ def method_missing(name, *args, &block)
117
+ self[name.to_s]
118
+ end
119
+
120
+ # Get the name of the database, from either the URI supplied or the
121
+ # database value in the options.
122
+ #
123
+ # @example Get the database name.
124
+ # db.name
125
+ #
126
+ # @return [ String ] The database name.
127
+ #
128
+ # @since 2.0.0.rc.1
129
+ def name
130
+ db_name = URI.parse(uri(self)).path.to_s.sub("/", "")
131
+ db_name.blank? ? database : db_name
132
+ end
133
+
134
+ # Get the options used in creating the database connection.
135
+ #
136
+ # @example Get the options.
137
+ # db.options
138
+ #
139
+ # @param [ true, false ] slave Are the options for a slave db?
140
+ #
141
+ # @return [ Hash ] The hash of configuration options.
142
+ #
143
+ # @since 2.0.0.rc.1
144
+ def optional(slave = false)
145
+ {
146
+ :pool_size => pool_size,
147
+ :logger => Mongoid::Logger.new,
148
+ :slave_ok => slave
149
+ }
150
+ end
151
+
152
+ # Get a Mongo compliant URI for the database connection.
153
+ #
154
+ # @example Get the URI.
155
+ # db.uri
156
+ #
157
+ # @param [ Hash ] options The options hash.
158
+ #
159
+ # @return [ String ] The URI for the connection.
160
+ #
161
+ # @since 2.0.0.rc.1
162
+ def uri(options = {})
163
+ options["uri"] || build_uri(options)
164
+ end
165
+ end
166
+ end
167
+ end
@@ -14,11 +14,8 @@ module Mongoid
14
14
  # Example:
15
15
  #
16
16
  # <tt>Contexts.context_for(criteria)</tt>
17
- def self.context_for(criteria)
18
- if criteria.klass.embedded?
19
- return Contexts::Enumerable.new(criteria)
20
- end
21
- Contexts::Mongo.new(criteria)
17
+ def self.context_for(criteria, embedded = false)
18
+ embedded ? Enumerable.new(criteria) : Mongo.new(criteria)
22
19
  end
23
20
  end
24
21
  end
@@ -6,7 +6,7 @@ module Mongoid #:nodoc:
6
6
  module Contexts #:nodoc:
7
7
  class Enumerable
8
8
  include Ids, Paging
9
- attr_reader :criteria
9
+ attr_accessor :criteria
10
10
 
11
11
  delegate :blank?, :empty?, :first, :last, :to => :execute
12
12
  delegate :klass, :documents, :options, :selector, :to => :criteria
@@ -42,6 +42,32 @@ module Mongoid #:nodoc:
42
42
  @count ||= filter.size
43
43
  end
44
44
 
45
+ # Delete all the documents in the database matching the selector.
46
+ #
47
+ # @example Delete the documents.
48
+ # context.delete_all
49
+ #
50
+ # @return [ Integer ] The number of documents deleted.
51
+ #
52
+ # @since 2.0.0.rc.1
53
+ def delete_all
54
+ count.tap { filter.each(&:delete) }
55
+ end
56
+ alias :delete :delete_all
57
+
58
+ # Destroy all the documents in the database matching the selector.
59
+ #
60
+ # @example Destroy the documents.
61
+ # context.destroy_all
62
+ #
63
+ # @return [ Integer ] The number of documents destroyed.
64
+ #
65
+ # @since 2.0.0.rc.1
66
+ def destroy_all
67
+ count.tap { filter.each(&:destroy) }
68
+ end
69
+ alias :destroy :destroy_all
70
+
45
71
  # Gets an array of distinct values for the supplied field across the
46
72
  # entire array or the susbset given the criteria.
47
73
  #
@@ -125,9 +151,9 @@ module Mongoid #:nodoc:
125
151
  #
126
152
  # The first document in the +Array+
127
153
  def shift
128
- document = first
129
- criteria.skip((options[:skip] || 0) + 1)
130
- document
154
+ first.tap do |document|
155
+ self.criteria = criteria.skip((options[:skip] || 0) + 1)
156
+ end
131
157
  end
132
158
 
133
159
  # Get the sum of the field values for all the documents.
@@ -13,9 +13,9 @@ module Mongoid #:nodoc:
13
13
  #
14
14
  # The single or multiple documents.
15
15
  def id_criteria(params)
16
- criteria.id(params)
16
+ self.criteria = criteria.id(params)
17
17
  result = params.is_a?(Array) ? criteria.entries : one
18
- if Mongoid.raise_not_found_error
18
+ if Mongoid.raise_not_found_error && !params.blank?
19
19
  raise Errors::DocumentNotFound.new(klass, params) if result.blank?
20
20
  end
21
21
  return result
@@ -3,7 +3,7 @@ module Mongoid #:nodoc:
3
3
  module Contexts #:nodoc:
4
4
  class Mongo
5
5
  include Ids, Paging
6
- attr_reader :criteria
6
+ attr_accessor :criteria
7
7
 
8
8
  delegate :klass, :options, :selector, :to => :criteria
9
9
 
@@ -51,7 +51,6 @@ module Mongoid #:nodoc:
51
51
  def blank?
52
52
  klass.collection.find_one(selector, { :fields => [ :_id ] }).nil?
53
53
  end
54
-
55
54
  alias :empty? :blank?
56
55
 
57
56
  # Get the count of matching documents in the database for the context.
@@ -67,6 +66,32 @@ module Mongoid #:nodoc:
67
66
  @count ||= klass.collection.find(selector, process_options).count
68
67
  end
69
68
 
69
+ # Delete all the documents in the database matching the selector.
70
+ #
71
+ # @example Delete the documents.
72
+ # context.delete_all
73
+ #
74
+ # @return [ Integer ] The number of documents deleted.
75
+ #
76
+ # @since 2.0.0.rc.1
77
+ def delete_all
78
+ klass.delete_all(:conditions => selector)
79
+ end
80
+ alias :delete :delete_all
81
+
82
+ # Destroy all the documents in the database matching the selector.
83
+ #
84
+ # @example Destroy the documents.
85
+ # context.destroy_all
86
+ #
87
+ # @return [ Integer ] The number of documents destroyed.
88
+ #
89
+ # @since 2.0.0.rc.1
90
+ def destroy_all
91
+ klass.destroy_all(:conditions => selector)
92
+ end
93
+ alias :destroy :destroy_all
94
+
70
95
  # Gets an array of distinct values for the supplied field across the
71
96
  # entire collection or the susbset given the criteria.
72
97
  #
@@ -134,10 +159,10 @@ module Mongoid #:nodoc:
134
159
  def initialize(criteria)
135
160
  @criteria = criteria
136
161
  if klass.hereditary? && !criteria.selector.keys.include?(:_type)
137
- criteria.in(:_type => criteria.klass._types)
162
+ @criteria = criteria.in(:_type => criteria.klass._types)
138
163
  end
139
- criteria.enslave if klass.enslaved?
140
- criteria.cache if klass.cached?
164
+ @criteria.enslave if klass.enslaved?
165
+ @criteria.cache if klass.cached?
141
166
  end
142
167
 
143
168
  # Iterate over each +Document+ in the results. This can take an optional
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Copyable
4
+ extend ActiveSupport::Concern
5
+
6
+ COPYABLES = [
7
+ :@accessed,
8
+ :@attributes,
9
+ :@metadata,
10
+ :@modifications,
11
+ :@previous_modifications
12
+ ]
13
+
14
+ protected
15
+
16
+ # Clone or dup the current +Document+. This will return all attributes with
17
+ # the exception of the document's id and versions, and will reset all the
18
+ # instance variables.
19
+ #
20
+ # Example:
21
+ #
22
+ # <tt>document.clone</tt>
23
+ # <tt>document.dup</tt>
24
+ #
25
+ # Options:
26
+ #
27
+ # other: The document getting cloned.
28
+ #
29
+ # Returns:
30
+ #
31
+ # A new document with all the attributes except id and versions
32
+ def initialize_copy(other)
33
+ instance_variables.each { |name| remove_instance_variable(name) }
34
+ COPYABLES.each do |name|
35
+ value = other.instance_variable_get(name)
36
+ instance_variable_set(name, value ? value.dup : nil)
37
+ end
38
+ @attributes.delete("_id")
39
+ @attributes.delete("versions")
40
+ @new_record = true
41
+ identify
42
+ end
43
+ end
44
+ end
@@ -1,11 +1,15 @@
1
1
  # encoding: utf-8
2
+ require "mongoid/criterion/creational"
2
3
  require "mongoid/criterion/complex"
4
+ require "mongoid/criterion/destructive"
3
5
  require "mongoid/criterion/exclusion"
4
6
  require "mongoid/criterion/inclusion"
7
+ require "mongoid/criterion/inspection"
5
8
  require "mongoid/criterion/optional"
6
9
  require "mongoid/criterion/selector"
7
10
 
8
11
  module Mongoid #:nodoc:
12
+
9
13
  # The +Criteria+ class is the core object needed in Mongoid to retrieve
10
14
  # objects from the database. It is a DSL that essentially sets up the
11
15
  # selector and options arguments that get passed on to a <tt>Mongo::Collection</tt>
@@ -21,13 +25,15 @@ module Mongoid #:nodoc:
21
25
  #
22
26
  # <tt>criteria.execute</tt>
23
27
  class Criteria
28
+ include Enumerable
29
+ include Criterion::Creational
30
+ include Criterion::Destructive
24
31
  include Criterion::Exclusion
25
32
  include Criterion::Inclusion
33
+ include Criterion::Inspection
26
34
  include Criterion::Optional
27
- include Enumerable
28
35
 
29
- attr_reader :collection, :ids, :klass, :options, :selector
30
- attr_accessor :documents
36
+ attr_accessor :collection, :documents, :embedded, :ids, :klass, :options, :selector
31
37
 
32
38
  delegate :aggregate, :avg, :blank?, :count, :distinct, :empty?,
33
39
  :execute, :first, :group, :id_criteria, :last, :max,
@@ -69,7 +75,7 @@ module Mongoid #:nodoc:
69
75
  # This will return an Enumerable context if the class is embedded,
70
76
  # otherwise it will return a Mongo context for root classes.
71
77
  def context
72
- @context ||= Contexts.context_for(self)
78
+ @context ||= Contexts.context_for(self, embedded)
73
79
  end
74
80
 
75
81
  # Iterate over each +Document+ in the results. This can take an optional
@@ -79,8 +85,7 @@ module Mongoid #:nodoc:
79
85
  #
80
86
  # <tt>criteria.each { |doc| p doc }</tt>
81
87
  def each(&block)
82
- context.iterate(&block)
83
- self
88
+ tap { context.iterate(&block) }
84
89
  end
85
90
 
86
91
  # Return true if the criteria has some Document or not
@@ -116,9 +121,9 @@ module Mongoid #:nodoc:
116
121
  #
117
122
  # type: One of :all, :first:, or :last
118
123
  # klass: The class to execute on.
119
- def initialize(klass)
120
- @selector = Mongoid::Criterion::Selector.new(klass)
121
- @options, @klass, @documents = {}, klass, []
124
+ def initialize(klass, embedded = false)
125
+ @selector = Criterion::Selector.new(klass)
126
+ @options, @klass, @documents, @embedded = {}, klass, [], embedded
122
127
  end
123
128
 
124
129
  # Merges another object into this +Criteria+. The other object may be a
@@ -133,9 +138,11 @@ module Mongoid #:nodoc:
133
138
  #
134
139
  # <tt>criteria.merge({ :conditions => { :title => "Sir" } })</tt>
135
140
  def merge(other)
136
- @selector.update(other.selector)
137
- @options.update(other.options)
138
- @documents = other.documents
141
+ clone.tap do |crit|
142
+ crit.selector.update(other.selector)
143
+ crit.options.update(other.options)
144
+ crit.documents = other.documents
145
+ end
139
146
  end
140
147
 
141
148
  # Used for chaining +Criteria+ scopes together in the for of class methods
@@ -157,8 +164,6 @@ module Mongoid #:nodoc:
157
164
  end
158
165
  end
159
166
 
160
- alias :to_ary :to_a
161
-
162
167
  # Returns the selector and options as a +Hash+ that would be passed to a
163
168
  # scope for use with named scopes.
164
169
  def scoped
@@ -167,39 +172,74 @@ module Mongoid #:nodoc:
167
172
  scope_options[:order_by] = sorting if sorting
168
173
  { :where => @selector }.merge(scope_options)
169
174
  end
175
+ alias :to_ary :to_a
170
176
 
171
- # Translate the supplied arguments into a +Criteria+ object.
172
- #
173
- # If the passed in args is a single +String+, then it will
174
- # construct an id +Criteria+ from it.
175
- #
176
- # If the passed in args are a type and a hash, then it will construct
177
- # the +Criteria+ with the proper selector, options, and type.
178
- #
179
- # Options:
180
- #
181
- # args: either a +String+ or a +Symbol+, +Hash combination.
182
- #
183
- # Example:
184
- #
185
- # <tt>Criteria.translate(Person, "4ab2bc4b8ad548971900005c")</tt>
186
- # <tt>Criteria.translate(Person, :conditions => { :field => "value"}, :limit => 20)</tt>
187
- def self.translate(*args)
188
- klass = args[0]
189
- params = args[1] || {}
190
- unless params.is_a?(Hash)
191
- return klass.criteria.id_criteria(params)
177
+ class << self
178
+
179
+ # Encaspulates the behavior of taking arguments and parsing them into a
180
+ # finder type and a corresponding criteria object.
181
+ #
182
+ # Example:
183
+ #
184
+ # <tt>Criteria.parse!(Person, :all, :conditions => {})</tt>
185
+ #
186
+ # Options:
187
+ #
188
+ # klass: The klass to create the criteria for.
189
+ # args: An assortment of finder options.
190
+ #
191
+ # Returns:
192
+ #
193
+ # An Array with the type and criteria.
194
+ def parse!(klass, embedded, *args)
195
+ if args[0].nil?
196
+ Errors::InvalidOptions.new("Calling Document#find with nil is invalid")
197
+ end
198
+ type = args.delete_at(0) if args[0].is_a?(Symbol)
199
+ criteria = translate(klass, embedded, *args)
200
+ return [ type, criteria ]
192
201
  end
193
- conditions = params.delete(:conditions) || {}
194
- if conditions.include?(:id)
195
- conditions[:_id] = conditions[:id]
196
- conditions.delete(:id)
202
+
203
+ # Translate the supplied arguments into a +Criteria+ object.
204
+ #
205
+ # If the passed in args is a single +String+, then it will
206
+ # construct an id +Criteria+ from it.
207
+ #
208
+ # If the passed in args are a type and a hash, then it will construct
209
+ # the +Criteria+ with the proper selector, options, and type.
210
+ #
211
+ # Options:
212
+ #
213
+ # args: either a +String+ or a +Symbol+, +Hash combination.
214
+ #
215
+ # Example:
216
+ #
217
+ # <tt>Criteria.translate(Person, "4ab2bc4b8ad548971900005c")</tt>
218
+ # <tt>Criteria.translate(Person, :conditions => { :field => "value"}, :limit => 20)</tt>
219
+ def translate(*args)
220
+ klass = args[0]
221
+ embedded = args[1]
222
+ params = args[2] || {}
223
+ unless params.is_a?(Hash)
224
+ return klass.criteria(embedded).id_criteria(params)
225
+ end
226
+ conditions = params.delete(:conditions) || {}
227
+ if conditions.include?(:id)
228
+ conditions[:_id] = conditions[:id]
229
+ conditions.delete(:id)
230
+ end
231
+ return klass.criteria(embedded).where(conditions).extras(params)
197
232
  end
198
- return klass.criteria.where(conditions).extras(params)
199
233
  end
200
234
 
201
235
  protected
202
236
 
237
+ # Return the entries of the other criteria or the object. Used for
238
+ # comparing criteria or an enumerable.
239
+ def comparable(other)
240
+ other.is_a?(Criteria) ? other.entries : other
241
+ end
242
+
203
243
  # Filters the unused options out of the options +Hash+. Currently this
204
244
  # takes into account the "page" and "per_page" options that would be passed
205
245
  # in if using will_paginate.
@@ -218,10 +258,24 @@ module Mongoid #:nodoc:
218
258
  end
219
259
  end
220
260
 
221
- # Return the entries of the other criteria or the object. Used for
222
- # comparing criteria or an enumerable.
223
- def comparable(other)
224
- other.is_a?(Criteria) ? other.entries : other
261
+ # Clone or dup the current +Criteria+. This will return a new criteria with
262
+ # the selector, options, klass, embedded options, etc intact.
263
+ #
264
+ # Example:
265
+ #
266
+ # <tt>criteria.clone</tt>
267
+ # <tt>criteria.dup</tt>
268
+ #
269
+ # Options:
270
+ #
271
+ # other: The criteria getting cloned.
272
+ #
273
+ # Returns:
274
+ #
275
+ # A new identical criteria
276
+ def initialize_copy(other)
277
+ @selector = other.selector.dup
278
+ @options = other.options.dup
225
279
  end
226
280
 
227
281
  # Update the selector setting the operator on the value for each key in the
@@ -231,20 +285,20 @@ module Mongoid #:nodoc:
231
285
  #
232
286
  # <tt>criteria.update_selector({ :field => "value" }, "$in")</tt>
233
287
  def update_selector(attributes, operator)
234
- attributes.each do |key, value|
235
- unless @selector[key]
236
- @selector[key] = { operator => value }
237
- else
238
- if @selector[key].has_key?(operator)
239
- # add the value to the current operator
240
- new_value = @selector[key].values.first + value
241
- @selector[key] = { operator => new_value }
288
+ clone.tap do |crit|
289
+ attributes.each do |key, value|
290
+ unless crit.selector[key]
291
+ crit.selector[key] = { operator => value }
242
292
  else
243
- # create a new operator on this key
244
- @selector[key][operator] = value
245
- end
293
+ if crit.selector[key].has_key?(operator)
294
+ new_value = crit.selector[key].values.first + value
295
+ crit.selector[key] = { operator => new_value }
296
+ else
297
+ crit.selector[key][operator] = value
298
+ end
299
+ end
246
300
  end
247
- end; self
301
+ end
248
302
  end
249
303
  end
250
304
  end