mongoid 2.0.0.beta.15 → 2.0.0.beta.16

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 (66) hide show
  1. data/lib/config/locales/en.yml +40 -0
  2. data/lib/config/locales/es.yml +41 -0
  3. data/lib/config/locales/fr.yml +42 -0
  4. data/lib/config/locales/it.yml +39 -0
  5. data/lib/config/locales/pl.yml +39 -0
  6. data/lib/config/locales/pt.yml +40 -0
  7. data/lib/config/locales/sv.yml +40 -0
  8. data/lib/mongoid.rb +7 -2
  9. data/lib/mongoid/associations.rb +16 -9
  10. data/lib/mongoid/associations/embedded_in.rb +11 -0
  11. data/lib/mongoid/associations/embeds_many.rb +28 -2
  12. data/lib/mongoid/associations/embeds_one.rb +18 -2
  13. data/lib/mongoid/associations/proxy.rb +28 -1
  14. data/lib/mongoid/associations/referenced_in.rb +10 -0
  15. data/lib/mongoid/associations/references_many.rb +10 -7
  16. data/lib/mongoid/associations/references_many_as_array.rb +29 -0
  17. data/lib/mongoid/associations/references_one.rb +9 -1
  18. data/lib/mongoid/attributes.rb +13 -3
  19. data/lib/mongoid/callbacks.rb +1 -0
  20. data/lib/mongoid/collections.rb +1 -1
  21. data/lib/mongoid/components.rb +1 -0
  22. data/lib/mongoid/config.rb +16 -1
  23. data/lib/mongoid/contexts/enumerable.rb +28 -1
  24. data/lib/mongoid/contexts/enumerable/sort.rb +43 -0
  25. data/lib/mongoid/contexts/mongo.rb +13 -1
  26. data/lib/mongoid/criteria.rb +4 -2
  27. data/lib/mongoid/criterion/inclusion.rb +22 -1
  28. data/lib/mongoid/criterion/optional.rb +14 -39
  29. data/lib/mongoid/criterion/selector.rb +65 -0
  30. data/lib/mongoid/document.rb +5 -11
  31. data/lib/mongoid/errors.rb +10 -130
  32. data/lib/mongoid/errors/document_not_found.rb +29 -0
  33. data/lib/mongoid/errors/invalid_collection.rb +19 -0
  34. data/lib/mongoid/errors/invalid_database.rb +20 -0
  35. data/lib/mongoid/errors/invalid_field.rb +19 -0
  36. data/lib/mongoid/errors/invalid_options.rb +16 -0
  37. data/lib/mongoid/errors/invalid_type.rb +26 -0
  38. data/lib/mongoid/errors/mongoid_error.rb +27 -0
  39. data/lib/mongoid/errors/too_many_nested_attribute_records.rb +21 -0
  40. data/lib/mongoid/errors/unsupported_version.rb +21 -0
  41. data/lib/mongoid/errors/validations.rb +22 -0
  42. data/lib/mongoid/extensions/hash/assimilation.rb +1 -1
  43. data/lib/mongoid/extensions/hash/criteria_helpers.rb +5 -3
  44. data/lib/mongoid/extensions/object/conversions.rb +5 -1
  45. data/lib/mongoid/extensions/objectid/conversions.rb +43 -1
  46. data/lib/mongoid/field.rb +13 -6
  47. data/lib/mongoid/finders.rb +1 -1
  48. data/lib/mongoid/hierarchy.rb +9 -4
  49. data/lib/mongoid/identity.rb +2 -2
  50. data/lib/mongoid/indexes.rb +1 -1
  51. data/lib/mongoid/matchers/default.rb +1 -1
  52. data/lib/mongoid/modifiers.rb +24 -0
  53. data/lib/mongoid/modifiers/command.rb +18 -0
  54. data/lib/mongoid/modifiers/inc.rb +24 -0
  55. data/lib/mongoid/persistence/command.rb +2 -10
  56. data/lib/mongoid/persistence/remove_all.rb +1 -1
  57. data/lib/mongoid/railtie.rb +2 -0
  58. data/lib/mongoid/railties/database.rake +102 -11
  59. data/lib/mongoid/railties/document.rb +12 -0
  60. data/lib/mongoid/safe.rb +13 -0
  61. data/lib/mongoid/safety.rb +12 -0
  62. data/lib/mongoid/validations.rb +0 -4
  63. data/lib/mongoid/version.rb +1 -1
  64. data/lib/mongoid/versioning.rb +11 -1
  65. metadata +55 -28
  66. data/lib/mongoid/validations/locale/en.yml +0 -5
