mongoid 2.0.1 → 2.0.2

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 (61) hide show
  1. data/Rakefile +4 -4
  2. data/lib/config/locales/{pt-br.yml → pt-BR.yml} +5 -5
  3. data/lib/config/locales/ru.yml +1 -1
  4. data/lib/config/locales/zh-CN.yml +2 -0
  5. data/lib/mongoid.rb +0 -1
  6. data/lib/mongoid/attributes.rb +9 -6
  7. data/lib/mongoid/collection.rb +21 -0
  8. data/lib/mongoid/config.rb +31 -8
  9. data/lib/mongoid/config/replset_database.rb +32 -2
  10. data/lib/mongoid/contexts.rb +0 -1
  11. data/lib/mongoid/contexts/enumerable.rb +73 -36
  12. data/lib/mongoid/contexts/mongo.rb +5 -12
  13. data/lib/mongoid/copyable.rb +2 -2
  14. data/lib/mongoid/criteria.rb +4 -23
  15. data/lib/mongoid/criterion/exclusion.rb +15 -0
  16. data/lib/mongoid/criterion/inclusion.rb +1 -1
  17. data/lib/mongoid/criterion/optional.rb +0 -1
  18. data/lib/mongoid/criterion/unconvertable.rb +20 -0
  19. data/lib/mongoid/cursor.rb +3 -3
  20. data/lib/mongoid/dirty.rb +8 -8
  21. data/lib/mongoid/document.rb +33 -36
  22. data/lib/mongoid/extensions.rb +7 -0
  23. data/lib/mongoid/extensions/object/checks.rb +32 -0
  24. data/lib/mongoid/extensions/object/conversions.rb +1 -1
  25. data/lib/mongoid/extensions/object_id/conversions.rb +6 -1
  26. data/lib/mongoid/extensions/range/conversions.rb +25 -0
  27. data/lib/mongoid/factory.rb +27 -10
  28. data/lib/mongoid/field.rb +50 -0
  29. data/lib/mongoid/fields.rb +42 -7
  30. data/lib/mongoid/finders.rb +5 -17
  31. data/lib/mongoid/identity.rb +1 -1
  32. data/lib/mongoid/inspection.rb +17 -21
  33. data/lib/mongoid/matchers.rb +6 -2
  34. data/lib/mongoid/matchers/strategies.rb +2 -2
  35. data/lib/mongoid/named_scope.rb +1 -1
  36. data/lib/mongoid/observer.rb +45 -14
  37. data/lib/mongoid/paranoia.rb +2 -2
  38. data/lib/mongoid/persistence.rb +2 -2
  39. data/lib/mongoid/persistence/update.rb +2 -1
  40. data/lib/mongoid/railtie.rb +3 -5
  41. data/lib/mongoid/relations.rb +1 -0
  42. data/lib/mongoid/relations/builders.rb +3 -3
  43. data/lib/mongoid/relations/builders/embedded/in.rb +1 -1
  44. data/lib/mongoid/relations/builders/embedded/many.rb +1 -1
  45. data/lib/mongoid/relations/builders/embedded/one.rb +1 -2
  46. data/lib/mongoid/relations/builders/referenced/in.rb +0 -3
  47. data/lib/mongoid/relations/builders/referenced/many.rb +21 -1
  48. data/lib/mongoid/relations/builders/referenced/one.rb +0 -4
  49. data/lib/mongoid/relations/embedded/many.rb +1 -17
  50. data/lib/mongoid/relations/macros.rb +3 -2
  51. data/lib/mongoid/relations/many.rb +2 -0
  52. data/lib/mongoid/relations/proxy.rb +1 -1
  53. data/lib/mongoid/relations/referenced/batch.rb +71 -0
  54. data/lib/mongoid/relations/referenced/batch/insert.rb +57 -0
  55. data/lib/mongoid/relations/referenced/many.rb +61 -2
  56. data/lib/mongoid/serialization.rb +1 -1
  57. data/lib/mongoid/validations/uniqueness.rb +1 -1
  58. data/lib/mongoid/version.rb +1 -1
  59. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +8 -11
  60. metadata +22 -64
  61. data/lib/mongoid/contexts/paging.rb +0 -50
