mongoid 2.0.0.beta.9 → 2.0.0.beta.10

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 (37) hide show
  1. data/README.rdoc +2 -4
  2. data/lib/mongoid.rb +2 -1
  3. data/lib/mongoid/associations.rb +2 -3
  4. data/lib/mongoid/associations/embeds_many.rb +6 -2
  5. data/lib/mongoid/associations/embeds_one.rb +1 -3
  6. data/lib/mongoid/associations/options.rb +11 -10
  7. data/lib/mongoid/associations/references_many.rb +27 -4
  8. data/lib/mongoid/associations/references_many_as_array.rb +24 -4
  9. data/lib/mongoid/associations/references_one.rb +1 -2
  10. data/lib/mongoid/atomicity.rb +17 -4
  11. data/lib/mongoid/config.rb +97 -20
  12. data/lib/mongoid/contexts/enumerable.rb +9 -4
  13. data/lib/mongoid/contexts/mongo.rb +1 -1
  14. data/lib/mongoid/criteria.rb +7 -1
  15. data/lib/mongoid/criterion/inclusion.rb +12 -5
  16. data/lib/mongoid/criterion/optional.rb +26 -3
  17. data/lib/mongoid/cursor.rb +0 -1
  18. data/lib/mongoid/deprecation.rb +1 -2
  19. data/lib/mongoid/document.rb +25 -3
  20. data/lib/mongoid/extensions.rb +2 -1
  21. data/lib/mongoid/extensions/float/conversions.rb +1 -1
  22. data/lib/mongoid/extensions/integer/conversions.rb +1 -1
  23. data/lib/mongoid/extensions/symbol/conversions.rb +21 -0
  24. data/lib/mongoid/finders.rb +3 -2
  25. data/lib/mongoid/indexes.rb +17 -6
  26. data/lib/mongoid/logger.rb +19 -0
  27. data/lib/mongoid/paranoia.rb +12 -14
  28. data/lib/mongoid/persistence/remove_embedded.rb +0 -4
  29. data/lib/mongoid/persistence/update.rb +3 -0
  30. data/lib/mongoid/railtie.rb +37 -4
  31. data/lib/mongoid/railties/database.rake +19 -1
  32. data/lib/mongoid/validations/associated.rb +1 -1
  33. data/lib/mongoid/validations/locale/en.yml +4 -3
  34. data/lib/mongoid/validations/uniqueness.rb +19 -8
  35. data/lib/mongoid/version.rb +1 -1
  36. metadata +50 -18
  37. data/lib/mongoid/extensions/array/aliasing.rb +0 -4
@@ -36,7 +36,7 @@ module Mongoid #:nodoc:
36
36
 
37
37
  # Gets the number of documents in the array. Delegates to size.
38
38
  def count
39
- @count ||= documents.size
39
+ @count ||= filter.size
40
40
  end
41
41
 
42
42
  # Gets an array of distinct values for the supplied field across the
@@ -56,7 +56,7 @@ module Mongoid #:nodoc:
56
56
  #
57
57
  # An +Array+ of documents that matched the selector.
58
58
  def execute(paginating = false)
59
- limit(documents.select { |document| document.matches?(selector) })
59
+ limit(filter) || []
60
60
  end
61
61
 
62
62
  # Groups the documents by the first field supplied in the field options.
@@ -66,7 +66,7 @@ module Mongoid #:nodoc:
66
66
  # A +Hash+ with field values as keys, arrays of documents as values.
67
67
  def group
68
68
  field = options[:fields].first
69
- documents.group_by { |doc| doc.send(field) }
69
+ execute.group_by { |doc| doc.send(field) }
70
70
  end
71
71
 
72
72
  # Create the new enumerable context. This will need the selector and
@@ -121,13 +121,18 @@ module Mongoid #:nodoc:
121
121
  #
122
122
  # The numerical sum of all the document field values.
123
123
  def sum(field)
124
- sum = documents.inject(nil) do |memo, doc|
124
+ sum = execute.inject(nil) do |memo, doc|
125
125
  value = doc.send(field)
126
126
  memo ? memo += value : value
127
127
  end
128
128
  end
129
129
 
130
130
  protected
131
+ # Filters the documents against the criteria's selector
132
+ def filter
133
+ documents.select { |document| document.matches?(selector) }
134
+ end
135
+
131
136
  # If the field exists, perform the comparison and set if true.