@@ -58,9 +58,14 @@ module Mongoid #:nodoc:
58
58
  #
59
59
  # A new target document.
60
60
  def nested_build(attributes, options = nil)
61
- unless @target.blank? && options[:update_only]
61
+ options ||= {}
62
+ _destroy = Boolean.set(attributes.delete('_destroy'))
63
+ if options[:allow_destroy] && _destroy
64
+ @target = nil
65
+ elsif @target.present? || !options[:update_only]
62
66
  @target.write_attributes(attributes)
63
- end; @target
67
+ end
68
+ target
64
69
  end
65
70
 
66
71
  class << self
@@ -89,6 +94,17 @@ module Mongoid #:nodoc:
89
94
  child.assimilate(parent, options)
90
95
  new(parent, options, child.is_a?(Hash) ? nil : child)
91
96
  end
97
+
98
+ # Validate the options passed to the embeds one macro, to encapsulate
99
+ # the behavior in this class instead of the associations module.
100
+ #
101
+ # Options:
102
+ #
103
+ # options: Thank you captain obvious.
104
+ def validate_options(options = {})
105
+ check_dependent_not_allowed!(options)
106
+ check_inverse_not_allowed!(options)
107
+ end
92
108
  end
93
109
  end
94
110
  end
@@ -3,7 +3,7 @@ module Mongoid #:nodoc
3
3
  module Associations #:nodoc
4
4
  class Proxy #:nodoc
5
5
  instance_methods.each do |method|
6
- undef_method(method) unless method =~ /(^__|^nil\?$|^send$|^object_id$|^extend$)/
6
+ undef_method(method) unless method =~ /(^__|^send$|^object_id$|^extend$)/
7
7
  end
8
8
  attr_reader \
9
9
  :options,
@@ -28,6 +28,33 @@ module Mongoid #:nodoc
28
28
  @foreign_key = options.foreign_key
29
29
  extends(options)
30
30
  end
31
+
32
+ protected
33
+ class << self
34
+ def check_dependent_not_allowed!(options)
35
+ if options.has_key?(:dependent)
36
+ raise Errors::InvalidOptions.new(
37
+ "dependent_only_references_one_or_many", {}
38
+ )
39
+ end
40
+ end
41
+
42
+ def check_inverse_not_allowed!(options)
43
+ if options.has_key?(:inverse_of)
44
+ raise Errors::InvalidOptions.new(
45
+ "association_cant_have_inverse_of", {}
46
+ )
47
+ end
48
+ end
49
+
50
+ def check_inverse_must_be_defined!(options)
51
+ unless options.has_key?(:inverse_of)
52
+ raise Errors::InvalidOptions.new(
53
+ "embedded_in_must_have_inverse_of", {}
54
+ )
55
+ end
56
+ end
57
+ end
31
58
  end
32
59
  end
33
60
  end
@@ -54,6 +54,16 @@ module Mongoid #:nodoc:
54
54
  document.send("#{options.foreign_key}=", target ? target.id : nil)
55
55
  new(document, options, target)
56
56
  end
57
+
58
+ # Validate the options passed to the referenced in macro, to encapsulate
59
+ # the behavior in this class instead of the associations module.
60
+ #
61
+ # Options:
62
+ #
63
+ # options: Thank you captain obvious.
64
+ def validate_options(options = {})
65
+ check_dependent_not_allowed!(options)
66
+ end
57
67
  end
58
68
  end
59
69
  end
@@ -227,19 +227,22 @@ module Mongoid #:nodoc:
227
227
  protected
228
228
  def determine_name(document, options)
229
229
  target = document.class
230
-
231
230
  if (inverse = options.inverse_of) && inverse.is_a?(Array)
232
231
  inverse = [*inverse].detect { |name| target.respond_to?(name) }
233
232
  end
234
-
235
233
  if !inverse
236
- association = options.klass.associations.values.detect do |metadata|
237
- metadata.options.klass == target
238
- end
239
- inverse = association.name if association
234
+ association = detect_association(target, options, false)
235
+ association = detect_association(target, options, true) if association.blank?
236
+ inferred = association.name if association
240
237
  end
