mongoid 2.0.0.rc.7 → 2.0.0.rc.8

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 (90) hide show
  1. data/lib/config/locales/en.yml +3 -0
  2. data/lib/config/locales/id.yml +46 -0
  3. data/lib/config/locales/ja.yml +40 -0
  4. data/lib/config/locales/vi.yml +45 -0
  5. data/lib/mongoid.rb +5 -3
  6. data/lib/mongoid/attributes.rb +24 -63
  7. data/lib/mongoid/attributes/processing.rb +5 -2
  8. data/lib/mongoid/callbacks.rb +10 -0
  9. data/lib/mongoid/collection.rb +24 -0
  10. data/lib/mongoid/collections/master.rb +14 -6
  11. data/lib/mongoid/collections/operations.rb +1 -1
  12. data/lib/mongoid/collections/retry.rb +39 -0
  13. data/lib/mongoid/collections/slaves.rb +26 -10
  14. data/lib/mongoid/components.rb +4 -4
  15. data/lib/mongoid/config.rb +6 -3
  16. data/lib/mongoid/contexts.rb +0 -1
  17. data/lib/mongoid/contexts/enumerable.rb +19 -7
  18. data/lib/mongoid/contexts/mongo.rb +9 -5
  19. data/lib/mongoid/copyable.rb +10 -8
  20. data/lib/mongoid/criteria.rb +83 -61
  21. data/lib/mongoid/criterion/builder.rb +34 -0
  22. data/lib/mongoid/criterion/creational.rb +2 -2
  23. data/lib/mongoid/criterion/exclusion.rb +58 -32
  24. data/lib/mongoid/criterion/inclusion.rb +49 -10
  25. data/lib/mongoid/criterion/optional.rb +1 -1
  26. data/lib/mongoid/criterion/selector.rb +80 -11
  27. data/lib/mongoid/cursor.rb +6 -1
  28. data/lib/mongoid/default_scope.rb +27 -19
  29. data/lib/mongoid/document.rb +26 -1
  30. data/lib/mongoid/errors.rb +1 -0
  31. data/lib/mongoid/errors/mixed_relations.rb +37 -0
  32. data/lib/mongoid/extensions/object_id/conversions.rb +7 -4
  33. data/lib/mongoid/factory.rb +1 -1
  34. data/lib/mongoid/field.rb +47 -30
  35. data/lib/mongoid/fields.rb +9 -2
  36. data/lib/mongoid/finders.rb +15 -49
  37. data/lib/mongoid/identity.rb +6 -4
  38. data/lib/mongoid/keys.rb +85 -31
  39. data/lib/mongoid/multi_parameter_attributes.rb +2 -2
  40. data/lib/mongoid/named_scope.rb +129 -28
  41. data/lib/mongoid/observer.rb +36 -0
  42. data/lib/mongoid/paranoia.rb +3 -3
  43. data/lib/mongoid/paths.rb +1 -1
  44. data/lib/mongoid/persistence.rb +2 -0
  45. data/lib/mongoid/persistence/atomic.rb +88 -0
  46. data/lib/mongoid/persistence/atomic/add_to_set.rb +30 -0
  47. data/lib/mongoid/persistence/atomic/inc.rb +28 -0
  48. data/lib/mongoid/persistence/atomic/operation.rb +44 -0
  49. data/lib/mongoid/persistence/atomic/pull_all.rb +33 -0
  50. data/lib/mongoid/persistence/atomic/push.rb +28 -0
  51. data/lib/mongoid/railtie.rb +13 -1
  52. data/lib/mongoid/relations.rb +1 -0
  53. data/lib/mongoid/relations/accessors.rb +20 -2
  54. data/lib/mongoid/relations/builders/embedded/one.rb +1 -0
  55. data/lib/mongoid/relations/builders/nested_attributes/many.rb +17 -6
  56. data/lib/mongoid/relations/builders/referenced/many.rb +2 -1
  57. data/lib/mongoid/relations/builders/referenced/one.rb +1 -0
  58. data/lib/mongoid/relations/embedded/atomic.rb +86 -0
  59. data/lib/mongoid/relations/embedded/atomic/operation.rb +63 -0
  60. data/lib/mongoid/relations/embedded/atomic/pull.rb +65 -0
  61. data/lib/mongoid/relations/embedded/atomic/push_all.rb +59 -0
  62. data/lib/mongoid/relations/embedded/atomic/set.rb +61 -0
  63. data/lib/mongoid/relations/embedded/atomic/unset.rb +41 -0
  64. data/lib/mongoid/relations/embedded/many.rb +57 -25
  65. data/lib/mongoid/relations/macros.rb +6 -4
  66. data/lib/mongoid/relations/many.rb +51 -10
  67. data/lib/mongoid/relations/metadata.rb +4 -2
  68. data/lib/mongoid/relations/proxy.rb +39 -24
  69. data/lib/mongoid/relations/referenced/many.rb +47 -26
  70. data/lib/mongoid/relations/referenced/many_to_many.rb +47 -14
  71. data/lib/mongoid/relations/referenced/one.rb +14 -0
  72. data/lib/mongoid/sharding.rb +51 -0
  73. data/lib/mongoid/state.rb +3 -2
  74. data/lib/mongoid/timestamps.rb +5 -29
  75. data/lib/mongoid/timestamps/created.rb +31 -0
  76. data/lib/mongoid/timestamps/updated.rb +33 -0
  77. data/lib/mongoid/validations.rb +10 -3
  78. data/lib/mongoid/validations/referenced.rb +58 -0
  79. data/lib/mongoid/version.rb +1 -1
  80. data/lib/mongoid/versioning.rb +67 -5
  81. data/lib/rails/generators/mongoid/model/templates/model.rb +2 -0
  82. data/lib/rails/generators/mongoid/observer/observer_generator.rb +17 -0
  83. data/lib/rails/generators/mongoid/observer/templates/observer.rb +4 -0
  84. data/lib/rails/generators/mongoid_generator.rb +10 -1
  85. data/lib/rails/mongoid.rb +1 -0
  86. metadata +29 -8
  87. data/lib/mongoid/contexts/ids.rb +0 -25
  88. data/lib/mongoid/modifiers.rb +0 -24
  89. data/lib/mongoid/modifiers/command.rb +0 -18
  90. data/lib/mongoid/modifiers/inc.rb +0 -24