@@ -66,7 +66,12 @@ module Mongoid #:nodoc:
66
66
  return args if args.is_a?(BSON::ObjectId) || !klass.using_object_ids?
67
67
  case args
68
68
  when ::String
69
- args.blank? ? nil : BSON::ObjectId.from_string(args)
69
+ return nil if args.blank?
70
+ if args.is_a?(Mongoid::Criterion::Unconvertable)
71
+ args
72
+ else
73
+ BSON::ObjectId.from_string(args)
74
+ end
70
75
  when ::Array
71
76
  args = args.reject(&:blank?) if reject_blank
72
77
  args.map do |arg|
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Extensions #:nodoc:
4
+ module Range #:nodoc:
5
+ module Conversions #:nodoc:
6
+ extend ActiveSupport::Concern
7
+
8
+ def to_hash
9
+ { "min" => min, "max" => max }
10
+ end
11
+
12
+ module ClassMethods #:nodoc:
13
+
14
+ def get(value)
15
+ value.nil? ? nil : ::Range.new(value["min"], value["max"])
16
+ end
17
+
18
+ def set(value)
19
+ value.nil? ? nil : value.to_hash
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,20 +1,37 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc:
3
- class Factory #:nodoc:
3
+
4
+ # Instantiates documents that came from the database.
5
+ module Factory
6
+ extend self
7
+
4
8
  # Builds a new +Document+ from the supplied attributes.
5
9
  #
6
- # Example:
10
+ # @example Build the document.
11
+ # Mongoid::Factory.build(Person, { "name" => "Durran" })
7
12
  #
8
- # <tt>Mongoid::Factory.build(Person, {})</tt>
13
+ # @param [ Class ] klass The class to instantiate from if _type is not present.
14
+ # @param [ Hash ] attributes The document attributes.
9
15
  #
10
- # Options:
16
+ # @return [ Document ] The instantiated document.
17
+ def build(klass, attributes = {})
18
+ type = (attributes || {})["_type"]
19
+ type.blank? ? klass.new(attributes) : type.constantize.new(attributes)
20
+ end
21
+
22
+ # Builds a new +Document+ from the supplied attributes loaded from the
23
+ # database.
24
+ #
25
+ # @example Build the document.
26
+ # Mongoid::Factory.from_db(Person, { "name" => "Durran" })
27
+ #
28
+ # @param [ Class ] klass The class to instantiate from if _type is not present.
29
+ # @param [ Hash ] attributes The document attributes.
11
30
  #
12
- # klass: The class to instantiate from if _type is not present.
13
- # attributes: The +Document+ attributes.
14
- def self.build(klass, attributes)
15
- attrs = {}.merge(attributes)
16
- type = attrs["_type"]
17
- type.present? ? type.constantize.instantiate(attrs) : klass.instantiate(attrs)
31
+ # @return [ Document ] The instantiated document.
32
+ def from_db(klass, attributes = {})
33
+ type = attributes["_type"]
34
+ type.blank? ? klass.instantiate(attributes) : type.constantize.instantiate(attributes)
18
35
  end
19
36
  end
20
37
  end
@@ -4,9 +4,59 @@ module Mongoid #:nodoc:
4
4
  # Defines the behaviour for defined fields in the document.
5
5
  class Field
6
6
 
7
+ NO_CAST_ON_READ = [
8
+ Array, Binary, Boolean, Float, Hash,
9
+ Integer, BSON::ObjectId, Set, String, Symbol
10
+ ]
11
+
7
12
  attr_accessor :type
8
13
  attr_reader :copyable, :klass, :label, :name, :options
9
14
 
