mongoid 2.0.1 → 2.0.2

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