238
+ inverse || inferred || target.to_s.underscore
239
+ end
241
240
 
242
- inverse || target.to_s.underscore
241
+ def detect_association(target, options, with_class_name = false)
242
+ association = options.klass.associations.values.detect do |metadata|
243
+ metadata.options.klass == target &&
244
+ (with_class_name ? true : metadata.options[:class_name].nil?)
245
+ end
243
246
  end
244
247
  end
245
248
  end
@@ -34,6 +34,7 @@ module Mongoid #:nodoc:
34
34
  @target << object
35
35
  object.save unless @parent.new_record?
36
36
  end
37
+ @parent.save unless @parent.new_record?
37
38
  end
38
39
 
39
40
  alias :concat :<<
@@ -50,6 +51,34 @@ module Mongoid #:nodoc:
50
51
  push(document); document
51
52
  end
52
53
 
54
+ # Destroy all the associated objects.
55
+ #
56
+ # Example:
57
+ #
58
+ # <tt>person.posts.destroy_all</tt>
59
+ #
60
+ # Returns:
61
+ #
62
+ # The number of objects destroyed.
63
+ def destroy_all(conditions = {})
64
+ removed = query.call.destroy_all(:conditions => conditions)
65
+ reset; removed
66
+ end
67
+
68
+ # Delete all the associated objects.
69
+ #
70
+ # Example:
71
+ #
72
+ # <tt>person.posts.delete_all</tt>
73
+ #
74
+ # Returns:
75
+ #
76
+ # The number of objects deleted.
77
+ def delete_all(conditions = {})
78
+ removed = query.call.delete_all(:conditions => conditions)
79
+ reset; removed
80
+ end
81
+
53
82
  protected
54
83
 
55
84
  # Find the inverse key for the supplied document.
@@ -61,7 +61,15 @@ module Mongoid #:nodoc:
61
61
  #
62
62
  # A new target document.
63
63
  def nested_build(attributes, options = nil)
64
- build(attributes) unless @target.blank? && options[:update_only]
64
+ options ||= {}
65
+ _destroy = Boolean.set(attributes.delete('_destroy'))
66
+ if options[:allow_destroy] && _destroy
67
+ @target.destroy
68
+ @target = nil
69
+ elsif @target.present? || !options[:update_only]
70
+ build(attributes)
71
+ end
72
+ @target
65
73
  end
66
74
 
67
75
  class << self
@@ -29,6 +29,11 @@ module Mongoid #:nodoc:
29
29
  end
30
30
  end
31
31
 
32
+ # Override respond_to? so it responds properly for dynamic attributes
33
+ def respond_to?(sym)
34
+ (Mongoid.allow_dynamic_fields && @attributes && @attributes.has_key?(sym.to_s)) || super
35
+ end
36
+
32
37
  # Process the provided attributes casting them to their proper values if a
33
38
  # field exists for them on the +Document+. This will be limited to only the
34
39
  # attributes provided in the suppied +Hash+ so that no extra nil values get
@@ -123,8 +128,7 @@ module Mongoid #:nodoc:
123
128
  # there is any.
124
129
  def write_attribute(name, value)
125
130
  access = name.to_s
126
- typed_value = fields.has_key?(access) ? fields[access].set(value) : value
127
- modify(access, @attributes[access], typed_value)
131
+ modify(access, @attributes[access], typed_value_for(access, value))
128
132
  notify if !id.blank? && new_record?
129
133
  end
130
134
 
@@ -152,11 +156,17 @@ module Mongoid #:nodoc:
152
156
  alias :attributes= :write_attributes
153
157
 
154
158
  protected
159
+
160
+ # Return the typecast value for a field.
161
+ def typed_value_for(key, value)
162
+ fields.has_key?(key) ? fields[key].set(value) : value
163
+ end
164
+
155
165
  # apply default values to attributes - calling procs as required
156
166
  def default_attributes
157
167
  default_values = defaults
158
168
  default_values.each_pair do |key, val|
159
- default_values[key] = val.call if val.respond_to?(:call)
169
+ default_values[key] = typed_value_for(key, val.call) if val.respond_to?(:call)
160
170
  end
161
171
  default_values || {}
162
172
  end
@@ -9,6 +9,7 @@ module Mongoid #:nodoc:
9
9
  define_model_callbacks \
10
10
  :create,
11
11
  :destroy,