132
137
  def determine(field, operator)
133
138
  matching = documents.inject(nil) do |memo, doc|
@@ -133,7 +133,7 @@ module Mongoid #:nodoc:
133
133
  # <tt>Mongoid::Contexts::Mongo.new(criteria)</tt>
134
134
  def initialize(criteria)
135
135
  @criteria = criteria
136
- if klass.hereditary
136
+ if klass.hereditary && !criteria.selector.keys.include?(:_type)
137
137
  criteria.in(:_type => criteria.klass._types)
138
138
  end
139
139
  criteria.enslave if klass.enslaved?
@@ -63,6 +63,12 @@ module Mongoid #:nodoc:
63
63
  end
64
64
  end
65
65
 
66
+ # Returns true if the supplied +Object+ is an instance of +Criteria+ or
67
+ # +Scope+.
68
+ def self.===(other)
69
+ super || Scope === other
70
+ end
71
+
66
72
  # Return or create the context in which this criteria should be executed.
67
73
  #
68
74
  # This will return an Enumerable context if the class is embedded,
@@ -148,7 +154,7 @@ module Mongoid #:nodoc:
148
154
  def method_missing(name, *args)
149
155
  if @klass.respond_to?(name)
150
156
  new_scope = @klass.send(name, *args)
151
- new_scope.merge(self)
157
+ new_scope.merge(self) if Criteria === new_scope
152
158
  return new_scope
153
159
  else
154
160
  return entries.send(name, *args)
@@ -97,12 +97,19 @@ module Mongoid #:nodoc:
97
97
  #
98
98
  # Returns: <tt>self</tt>
99
99
  def where(selector = nil)
100
- case selector
101
- when String
102
- @selector.update("$where" => selector)
103
- else
104
- @selector.update(selector ? selector.expand_complex_criteria : {})
100
+ selector = case selector
101
+ when String then {"$where" => selector}
102
+ else selector ? selector.expand_complex_criteria : {}
105
103
  end
104
+
105
+ selector.each_pair do |key, value|
106
+ if @selector.has_key?(key) && @selector[key].respond_to?(:merge!)
107
+ @selector[key].merge!(value)
108
+ else
109
+ @selector[key] = value
110
+ end
111
+ end
112
+
106
113
  self
107
114
  end
108
115
  end
@@ -91,15 +91,21 @@ module Mongoid #:nodoc:
91
91
  #
92
92
  # Options:
93
93
  #
94
- # object_id: A +String+ representation of a <tt>BSON::ObjectID</tt>
94
+ # object_id: A single id or an array of ids in +String+ or <tt>BSON::ObjectID</tt> format
95
95
  #
96
96
  # Example:
97
97
  #
98
98
  # <tt>criteria.id("4ab2bc4b8ad548971900005c")</tt>
99
+ # <tt>criteria.id(["4ab2bc4b8ad548971900005c", "4c454e7ebf4b98032d000001"])</tt>
99
100
  #
100
101
  # Returns: <tt>self</tt>
101
- def id(*args)
102
- (args.flatten.size > 1) ? self.in(:_id => args.flatten) : (@selector[:_id] = args.first)
102
+ def id(*ids)
103
+ ids.flatten!
104
+ if ids.size > 1
105
+ self.in(:_id => Mongoid.convert_to_object_id(ids, self.klass.primary_key.nil?))
106
+ else
107
+ @selector[:_id] = Mongoid.convert_to_object_id(ids.first, self.klass.primary_key.nil?)
108
+ end
103
109
  self
104
110
  end
105
111
 
@@ -167,6 +173,23 @@ module Mongoid #:nodoc:
167
173
  def skip(value = 0)
168
174
  @options[:skip] = value; self
169
175
  end
176
+
177
+ # Adds a criterion to the +Criteria+ that specifies a type or an Array of type that must be matched.
178
+ #
179
+ # Options:
180
+ #
181
+ # types : An +Array+ of types of a +String+ representing the Type of you search
182
+ #
183
+ # Example:
184
+ #
185
+ # <tt>criteria.type('Browser')</tt>
186
+ # <tt>criteria.type(['Firefox', 'Browser'])</tt>
187
+ #
188
+ # Returns: <tt>self</tt>
189
+ def type(types)
190
+ types = [types] unless types.is_a?(Array)
191
+ self.in(:_type => types)
192
+ end
170
193
  end