@@ -1,45 +1,83 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc:
3
+
4
+ # This module defines the behaviour for overriding the default ids on
5
+ # documents.
3
6
  module Keys
4
7
  extend ActiveSupport::Concern
5
- included do
6
- cattr_accessor :primary_key, :_identity
7
- self._identity = { :type => BSON::ObjectId }
8
8
 
9
- delegate \
10
- :_id_type,
11
- :primary_key,
12
- :using_object_ids?, :to => "self.class"
9
+ included do
10
+ cattr_accessor :primary_key, :object_ids
11
+ delegate :primary_key, :using_object_ids?, :to => "self.class"
13
12
  end
14
13
 
15
- module ClassMethods #:nodoc:
14
+ private
16
15
 
17
- # Convenience method for returning the type of the id for this class.
18
- #
19
- # Example:
20
- #
21
- # <tt>Person._id_type</tt>
22
- #
23
- # Returns:
24
- #
25
- # The type of the id.
26
- def _id_type
27
- _identity[:type]
16
+ # Determines if any field that the document id is composed of has changed.
17
+ #
18
+ # @example Has any key field changed?
19
+ # document.key_field_changed?
20
+ #
21
+ # @return [ true, false ] Has a key field changed?
22
+ #
23
+ # @since 2.0.0
24
+ def key_field_changed?
25
+ primary_key.any? { |field| changed.include?(field.to_s) }
26
+ end
27
+
28
+ # Sits around a save when composite keys are in play to handle the id magic
29
+ # if a key field has changed.
30
+ #
31
+ # @example Set the composite key.
32
+ # document.set_composite_key
33
+ #
34
+ # @param [ Proc ] block The block this surrounds.
35
+ #
36
+ # @since 2.0.0
37
+ def set_composite_key(&block)
38
+ if persisted? && key_field_changed?
39
+ swap_composite_keys(&block)
40
+ else
41
+ identify and block.call
28
42
  end