15
+ class << self
16
+
17
+ # Return a map of custom option names to their handlers.
18
+ #
19
+ # @example
20
+ # Mongoid::Field.options
21
+ # # => { :required => #<Proc:0x00000100976b38> }
22
+ #
23
+ # @return [ Hash ] the option map
24
+ def options
25
+ @options ||= {}
26
+ end
27
+
28
+ # Stores the provided block to be run when the option name specified is
29
+ # defined on a field.
30
+ #
31
+ # No assumptions are made about what sort of work the handler might
32
+ # perform, so it will always be called if the `option_name` key is
33
+ # provided in the field definition -- even if it is false or nil.
34
+ #
35
+ # @example
36
+ # Mongoid::Field.option :required do |model, field, value|
37
+ # model.validates_presence_of field if value
38
+ # end
39
+ #
40
+ # @param [ Symbol ] option_name the option name to match against
41
+ # @param [ Proc ] block the handler to execute when the option is
42
+ # provided.
43
+ def option(option_name, &block)
44
+ options[option_name] = block
45
+ end
46
+
47
+ end
48
+
49
+ # When reading the field do we need to cast the value? This holds true when
50
+ # times are stored or for big decimals which are stored as strings.
51
+ #
52
+ # @example Typecast on a read?
53
+ # field.cast_on_read?
54
+ #
55
+ # @return [ true, false ] If the field should be cast.
56
+ def cast_on_read?
57
+ !NO_CAST_ON_READ.include?(type)
58
+ end
59
+
10
60
  # Get the default value for the field.
11
61
  #
12
62
  # @example Get the default.
@@ -32,6 +32,8 @@ module Mongoid #:nodoc
32
32
  # @option options [ Class ] :type The type of the field.
33
33
  # @option options [ String ] :label The label for the field.
34
34
  # @option options [ Object, Proc ] :default The field's default
35
+ #
36
+ # @return [ Field ] The generated field
35
37
  def field(name, options = {})
36
38
  access = name.to_s
37
39
  set_field(access, options)
@@ -101,9 +103,35 @@ module Mongoid #:nodoc
101
103
  # @param [ Hash ] options The hash of options.
102
104
  def set_field(name, options = {})
103
105
  meth = options.delete(:as) || name
104
- fields[name] = Field.new(name, options)
105
- create_accessors(name, meth, options)
106
- add_dirty_methods(name)
106
+ Field.new(name, options).tap do |field|
107
+ fields[name] = field
108
+ create_accessors(name, meth, options)
109
+ add_dirty_methods(name)
110
+ process_options(field)
111
+ end
112
+ end
113
+
114
+ # Run through all custom options stored in Mongoid::Field.options and
115
+ # execute the handler if the option is provided.
116
+ #
117
+ # @example
118
+ # Mongoid::Field.option :custom do
119
+ # puts "called"
120
+ # end
121
+ #
122
+ # field = Mongoid::Field.new(:test, :custom => true)
123
+ # Person.process_options(field)
124
+ # # => "called"
125
+ #
126
+ # @param [ Field ] field the field to process
127
+ def process_options(field)
128
+ options = field.options
129
+
130
+ Field.options.each do |option_name, handler|
131
+ if options.has_key?(option_name)
132
+ handler.call(self, field, options[option_name])
133
+ end
134
+ end
107
135
  end
108
136
 
109
137
  # Create the field accessors.
@@ -118,13 +146,20 @@ module Mongoid #:nodoc
118
146
  # @param [ Symbol ] meth The name of the accessor.
119
147
  # @param [ Hash ] options The options.
120
148
  def create_accessors(name, meth, options = {})
149
+ field = fields[name]
121
150
  generated_field_methods.module_eval do
122
- if [ Time, DateTime ].include?(options[:type])
123
- define_method(meth) { Time.get(read_attribute(name)) }
151
+ if field.cast_on_read?
152
+ define_method(meth) do
153
+ field.get(read_attribute(name))
154
+ end
124
155
  else
125
- define_method(meth) { read_attribute(name) }
156
+ define_method(meth) do
157
+ read_attribute(name)
158
+ end
159
+ end
160
+ define_method("#{meth}=") do |value|
161
+ write_attribute(name, value)
126
162
  end
127
- define_method("#{meth}=") { |value| write_attribute(name, value) }
128
163
  define_method("#{meth}?") do
