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

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