43
+ end
44
+
45
+ # Swap out the composite key only after the document has been saved.
46
+ #
47
+ # @example Swap out the keys.
48
+ # document.swap_composite_keys
49
+ #
50
+ # @param [ Proc ] block The save block getting called.
51
+ #
52
+ # @since 2.0.0
53
+ def swap_composite_keys(&block)
54
+ old_id, new_id = id.dup, identify
55
+ @attributes["_id"] = old_id
56
+ block.call
57
+ @attributes["_id"] = new_id
58
+ end
59
+
60
+ module ClassMethods #:nodoc:
29
61
 
30
62
  # Used for telling Mongoid on a per model basis whether to override the
31
63
  # default +BSON::ObjectId+ and use a different type. This will be
32
64
  # expanded in the future for requiring a PkFactory if the type is not a
33
65
  # +BSON::ObjectId+ or +String+.
34
66
  #
35
- # Example:
36
- #
67
+ # @example Change the documents key type.
37
68
  # class Person
38
69
  # include Mongoid::Document
39
70
  # identity :type => String
40
71
  # end
72
+ #
73
+ # @param [ Hash ] options The options.
74
+ #
75
+ # @option options [ Class ] :type The type of the id.
76
+ #
77
+ # @since 2.0.0.beta.1
41
78
  def identity(options = {})
42
- self._identity = options
79
+ fields["_id"].type = options[:type]
80
+ @object_ids = id_is_object
43
81
  end
44
82
 
45
83
  # Defines the field that will be used for the id of this +Document+. This
@@ -47,30 +85,46 @@ module Mongoid #:nodoc:
47
85
  # the field that was supplied. This is good for use for readable URLS in
48
86
  # web applications.
49
87
  #
50
- # Example:
51
- #
88
+ # @example Create a composite id.
52
89
  # class Person
53
90
  # include Mongoid::Document
54
91
  # key :first_name, :last_name
55
92
  # end
93
+ #
94
+ # @param [ Array<Symbol> ] The fields the key is composed of.
95
+ #
96
+ # @since 1.0.0
56
97
  def key(*fields)
57
98
  self.primary_key = fields
58
99
  identity(:type => String)
59
- set_callback :save, :before, :identify
100
+ set_callback(:save, :around, :set_composite_key)
60
101
  end
61
102
 
62
103
  # Convenience method for determining if we are using +BSON::ObjectIds+ as
63
104
  # our id.
64
105
  #
65
- # Example:
106
+ # @example Does this class use object ids?
107
+ # person.using_object_ids?
66
108
  #
67
- # <tt>person.using_object_ids?</tt>
109
+ # @return [ true, false ] If the class uses BSON::ObjectIds for the id.
68
110
  #
69
- # Returns:
70
- #
71
- # true if we are using BSON::ObjectIds
111
+ # @since 1.0.0
72
112
  def using_object_ids?
73
- _id_type == BSON::ObjectId
113
+ @object_ids ||= id_is_object
114
+ end
115
+
116
+ private
117
+
118
+ # Is the id field type an object id?
119
+ #
120
+ # @example Is type an object id.
121
+ # Class.id_is_object
122
+ #
123
+ # @return [ true, false ] Is the id an object id.
124
+ #
125
+ # @since 2.0.0
126
+ def id_is_object
127
+ fields["_id"].type == BSON::ObjectId
74
128
  end
75
129
  end
76
130
  end
@@ -27,7 +27,7 @@ module Mongoid #:nodoc:
27
27
  end
28
28
  end
29
29
 
30
- def process(attrs = nil)
30
+ def process(attrs = nil, guard_protected_attributes = true)
31
31
  if attrs
32
32
  errors = []
33
33
  attributes = {}
@@ -56,7 +56,7 @@ module Mongoid #:nodoc:
56
56
  raise Errors::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
57
57
  end
58
58
 
59
- super attributes
59
+ super attributes, guard_protected_attributes
60
60
  else