12
+ :initialize,
12
13
  :save,
13
14
  :update,
14
15
  :validation
@@ -56,7 +56,7 @@ module Mongoid #:nodoc
56
56
  #
57
57
  # Example:
58
58
  #
59
- # <tt>Person.store_in :populdation</tt>
59
+ # <tt>Person.store_in :population</tt>
60
60
  def store_in(name)
61
61
  self.collection_name = name.to_s
62
62
  set_collection
@@ -24,6 +24,7 @@ module Mongoid #:nodoc
24
24
  include Mongoid::Keys
25
25
  include Mongoid::Matchers
26
26
  include Mongoid::Memoization
27
+ include Mongoid::Modifiers
27
28
  include Mongoid::Paths
28
29
  include Mongoid::Persistence
29
30
  include Mongoid::Safety
@@ -169,6 +169,21 @@ module Mongoid #:nodoc
169
169
  end
170
170
  end
171
171
 
172
+ # Adds a new I18n locale file to the load path
173
+ #
174
+ # Example:
175
+ #
176
+ # Add portuguese locale
177
+ # <tt>Mongoid::config.add_language('pt')</tt>
178
+ #
179
+ # Adds all available languages
180
+ # <tt>Mongoid::Config.add_language('*')</tt>
181
+ def add_language(language_code = nil)
182
+ Dir[File.join(File.dirname(__FILE__), "..", "config", "locales", "#{language_code}.yml")].each do |file|
183
+ I18n.load_path << File.expand_path(file)
184
+ end
185
+ end
186
+
172
187
  # Convenience method for connecting to the master database after forking a
173
188
  # new process.
174
189
  #
@@ -177,7 +192,7 @@ module Mongoid #:nodoc
177
192
  # <tt>Mongoid.reconnect!</tt>
178
193
  def reconnect!(now = true)
179
194
  if now
180
- master.connection.connect_to_master
195
+ master.connection.connect
181
196
  else
182
197
  # We set a @reconnect flag so that #master knows to reconnect the next
183
198
  # time the connection is accessed.
@@ -1,4 +1,7 @@
1
1
  # encoding: utf-8
2
+
3
+ require 'mongoid/contexts/enumerable/sort'
4
+
2
5
  module Mongoid #:nodoc:
3
6
  module Contexts #:nodoc:
4
7
  class Enumerable
@@ -56,7 +59,7 @@ module Mongoid #:nodoc:
56
59
  #
57
60
  # An +Array+ of documents that matched the selector.
58
61
  def execute(paginating = false)
59
- limit(filter) || []
62
+ limit(sort(filter)) || []
60
63
  end
61
64
 
62
65
  # Groups the documents by the first field supplied in the field options.
@@ -115,6 +118,18 @@ module Mongoid #:nodoc:
115
118
  # The first document in the +Array+
116
119
  alias :one :first
117
120
 
121
+ # Get one document and tell the criteria to skip this record on
122
+ # successive calls.
123
+ #
124
+ # Returns:
125
+ #
126
+ # The first document in the +Array+
127
+ def shift
128
+ document = first
129
+ criteria.skip((options[:skip] || 0) + 1)
130
+ document
131
+ end
132
+
118
133
  # Get the sum of the field values for all the documents.
119
134
  #
120
135
  # Returns:
@@ -148,9 +163,21 @@ module Mongoid #:nodoc:
148
163
  return documents.slice(skip, limit)
149
164
  elsif limit
150
165
  return documents.first(limit)
166
+ elsif skip
167
+ return documents.slice(skip..-1)
151
168
  end
152
169
  documents
153
170
  end
171
+
172
+ # Sorts the result set if sort options have been set.
173
+ def sort(documents)
174
+ return documents if options[:sort].blank?
175
+ documents.sort_by do |document|
176
+ options[:sort].map do |key, direction|
177
+ Sort.new(document.read_attribute(key), direction)
178
+ end
179
+ end
180
+ end
154
181
  end
155
182
  end
156
183
  end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Contexts #:nodoc:
