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

Sign up to get free protection for your applications and to get access to all the features.
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