61
61
  super
62
62
  end
@@ -1,36 +1,137 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc:
3
+
4
+ # This module contains the named scoping behaviour.
3
5
  module NamedScope
4
- # Creates a named_scope for the +Document+, similar to ActiveRecord's
5
- # named_scopes. +NamedScopes+ are proxied +Criteria+ objects that can be
6
- # chained.
7
- #
8
- # Example:
9
- #
10
- # class Person
11
- # include Mongoid::Document
12
- # field :active, :type => Boolean
13
- # field :count, :type => Integer
14
- #
15
- # named_scope :active, :where => { :active => true }
16
- # named_scope :count_gt_one, :where => { :count.gt => 1 }
17
- # named_scope :at_least_count, lambda { |count| { :where => { :count.gt => count } } }
18
- # end
19
- def named_scope(name, conditions = {}, &block)
20
- name = name.to_sym
21
- scopes[name] = Scope.new(conditions, &block)
22
- (class << self; self; end).class_eval <<-EOT
23
- def #{name}(*args)
24
- scope = scopes[:#{name}]
25
- scope.extend(criteria.fuse(scope.conditions.scoped(*args)))
26
- end
27
- EOT
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :scopes
10
+ self.scopes = {}
28
11
  end
29
- alias :scope :named_scope
30
12
 
31
- # Return the scopes or default to an empty +Hash+.
32
- def scopes
33
- read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
13
+ module ClassMethods #:nodoc:
14
+
15
+ # Gets either the last scope on the stack or creates a new criteria.
16
+ #
17
+ # @example Get the last or new.
18
+ # Person.scoping(true)
19
+ #
20
+ # @param [ true, false ] embedded Is this scope for an embedded doc?
21
+ # @param [ true, false ] scoped Are we applying default scoping?
22
+ #
23
+ # @return [ Criteria ] The last scope or a new one.
24
+ #
25
+ # @since 2.0.0
26
+ def criteria(embedded = false, scoped = true)
27
+ scope_stack.last || Criteria.new(self, embedded).tap do |crit|
28
+ return crit.fuse(default_scoping) if default_scoping && scoped
29
+ end
30
+ end
31
+
32
+ # Creates a named_scope for the +Document+, similar to ActiveRecord's
33
+ # named_scopes. +NamedScopes+ are proxied +Criteria+ objects that can be
34
+ # chained.
35
+ #
36
+ # @example Create named scopes.
37
+ #
38
+ # class Person
39
+ # include Mongoid::Document
40
+ # field :active, :type => Boolean
41
+ # field :count, :type => Integer
42
+ #
43
+ # scope :active, :where => { :active => true }
44
+ # scope :count_gt_one, :where => { :count.gt => 1 }
45
+ # scope :at_least_count, lambda { |count| { :where => { :count.gt => count } } }
46
+ # end
47
+ #
48
+ # @param [ Symbol ] name The name of the scope.
49
+ # @param [ Hash, Criteria ] conditions The conditions of the scope.
50
+ #
51
+ # @since 1.0.0
52
+ def scope(name, conditions = {}, &block)
53
+ name = name.to_sym
54
+ valid_scope_name?(name)
55
+ scopes[name] = Scope.new(conditions, &block)
56
+ (class << self; self; end).class_eval <<-EOT
57
+ def #{name}(*args)
58
+ scope = scopes[:#{name}]
59
+ scope.extend(criteria.fuse(scope.conditions.scoped(*args)))
60
+ end
61
+ EOT
62
+ end
63
+ alias :named_scope :scope
64
+
65
+ # Get a criteria object for the class, scoped to the default if defined.
66
+ #
67
+ # @example Get a scoped criteria.
68
+ # Person.scoped
69
+ #
70
+ # @param [ true, false ] embedded Is the criteria for embedded docs?
71
+ #
72
+ # @return [ Criteria ] The scoped criteria.
73
+ #
74
+ # @since 2.0.0
75
+ def scoped(embedded = false)
76
+ criteria(embedded, true)
77
+ end
78
+
79
+ # Initializes and returns the current scope stack.
80
+ #
81
+ # @example Get the scope stack.
82
+ # Person.scope_stack
83
+ #
84
+ # @return [ Array<Criteria> ] The scope stack.
85
+ #
86
+ # @since 1.0.0
87
+ def scope_stack
88
+ scope_stack_for = Thread.current[:mongoid_scope_stack] ||= {}
89
+ scope_stack_for[object_id] ||= []
90
+ end
91
+
92
+ # Get a criteria object for the class, ignoring default scoping.
93
+ #
94
+ # @example Get an unscoped criteria.
95
+ # Person.scoped
96
+ #
97
+ # @param [ true, false ] embedded Is the criteria for embedded docs?
98
+ #
99
+ # @return [ Criteria ] The unscoped criteria.
100
+ #
101
+ # @since 2.0.0
102
+ def unscoped(embedded = false)
103
+ criteria(embedded, false)
104
+ end
105
+
106
+ # Pushes the provided criteria onto the scope stack, and removes it after the
107
+ # provided block is yielded.
108
+ #
109
+ # @example Yield to the criteria.
110
+ # Person.with_scope(criteria)
111
+ #
112
+ # @param [ Criteria ] criteria The criteria to apply.
113
+ #
114
+ # @return [ Criteria ] The yielded criteria.
115
+ #
116
+ # @since 1.0.0
117
+ def with_scope(criteria)
118
+ scope_stack = self.scope_stack
119
+ scope_stack << criteria
120
+ begin
121
+ yield criteria
122
+ ensure
123
+ scope_stack.pop
124
+ end
125
+ end
126
+
127
+ protected
128
+
129
+ def valid_scope_name?(name)
130
+ if !scopes[name] && respond_to?(name, true)
131
+ Mongoid.logger.warn "Creating scope :#{name}. " \
132
+ "Overwriting existing method #{self.name}.#{name}."
133
+ end
134
+ end
34
135
  end
35
136
  end
36
137
  end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ class Observer < ActiveModel::Observer
4
+ def initialize
5
+ super
6
+ observed_descendants.each { |klass| add_observer!(klass) }
7
+ end
8
+
9
+ protected
10
+
11
+ def observed_descendants
12
+ observed_classes.sum([]) { |klass| klass.descendants }
13
+ end
14
+
15
+ def add_observer!(klass)
16
+ super
17
+ define_callbacks klass
18
+ end
19
+
20
+ def define_callbacks(klass)
21
+ observer = self
22
+ observer_name = observer.class.name.underscore.gsub('/', '__')
23
+
24
+ Mongoid::Callbacks::CALLBACKS.each do |callback|
25
+ next unless respond_to?(callback)
26
+ callback_meth = :"_notify_#{observer_name}_for_#{callback}"
27
+ unless klass.respond_to?(callback_meth)
28
+ klass.send(:define_method, callback_meth) do |&block|
29
+ observer.send(callback, self, &block)
30
+ end
31
+ klass.send(callback, callback_meth)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -42,19 +42,19 @@ module Mongoid #:nodoc:
42
42
  #
43
43
  # Example:
44
44
  #
45
- # <tt>document._remove</tt>
45
+ # <tt>document.remove</tt>
46
46
  #
47
47
  # Returns:
48
48
  #
49
49
  # true
50
- def _remove(options = {})
50
+ def remove(options = {})
51
51
  now = Time.now
52
52
  collection.update({ :_id => self.id }, { '$set' => { :deleted_at => Time.now } })
53
53
  @attributes["deleted_at"] = now
54
54
  true
55
55
  end
56
56
 
57
- alias :delete :_remove
57
+ alias :delete :remove
58
58
 
59
59
  # Determines if this document is destroyed.
60
60
  #
@@ -55,7 +55,7 @@ module Mongoid #:nodoc:
55
55
  #
56
56
  # <tt>address.selector</tt>
57
57
  def _selector
58
- embedded? ? _parent._selector.merge("#{_path}._id" => id) : { "_id" => id }
58
+ (embedded? ? _parent._selector.merge("#{_path}._id" => id) : { "_id" => id }).merge(shard_key_selector)
59
59
  end
60
60
  end
61
61
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ require "mongoid/persistence/atomic"
2
3
  require "mongoid/persistence/command"
3
4
  require "mongoid/persistence/insert"
4
5
  require "mongoid/persistence/insert_embedded"
@@ -19,6 +20,7 @@ module Mongoid #:nodoc:
19
20
  # document.upsert
20
21
  module Persistence
21
22
  extend ActiveSupport::Concern
23
+ include Atomic
22
24
 
23
25
  # Remove the document from the datbase with callbacks.
24
26
  #
@@ -0,0 +1,88 @@
1
+ # encoding: utf-8
2
+ require "mongoid/persistence/atomic/operation"
3
+ require "mongoid/persistence/atomic/add_to_set"
4
+ require "mongoid/persistence/atomic/inc"
5
+ require "mongoid/persistence/atomic/pull_all"
6
+ require "mongoid/persistence/atomic/push"
7
+
8
+ module Mongoid #:nodoc:
9
+ module Persistence #:nodoc:
10
+
11
+ # This module provides the explicit atomic operations helpers on the
12
+ # document itself.
13
+ module Atomic
14
+
15
+ # Performs an atomic $addToSet of the provided value on the supplied field.
16
+ # If the field does not exist it will be initialized as an empty array.
17
+ #
18
+ # If the value already exists on the array it will not be added.
19
+ #
20
+ # @example Add only a unique value on the field.
21
+ # person.add_to_set(:aliases, "Bond")
22
+ #
23
+ # @param [ Symbol ] field The name of the field.
24
+ # @param [ Object ] value The value to add.
25
+ # @param [ Hash ] options The mongo persistence options.
26
+ #
27
+ # @return [ Array<Object> ] The new value of the field.
28
+ #
29
+ # @since 2.0.0
30
+ def add_to_set(field, value, options = {})
31
+ AddToSet.new(self, field, value, options).persist
32
+ end
33
+
34
+ # Performs an atomic $inc of the provided value on the supplied
35
+ # field. If the field does not exist it will be initialized as
36
+ # the provided value.
37
+ #
38
+ # @example Increment a field.
39
+ # person.inc(:score, 2)
40
+ #
41
+ # @param [ Symbol ] field The name of the field.
42
+ # @param [ Integer ] value The value to increment.
43
+ # @param [ Hash ] options The mongo persistence options.
44
+ #
45
+ # @return [ Array<Object> ] The new value of the field.
46
+ #
47
+ # @since 2.0.0
48
+ def inc(field, value, options = {})
49
+ Inc.new(self, field, value, options).persist
50
+ end
51
+
52
+ # Performs an atomic $pullAll of the provided value on the supplied
53
+ # field. If the field does not exist it will be initialized as an
54
+ # empty array.
55
+ #
56
+ # @example Pull the values from the field.
57
+ # person.pull_all(:aliases, [ "Bond", "James" ])
58
+ #
59
+ # @param [ Symbol ] field The name of the field.
60
+ # @param [ Array<Object> ] value The values to pull.
61
+ # @param [ Hash ] options The mongo persistence options.
62
+ #
63
+ # @return [ Array<Object> ] The new value of the field.
64
+ #
65
+ # @since 2.0.0
66
+ def pull_all(field, value, options = {})
67
+ PullAll.new(self, field, value, options).persist
68
+ end
69
+
70
+ # Performs an atomic $push of the provided value on the supplied field. If
71
+ # the field does not exist it will be initialized as an empty array.
72
+ #
73
+ # @example Push a value on the field.
74
+ # person.push(:aliases, "Bond")
75
+ #
76
+ # @param [ Symbol ] field The name of the field.
77
+ # @param [ Object ] value The value to push.
78
+ # @param [ Hash ] options The mongo persistence options.
79
+ #
80
+ # @return [ Array<Object> ] The new value of the field.
81
+ #
82
+ # @since 2.0.0
83
+ def push(field, value, options = {})
84
+ Push.new(self, field, value, options).persist
85
+ end
86
+ end
87
+ end
88
+ end