4
+ class Enumerable
5
+ class Sort
6
+ attr_reader :value, :direction
7
+
8
+ # Create a new sorting object. This requires a value and a sort
9
+ # direction of +:asc+ or +:desc+.
10
+ def initialize(value, direction)
11
+ @value = value
12
+ @direction = direction
13
+ end
14
+
15
+ # Return +true+ if the direction is +:asc+, otherwise false.
16
+ def ascending?
17
+ direction == :asc
18
+ end
19
+
20
+ # Compare two +Sort+ objects against each other, taking into
21
+ # consideration the direction of the sorting.
22
+ def <=>(other)
23
+ cmp = compare(value, other.value)
24
+ ascending? ? cmp : cmp * -1
25
+ end
26
+
27
+ private
28
+
29
+ # Compare two values allowing for nil values.
30
+ def compare(a, b)
31
+ case
32
+ when a.nil?
33
+ b.nil? ? 0 : 1
34
+ when b.nil?
35
+ -1
36
+ else
37
+ a <=> b
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -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 && !criteria.selector.keys.include?(:_type)
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?
@@ -225,6 +225,18 @@ module Mongoid #:nodoc:
225
225
 
226
226
  alias :first :one
227
227
 
228
+ # Return the first result for the +Context+ and skip it
229
+ # for successive calls.
230
+ #
231
+ # Returns:
232
+ #
233
+ # The first document in the collection.
234
+ def shift
235
+ document = first
236
+ criteria.skip((options[:skip] || 0) + 1)
237
+ document
238
+ end
239
+
228
240
  # Sum the context.
229
241
  #
230
242
  # This will take the internally built selector and options
@@ -3,6 +3,7 @@ require "mongoid/criterion/complex"
3
3
  require "mongoid/criterion/exclusion"
4
4
  require "mongoid/criterion/inclusion"
5
5
  require "mongoid/criterion/optional"
6
+ require "mongoid/criterion/selector"
6
7
 
7
8
  module Mongoid #:nodoc:
8
9
  # The +Criteria+ class is the core object needed in Mongoid to retrieve
@@ -30,7 +31,7 @@ module Mongoid #:nodoc:
30
31
 
31
32
  delegate :aggregate, :avg, :blank?, :count, :distinct, :empty?,
32
33
  :execute, :first, :group, :id_criteria, :last, :max,
33
- :min, :one, :page, :paginate, :per_page, :sum, :to => :context
34
+ :min, :one, :page, :paginate, :per_page, :shift, :sum, :to => :context
34
35
 
35
36
  # Concatinate the criteria with another enumerable. If the other is a
36
37
  # +Criteria+ then it needs to get the collection from it.
@@ -116,7 +117,8 @@ module Mongoid #:nodoc:
116
117
  # type: One of :all, :first:, or :last
117
118
  # klass: The class to execute on.
118
119
  def initialize(klass)
119
- @selector, @options, @klass, @documents = {}, {}, klass, []
120
+ @selector = Mongoid::Criterion::Selector.new(klass)
121
+ @options, @klass, @documents = {}, klass, []
120
122
  end
121
123
 
122
124
  # Merges another object into this +Criteria+. The other object may be a
@@ -43,6 +43,27 @@ module Mongoid #:nodoc:
43
43
  where(selector)
44
44
  end
45
45
 
46
+ # Adds a criterion to the +Criteria+ that specifies a set of expressions
47
+ # to match if any of them return true. This is a $or query in MongoDB and
48
+ # is similar to a SQL OR. This is named #any_of and aliased "or" for
49
+ # readability.
50
+ #
51
+ # Options:
52
+ #
53
+ # selector: Multiple +Hash+ expressions that any can match.
54
+ #
55
+ # Example:
56
+ #
57
+ # <tt>criteria.any_of({ :field1 => "value" }, { :field2 => "value2" })</tt>
58
+ #
59
+ # Returns: <tt>self</tt>
60
+ def any_of(*args)
61
+ criterion = @selector["$or"] || []
62
+ @selector["$or"] = criterion.concat(args)
63
+ self
64
+ end
65
+ alias :or :any_of
66
+
46
67
  # Adds a criterion to the +Criteria+ that specifies values where any can
47
68
  # be matched in order to return results. This is similar to an SQL "IN"
48
69
  # clause. The MongoDB conditional operator that will be used is "$in".
@@ -89,7 +110,7 @@ module Mongoid #:nodoc:
89
110
  #
90
111
  # Options:
91
112
  #
92
- # selectior: A +Hash+ that must match the attributes of the +Document+.
113
+ # selector: A +Hash+ that must match the attributes of the +Document+.
93
114
  #
94
115
  # Example:
95
116
  #