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

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