129
164
  attr = read_attribute(name)
130
165
  (options[:type] == Boolean) ? attr == true : attr.present?
@@ -34,6 +34,11 @@ module Mongoid #:nodoc:
34
34
  find(:all, *args).count
35
35
  end
36
36
 
37
+ # Returns true if count is zero
38
+ def empty?
39
+ count == 0
40
+ end
41
+
37
42
  # Returns true if there are on document in database based on the
38
43
  # provided arguments.
39
44
  #
@@ -113,23 +118,6 @@ module Mongoid #:nodoc:
113
118
  find(:last, *args)
114
119
  end
115
120
 
116
- # Find all documents in paginated fashion given the supplied arguments.
117
- # If no parameters are passed just default to offset 0 and limit 20.
118
- #
119
- # Options:
120
- #
121
- # params: A +Hash+ of params to pass to the Criteria API.
122
- #
123
- # Example:
124
- #
125
- # <tt>Person.paginate(:conditions => { :field => "Test" }, :page => 1,
126
- # :per_page => 20)</tt>
127
- #
128
- # Returns paginated array of docs.
129
- def paginate(params = {})
130
- find(:all, params).paginate
131
- end
132
-
133
121
  protected
134
122
  # Find the first object or create/initialize it.
135
123
  def find_or(method, attrs = {}, &block)
@@ -72,7 +72,7 @@ module Mongoid #:nodoc:
72
72
  # @return [ Array<Object> ] The array of keys.
73
73
  def compose
74
74
  document.primary_key.collect do |key|
75
- document.attributes[key]
75
+ document.attributes[key.to_s]
76
76
  end.reject { |val| val.nil? }
77
77
  end
78
78
 
@@ -1,17 +1,17 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc
3
- module Inspection #:nodoc
3
+
4
+ # Contains the bahviour around inspecting documents via
5
+ # <tt>Object#inspect</tt>.
6
+ module Inspection
4
7
 
5
8
  # Returns the class name plus its attributes. If using dynamic fields will
6
9
  # include those as well.
7
10
  #
8
- # Example:
9
- #
10
- # <tt>person.inspect</tt>
11
- #
12
- # Returns:
11
+ # @example Inspect the document.
12
+ # person.inspect
13
13
  #
14
- # A nice pretty string to look at.
14
+ # @return [ String ] A nice pretty string to look at.
15
15
  def inspect
16
16
  inspection = []
17
17
  inspection.concat(inspect_fields).concat(inspect_dynamic_fields)
@@ -22,28 +22,24 @@ module Mongoid #:nodoc
22
22
 
23
23
  # Get an array of inspected fields for the document.
24
24
  #
25
- # Example:
26
- #
27
- # <tt>inspect_fields</tt>
28
- #
29
- # Returns:
25
+ # @example Inspect the defined fields.
26
+ # document.inspect_fields
30
27
  #
31
- # An array of pretty printed field values.
28
+ # @return [ String ] An array of pretty printed field values.
32
29
  def inspect_fields
33
30
  fields.map do |name, field|
34
- "#{name}: #{@attributes[name].inspect}"
35
- end
31
+ unless name == "_id"
32
+ "#{name}: #{@attributes[name].inspect}"
33
+ end
34
+ end.compact
36
35
  end
37
36
 
38
37
  # Get an array of inspected dynamic fields for the document.
39
38
  #
40
- # Example:
41
- #
42
- # <tt>inspect_dynamic_fields</tt>
43
- #
44
- # Returns:
39
+ # @example Inspect the dynamic fields.
40
+ # document.inspect_dynamic_fields
45
41
  #
46
- # An array of pretty printed dynamic field values.
42
+ # @return [ String ] An array of pretty printed dynamic field values.
47
43
  def inspect_dynamic_fields
48
44
  if Mongoid.allow_dynamic_fields
49
45
  keys = @attributes.keys - fields.keys - relations.keys - ["_id", "_type"]
@@ -18,8 +18,12 @@ module Mongoid #:nodoc:
18
18
  # @return [ true, false ] True if matches, false if not.