171
194
  end
172
195
  end
@@ -5,7 +5,6 @@ module Mongoid #:nodoc
5
5
  # Operations on the Mongo::Cursor object that will not get overriden by the
6
6
  # Mongoid::Cursor are defined here.
7
7
  OPERATIONS = [
8
- :admin,
9
8
  :close,
10
9
  :closed?,
11
10
  :count,
@@ -14,9 +14,8 @@ module Mongoid #:nodoc:
14
14
  end
15
15
 
16
16
  protected
17
- # Instantiate a new logger to stdout or a rails logger if available.
18
17
  def initialize
19
- @logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
18
+ @logger = Mongoid::Logger.new
20
19
  end
21
20
  end
22
21
  end
@@ -11,6 +11,10 @@ module Mongoid #:nodoc:
11
11
  attr_reader :new_record
12
12
 
13
13
  delegate :primary_key, :to => "self.class"
14
+
15
+ unless self.instance_of?(Class) and self.name == ""
16
+ (@@descendants ||= {})[self] = :seen
17
+ end
14
18
  end
15
19
 
16
20
  module ClassMethods #:nodoc:
@@ -58,12 +62,17 @@ module Mongoid #:nodoc:
58
62
  set_callback :save, :before, :identify
59
63
  end
60
64
 
65
+ # Returns the classes that have included Mongoid::Document
66
+ def self.descendents
67
+ (@@descendants ||= {}).keys
68
+ end
69
+
61
70
  # Returns all types to query for when using this class as the base.
62
71
  # *subclasses* is from activesupport. Note that a bug in *subclasses*
63
72
  # causes the first call to only return direct children, hence
64
73
  # the double call and unique.
65
74
  def _types
66
- @_type ||= [subclasses + subclasses + [self.name]].flatten.uniq
75
+ @_type ||= [subclasses + subclasses + [self.name]].flatten.uniq.map(&:to_s)
67
76
  end
68
77
  end
69
78
 
@@ -169,10 +178,23 @@ module Mongoid #:nodoc:
169
178
  # memoized association and notify the parent of the change.
170
179
  def remove(child)
171
180
  name = child.association_name
172
- reset(name) { @attributes.remove(name, child.raw_attributes) }
173
- notify
181
+ if @building_nested
182
+ @attributes.remove(name, child.raw_attributes)
183
+ else
184
+ reset(name) do
185
+ @attributes.remove(name, child.raw_attributes)
186
+ @attributes[name]
187
+ end
188
+ notify
189
+ end
174
190
  end
175
191
 
192
+ # def remove_without_reset
193
+ # name = child.association_name
194
+ # @attributes.remove(name, child.raw_attributes)
195
+ # notify
196
+ # end
197
+
176
198
  # Return an array with this +Document+ only in it.
177
199
  def to_a
178
200
  [ self ]
@@ -1,7 +1,6 @@
1
1
  # encoding: utf-8
2
2
  require "mongoid/extensions/time_conversions"
3
3
  require "mongoid/extensions/array/accessors"
4
- require "mongoid/extensions/array/aliasing"
5
4
  require "mongoid/extensions/array/assimilation"
6
5
  require "mongoid/extensions/array/conversions"
7
6
  require "mongoid/extensions/array/parentization"
@@ -25,6 +24,7 @@ require "mongoid/extensions/proc/scoping"
25
24
  require "mongoid/extensions/string/conversions"
26
25
  require "mongoid/extensions/string/inflections"
27
26
  require "mongoid/extensions/symbol/inflections"
27
+ require "mongoid/extensions/symbol/conversions"
28
28
  require "mongoid/extensions/true_class/equality"
29
29
  require "mongoid/extensions/objectid/conversions"
30
30
 
@@ -101,6 +101,7 @@ end
101
101
  class Symbol #:nodoc
102
102
  remove_method :size if instance_methods.include? :size # temporal fix for ruby 1.9
103
103
  include Mongoid::Extensions::Symbol::Inflections
104
+ include Mongoid::Extensions::Symbol::Conversions
104
105
  end
105
106
 
106
107
  class Time #:nodoc
@@ -8,7 +8,7 @@ module Mongoid #:nodoc:
8
8
  begin
9
9
  Float(value)
10
10
  rescue ArgumentError => e
11
- puts(e.message); value
11
+ value
12
12
  end
13
13
  end
14
14
  def get(value)
@@ -8,7 +8,7 @@ module Mongoid #:nodoc:
8
8
  begin
9
9
  Integer(value)
10
10
  rescue ArgumentError => e
11
- puts(e.message); value
11
+ value
12
12
  end
13
13
  end
14
14
  def get(value)
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Extensions #:nodoc:
4
+ module Symbol#:nodoc:
5
+ # This module converts objects into mongoid related objects.
6
+ module Conversions #:nodoc:
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def set(value)
11
+ (value.nil? or (value.respond_to?(:empty?) && value.empty?)) ? nil : value.to_sym
12
+ end
13
+
14
+ def get(value)
15
+ value
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -4,8 +4,9 @@ module Mongoid #:nodoc:
4
4
 
5
5
  # Delegate to the criteria methods that are natural for creating a new
6
6
  # criteria.
7
- [ :all_in, :any_in, :avg, :excludes, :limit, :max, :min,
8
- :not_in, :only, :order_by, :skip, :sum, :where ].each do |name|
7
+ [ :all_in, :any_in, :asc, :ascending, :avg, :desc, :descending,
8
+ :excludes, :limit, :max, :min, :not_in, :only, :order_by,
9
+ :skip, :sum, :where, :near ].each do |name|
9
10
  define_method(name) do |*args|
10
11
  criteria.send(name, *args)
11
12
  end
@@ -3,24 +3,35 @@ module Mongoid #:nodoc
3
3
  module Indexes #:nodoc
4
4
  extend ActiveSupport::Concern
5
5
  included do
6
- cattr_accessor :indexed
7
- self.indexed = false
6
+ cattr_accessor :index_options
7
+ self.index_options = {}
8
8
  end
9
9
 
10
10
  module ClassMethods #:nodoc
11
+
12
+ # Send the actual index creation comments to the MongoDB driver
13
+ def create_indexes
14
+ return unless index_options
15
+
16
+ index_options.each do |name, options|
17
+ self._collection.create_index(name, options)
18
+ end
19
+ end
20
+
11
21
  # Add the default indexes to the root document if they do not already
12
22
  # exist. Currently this is only _type.
13
23
  def add_indexes
14
- if hereditary && !indexed
15
- self._collection.create_index(:_type, :unique => false, :background => true)
16
- self.indexed = true
24
+ if hereditary && !index_options[:_type]
25
+ self.index_options[:_type] = {:unique => false, :background => true}
17
26
  end
27
+ create_indexes if Mongoid.autocreate_indexes
18
28
  end
19
29
 
20
30
  # Adds an index on the field specified. Options can be :unique => true or
21
31
  # :unique => false. It will default to the latter.
22
32
  def index(name, options = { :unique => false })
23
- collection.create_index(name, options)
33
+ self.index_options[name] = options
34
+ create_indexes if Mongoid.autocreate_indexes
24
35
  end
25
36
  end
26
37
  end
@@ -0,0 +1,19 @@
1
+ module Mongoid
2
+ class Logger
3
+
4
+ delegate :info, :debug, :error, :fatal, :unknown, :to => :logger, :allow_nil => true
5
+
6
+ def warn(message)
7
+ logger.warn(message) if logger && logger.respond_to?(:warn)
8
+ end
9
+
10
+ def logger
11
+ Mongoid.logger
12
+ end
13
+
14
+ def inspect
15
+ "#<Mongoid::Logger:0x#{object_id.to_s(16)} @logger=#{logger.inspect}>"
16
+ end
17
+
18
+ end
19
+ end
@@ -1,18 +1,5 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc:
3
- module Paranoid #:nodoc:
4
- # Find deleted documents
5
- #
6
- # Examples:
7
- #
8
- # <tt>Person.deleted</tt> # all deleted employees
9
- # <tt>Company.first.employees.deleted</tt> # works with a join
10
- # <tt>Person.deleted.find("4c188dea7b17235a2a000001").first</tt> # retrieve by id a deleted person
11
- def deleted
12
- where(:deleted_at.exists => false)
13
- end
14
- end
15
-
16
3
  # Include this module to get soft deletion of root level documents.
17
4
  # This will add a deleted_at field to the +Document+, managed automatically.
18
5
  # Potentially incompatible with unique indices. (if collisions with deleted items)
@@ -27,7 +14,6 @@ module Mongoid #:nodoc:
27
14
  extend ActiveSupport::Concern
28
15
 
29
16
  included do
30
- Mongoid::Criteria.send(:include, Mongoid::Paranoid)
31
17
  field :deleted_at, :type => Time
32
18
  end
33
19
 
@@ -101,6 +87,18 @@ module Mongoid #:nodoc:
101
87
  def criteria
102
88
  super.where(:deleted_at.exists => false)
103
89
  end
90
+
91
+ # Find deleted documents
92
+ #
93
+ # Examples:
94
+ #
95
+ # <tt>Person.deleted</tt> # all deleted employees
96
+ # <tt>Company.first.employees.deleted</tt> # works with a join
97
+ # <tt>Person.deleted.find("4c188dea7b17235a2a000001").first</tt> # retrieve by id a deleted person
98
+ def deleted
99
+ where(:deleted_at.exists => true)
100
+ end
101
+
104
102
  end
105
103
  end
106
104
  end
@@ -11,10 +11,6 @@ module Mongoid #:nodoc:
11
11
  # false
12
12
  # );
13
13
  class RemoveEmbedded < Command
14
- # Insert the new document in the database. If the document's parent is a
15
- # new record, we will call save on the parent, otherwise we will $push
16
- # the document onto the parent.
17
- #
18
14
  # Remove the document from the database. If the parent is a new record,
19
15
  # it will get removed in Ruby only. If the parent is not a new record
20
16
  # then either an $unset or $set will occur, depending if it's an
@@ -60,7 +60,10 @@ module Mongoid #:nodoc:
60
60
  def update
61
61
  updates = @document._updates
62
62
  unless updates.empty?
63
+ other_pushes = updates.delete(:other)
64
+
63
65
  @collection.update(@document._selector, updates, @options.merge(:multi => false))
66
+ @collection.update(@document._selector, { "$pushAll" => other_pushes }, @options.merge(:multi => false)) if other_pushes
64
67
  end; true
65
68
  end
66
69
  end
@@ -1,17 +1,28 @@
1
+ require "singleton"
1
2
  require "rails"
3
+ require "mongoid/config"
2
4
  module Rails #:nodoc:
3
5
  module Mongoid #:nodoc:
4
6
  class Railtie < Rails::Railtie #:nodoc:
5
7
 
6
- # do we want a custom log subscriber for mongoid?
7
- # log_subscriber :mongoid, ::Mongoid::Railties::LogSubscriber.new
8
-
9
8
  config.generators.orm :mongoid, :migration => false
10
9
 
11
10
  rake_tasks do
12
11
  load "mongoid/railties/database.rake"
13
12
  end
14
13
 
14
+ # Exposes Mongoid's configuration to the Rails application configuration.
15
+ #
16
+ # Example:
17
+ #
18
+ # module MyApplication
19
+ # class Application < Rails::Application
20
+ # config.mongoid.logger = Logger.new($stdout, :warn)
21
+ # config.mongoid.reconnect_time = 10
22
+ # end
23
+ # end
24
+ config.mongoid = ::Mongoid::Config.instance
25
+
15
26
  # Initialize Mongoid. This will look for a mongoid.yml in the config
16
27
  # directory and configure mongoid appropriately.
17
28
  #
@@ -51,6 +62,28 @@ module Rails #:nodoc:
51
62
  end
52
63
  end
53
64
  end
65
+
66
+ initializer "reconnect to master if application is preloaded" do
67
+ config.after_initialize do
68
+ # Unicorn clears the START_CTX when a worker is forked, so if we have
69
+ # data in START_CTX then we know we're being preloaded. Unicorn does
70
+ # not provide application-level hooks for executing code after the
71
+ # process has forked, so we reconnect lazily.
72
+ if defined?(Unicorn) && !Unicorn::HttpServer::START_CTX.empty?
73
+ ::Mongoid.reconnect!(false)
74
+ end
75
+
76
+ # Passenger provides the :starting_worker_process event for executing
77
+ # code after it has forked, so we use that and reconnect immediately.
78
+ if defined?(PhusionPassenger)
79
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
80
+ if forked
81
+ ::Mongoid.reconnect!
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
54
87
  end
55
88
  end
56
- end
89
+ end