19
19
  def matches?(selector)
20
20
  selector.each_pair do |key, value|
21
- unless Strategies.matcher(self, key, value).matches?(value)
22
- return false
21
+ if value.is_a?(Hash)
22
+ value.each do |item|
23
+ return false unless Strategies.matcher(self, key, Hash[*item]).matches?(Hash[*item])
24
+ end
25
+ else
26
+ return false unless Strategies.matcher(self, key, value).matches?(value)
23
27
  end
24
28
  end
25
29
  return true
@@ -49,12 +49,12 @@ module Mongoid #:nodoc:
49
49
  # @since 2.0.0.rc.7
50
50
  def matcher(document, key, value)
51
51
  if value.is_a?(Hash)
52
- MATCHERS[value.keys.first].new(document.attributes[key])
52
+ MATCHERS[value.keys.first].new(document.attributes[key.to_s])
53
53
  else
54
54
  if key == "$or"
55
55
  Matchers::Or.new(value, document)
56
56
  else
57
- Default.new(document.attributes[key])
57
+ Default.new(document.attributes[key.to_s])
58
58
  end
59
59
  end
60
60
  end
@@ -129,7 +129,7 @@ module Mongoid #:nodoc:
129
129
  def valid_scope_name?(name)
130
130
  if !scopes[name] && respond_to?(name, true)
131
131
  Mongoid.logger.warn "Creating scope :#{name}. " \
132
- "Overwriting existing method #{self.name}.#{name}."
132
+ "Overwriting existing method #{self.name}.#{name}." if Mongoid.logger
133
133
  end
134
134
  end
135
135
  end
@@ -1,34 +1,65 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc:
3
+
4
+ # Mongoid observers hook into the lifecycle of documents.
3
5
  class Observer < ActiveModel::Observer
6
+
7
+ # Instantiate the new observer. Will add all child observers as well.
8
+ #
9
+ # @example Instantiate the observer.
10
+ # Mongoid::Observer.new
11
+ #
12
+ # @since 2.0.0
4
13
  def initialize
5
- super
6
- observed_descendants.each { |klass| add_observer!(klass) }
14
+ super and observed_descendants.each { |klass| add_observer!(klass) }
7
15
  end
8
16
 
9
17
  protected
10
18
 
19
+ # Get all the child observers.
20
+ #
21
+ # @example Get the children.
22
+ # observer.observed_descendants
23
+ #
24
+ # @return [ Array<Class> ] The children.
25
+ #
26
+ # @since 2.0.0
11
27
  def observed_descendants
12
28
  observed_classes.sum([]) { |klass| klass.descendants }
13
29
  end
14
30
 
31
+ # Adds the specified observer to the class.
32
+ #
33
+ # @example Add the observer.
34
+ # observer.add_observer!(Document)
35
+ #
36
+ # @param [ Class ] klass The child observer to add.
37
+ #
38
+ # @since 2.0.0
15
39
  def add_observer!(klass)
16
- super
17
- define_callbacks klass
40
+ super and define_callbacks(klass)
18
41
  end
19
42
 
43
+ # Defines all the callbacks for each observer of the model.
44
+ #
45
+ # @example Define all the callbacks.
46
+ # observer.define_callbacks(Document)
47
+ #
48
+ # @param [ Class ] klass The model to define them on.
49
+ #
50
+ # @since 2.0.0
20
51
  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)
52
+ tap do |observer|
53
+ observer_name = observer.class.name.underscore.gsub('/', '__')
54
+ Mongoid::Callbacks::CALLBACKS.each do |callback|
55
+ next unless respond_to?(callback)
56
+ callback_meth = :"_notify_#{observer_name}_for_#{callback}"
57
+ unless klass.respond_to?(callback_meth)
58
+ klass.send(:define_method, callback_meth) do |&block|
59
+ observer.send(callback, self, &block)
60
+ end
61
+ klass.send(callback, callback_meth)
30
62
  end
31
- klass.send(callback, callback_meth)
32
63
  end
33
64
  end
34
65
  end