mongoid 2.0.0.alpha → 2.0.0.beta.5

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 (216) hide show
  1. data/lib/mongoid.rb +11 -5
  2. data/lib/mongoid/associations.rb +112 -107
  3. data/lib/mongoid/associations/belongs_to_related.rb +2 -3
  4. data/lib/mongoid/associations/embedded_in.rb +12 -4
  5. data/lib/mongoid/associations/{embed_many.rb → embeds_many.rb} +101 -32
  6. data/lib/mongoid/associations/{embed_one.rb → embeds_one.rb} +10 -10
  7. data/lib/mongoid/associations/has_many_related.rb +51 -5
  8. data/lib/mongoid/associations/has_one_related.rb +9 -5
  9. data/lib/mongoid/associations/meta_data.rb +2 -1
  10. data/lib/mongoid/associations/options.rb +15 -6
  11. data/lib/mongoid/associations/proxy.rb +14 -21
  12. data/lib/mongoid/attributes.rb +34 -13
  13. data/lib/mongoid/callbacks.rb +1 -2
  14. data/lib/mongoid/collection.rb +4 -3
  15. data/lib/mongoid/collections.rb +41 -0
  16. data/lib/mongoid/collections/master.rb +3 -2
  17. data/lib/mongoid/collections/slaves.rb +3 -2
  18. data/lib/mongoid/components.rb +4 -1
  19. data/lib/mongoid/config.rb +163 -13
  20. data/lib/mongoid/contexts.rb +1 -2
  21. data/lib/mongoid/contexts/enumerable.rb +1 -1
  22. data/lib/mongoid/contexts/mongo.rb +1 -1
  23. data/lib/mongoid/contexts/paging.rb +10 -2
  24. data/lib/mongoid/criteria.rb +13 -22
  25. data/lib/mongoid/criterion/exclusion.rb +3 -3
  26. data/lib/mongoid/criterion/inclusion.rb +17 -0
  27. data/lib/mongoid/criterion/optional.rb +1 -1
  28. data/lib/mongoid/dirty.rb +253 -0
  29. data/lib/mongoid/document.rb +40 -85
  30. data/lib/mongoid/errors.rb +48 -1
  31. data/lib/mongoid/extensions.rb +11 -9
  32. data/lib/mongoid/extensions/big_decimal/conversions.rb +2 -2
  33. data/lib/mongoid/extensions/boolean/conversions.rb +8 -2
  34. data/lib/mongoid/extensions/date/conversions.rb +13 -4
  35. data/lib/mongoid/extensions/datetime/conversions.rb +1 -6
  36. data/lib/mongoid/extensions/float/conversions.rb +5 -1
  37. data/lib/mongoid/extensions/hash/assimilation.rb +12 -3
  38. data/lib/mongoid/extensions/hash/conversions.rb +34 -4
  39. data/lib/mongoid/extensions/integer/conversions.rb +5 -1
  40. data/lib/mongoid/extensions/nil/assimilation.rb +4 -0
  41. data/lib/mongoid/extensions/object/conversions.rb +3 -3
  42. data/lib/mongoid/extensions/string/conversions.rb +1 -1
  43. data/lib/mongoid/extensions/symbol/inflections.rb +5 -2
  44. data/lib/mongoid/extensions/time_conversions.rb +35 -0
  45. data/lib/mongoid/factory.rb +2 -1
  46. data/lib/mongoid/field.rb +15 -2
  47. data/lib/mongoid/fields.rb +1 -1
  48. data/lib/mongoid/identity.rb +3 -3
  49. data/lib/mongoid/indexes.rb +3 -3
  50. data/lib/mongoid/matchers.rb +1 -2
  51. data/lib/mongoid/memoization.rb +8 -2
  52. data/lib/mongoid/named_scope.rb +0 -5
  53. data/lib/mongoid/observable.rb +1 -1
  54. data/lib/mongoid/paths.rb +30 -22
  55. data/lib/mongoid/persistence.rb +218 -0
  56. data/lib/mongoid/persistence/command.rb +39 -0
  57. data/lib/mongoid/persistence/insert.rb +47 -0
  58. data/lib/mongoid/persistence/insert_embedded.rb +38 -0
  59. data/lib/mongoid/persistence/remove.rb +39 -0
  60. data/lib/mongoid/persistence/remove_all.rb +37 -0
  61. data/lib/mongoid/persistence/remove_embedded.rb +50 -0
  62. data/lib/mongoid/persistence/update.rb +63 -0
  63. data/lib/mongoid/railtie.rb +53 -0
  64. data/lib/mongoid/railties/database.rake +37 -0
  65. data/lib/mongoid/timestamps.rb +2 -2
  66. data/lib/mongoid/validations.rb +2 -2
  67. data/lib/mongoid/validations/associated.rb +2 -2
  68. data/lib/mongoid/validations/uniqueness.rb +13 -2
  69. data/lib/mongoid/version.rb +4 -0
  70. data/lib/mongoid/versioning.rb +3 -2
  71. data/lib/rails/generators/mongoid/config/config_generator.rb +41 -0
  72. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +24 -0
  73. data/lib/rails/generators/mongoid/model/model_generator.rb +24 -0
  74. data/lib/rails/generators/mongoid/model/templates/model.rb +15 -0
  75. data/lib/rails/generators/mongoid_generator.rb +61 -0
  76. metadata +76 -301
  77. data/.gitignore +0 -6
  78. data/.watchr +0 -29
  79. data/Rakefile +0 -52
  80. data/VERSION +0 -1
  81. data/caliper.yml +0 -4
  82. data/lib/mongoid/collections/mimic.rb +0 -46
  83. data/lib/mongoid/commands.rb +0 -161
  84. data/lib/mongoid/commands/create.rb +0 -19
  85. data/lib/mongoid/commands/delete.rb +0 -16
  86. data/lib/mongoid/commands/delete_all.rb +0 -23
  87. data/lib/mongoid/commands/deletion.rb +0 -18
  88. data/lib/mongoid/commands/destroy.rb +0 -17
  89. data/lib/mongoid/commands/destroy_all.rb +0 -23
  90. data/lib/mongoid/commands/save.rb +0 -29
  91. data/lib/mongoid/extensions/time/conversions.rb +0 -18
  92. data/mongoid.gemspec +0 -408
  93. data/perf/benchmark.rb +0 -77
  94. data/spec/integration/mongoid/associations_spec.rb +0 -340
  95. data/spec/integration/mongoid/attributes_spec.rb +0 -22
  96. data/spec/integration/mongoid/commands_spec.rb +0 -227
  97. data/spec/integration/mongoid/contexts/enumerable_spec.rb +0 -33
  98. data/spec/integration/mongoid/criteria_spec.rb +0 -272
  99. data/spec/integration/mongoid/document_spec.rb +0 -650
  100. data/spec/integration/mongoid/extensions_spec.rb +0 -22
  101. data/spec/integration/mongoid/finders_spec.rb +0 -119
  102. data/spec/integration/mongoid/inheritance_spec.rb +0 -137
  103. data/spec/integration/mongoid/named_scope_spec.rb +0 -46
  104. data/spec/models/address.rb +0 -39
  105. data/spec/models/animal.rb +0 -6
  106. data/spec/models/callbacks.rb +0 -18
  107. data/spec/models/comment.rb +0 -8
  108. data/spec/models/country_code.rb +0 -6
  109. data/spec/models/employer.rb +0 -5
  110. data/spec/models/game.rb +0 -7
  111. data/spec/models/inheritance.rb +0 -56
  112. data/spec/models/location.rb +0 -5
  113. data/spec/models/mixed_drink.rb +0 -4
  114. data/spec/models/name.rb +0 -13
  115. data/spec/models/namespacing.rb +0 -11
  116. data/spec/models/patient.rb +0 -4
  117. data/spec/models/person.rb +0 -99
  118. data/spec/models/pet.rb +0 -7
  119. data/spec/models/pet_owner.rb +0 -6
  120. data/spec/models/phone.rb +0 -7
  121. data/spec/models/post.rb +0 -15
  122. data/spec/models/translation.rb +0 -5
  123. data/spec/models/vet_visit.rb +0 -5
  124. data/spec/spec.opts +0 -3
  125. data/spec/spec_helper.rb +0 -31
  126. data/spec/unit/mongoid/associations/belongs_to_related_spec.rb +0 -145
  127. data/spec/unit/mongoid/associations/embed_many_spec.rb +0 -516
  128. data/spec/unit/mongoid/associations/embed_one_spec.rb +0 -282
  129. data/spec/unit/mongoid/associations/embedded_in_spec.rb +0 -193
  130. data/spec/unit/mongoid/associations/has_many_related_spec.rb +0 -418
  131. data/spec/unit/mongoid/associations/has_one_related_spec.rb +0 -179
  132. data/spec/unit/mongoid/associations/meta_data_spec.rb +0 -88
  133. data/spec/unit/mongoid/associations/options_spec.rb +0 -192
  134. data/spec/unit/mongoid/associations_spec.rb +0 -595
  135. data/spec/unit/mongoid/attributes_spec.rb +0 -507
  136. data/spec/unit/mongoid/callbacks_spec.rb +0 -55
  137. data/spec/unit/mongoid/collection_spec.rb +0 -187
  138. data/spec/unit/mongoid/collections/cyclic_iterator_spec.rb +0 -75
  139. data/spec/unit/mongoid/collections/master_spec.rb +0 -41
  140. data/spec/unit/mongoid/collections/mimic_spec.rb +0 -43
  141. data/spec/unit/mongoid/collections/slaves_spec.rb +0 -81
  142. data/spec/unit/mongoid/commands/create_spec.rb +0 -31
  143. data/spec/unit/mongoid/commands/delete_all_spec.rb +0 -58
  144. data/spec/unit/mongoid/commands/delete_spec.rb +0 -38
  145. data/spec/unit/mongoid/commands/destroy_all_spec.rb +0 -21
  146. data/spec/unit/mongoid/commands/destroy_spec.rb +0 -51
  147. data/spec/unit/mongoid/commands/save_spec.rb +0 -107
  148. data/spec/unit/mongoid/commands_spec.rb +0 -270
  149. data/spec/unit/mongoid/config_spec.rb +0 -172
  150. data/spec/unit/mongoid/contexts/enumerable_spec.rb +0 -421
  151. data/spec/unit/mongoid/contexts/mongo_spec.rb +0 -682
  152. data/spec/unit/mongoid/contexts_spec.rb +0 -25
  153. data/spec/unit/mongoid/criteria_spec.rb +0 -824
  154. data/spec/unit/mongoid/criterion/complex_spec.rb +0 -19
  155. data/spec/unit/mongoid/criterion/exclusion_spec.rb +0 -91
  156. data/spec/unit/mongoid/criterion/inclusion_spec.rb +0 -219
  157. data/spec/unit/mongoid/criterion/optional_spec.rb +0 -319
  158. data/spec/unit/mongoid/cursor_spec.rb +0 -74
  159. data/spec/unit/mongoid/deprecation_spec.rb +0 -24
  160. data/spec/unit/mongoid/document_spec.rb +0 -818
  161. data/spec/unit/mongoid/errors_spec.rb +0 -103
  162. data/spec/unit/mongoid/extensions/array/accessors_spec.rb +0 -50
  163. data/spec/unit/mongoid/extensions/array/assimilation_spec.rb +0 -24
  164. data/spec/unit/mongoid/extensions/array/conversions_spec.rb +0 -35
  165. data/spec/unit/mongoid/extensions/array/parentization_spec.rb +0 -20
  166. data/spec/unit/mongoid/extensions/big_decimal/conversions_spec.rb +0 -22
  167. data/spec/unit/mongoid/extensions/binary/conversions_spec.rb +0 -22
  168. data/spec/unit/mongoid/extensions/boolean/conversions_spec.rb +0 -49
  169. data/spec/unit/mongoid/extensions/date/conversions_spec.rb +0 -102
  170. data/spec/unit/mongoid/extensions/datetime/conversions_spec.rb +0 -67
  171. data/spec/unit/mongoid/extensions/float/conversions_spec.rb +0 -61
  172. data/spec/unit/mongoid/extensions/hash/accessors_spec.rb +0 -184
  173. data/spec/unit/mongoid/extensions/hash/assimilation_spec.rb +0 -46
  174. data/spec/unit/mongoid/extensions/hash/conversions_spec.rb +0 -21
  175. data/spec/unit/mongoid/extensions/hash/criteria_helpers_spec.rb +0 -17
  176. data/spec/unit/mongoid/extensions/hash/scoping_spec.rb +0 -14
  177. data/spec/unit/mongoid/extensions/integer/conversions_spec.rb +0 -61
  178. data/spec/unit/mongoid/extensions/nil/assimilation_spec.rb +0 -24
  179. data/spec/unit/mongoid/extensions/object/conversions_spec.rb +0 -57
  180. data/spec/unit/mongoid/extensions/proc/scoping_spec.rb +0 -34
  181. data/spec/unit/mongoid/extensions/string/conversions_spec.rb +0 -17
  182. data/spec/unit/mongoid/extensions/string/inflections_spec.rb +0 -208
  183. data/spec/unit/mongoid/extensions/symbol/inflections_spec.rb +0 -91
  184. data/spec/unit/mongoid/extensions/time/conversions_spec.rb +0 -70
  185. data/spec/unit/mongoid/extras_spec.rb +0 -102
  186. data/spec/unit/mongoid/factory_spec.rb +0 -31
  187. data/spec/unit/mongoid/field_spec.rb +0 -143
  188. data/spec/unit/mongoid/fields_spec.rb +0 -181
  189. data/spec/unit/mongoid/finders_spec.rb +0 -404
  190. data/spec/unit/mongoid/identity_spec.rb +0 -109
  191. data/spec/unit/mongoid/indexes_spec.rb +0 -93
  192. data/spec/unit/mongoid/javascript_spec.rb +0 -48
  193. data/spec/unit/mongoid/matchers/all_spec.rb +0 -27
  194. data/spec/unit/mongoid/matchers/default_spec.rb +0 -27
  195. data/spec/unit/mongoid/matchers/exists_spec.rb +0 -56
  196. data/spec/unit/mongoid/matchers/gt_spec.rb +0 -39
  197. data/spec/unit/mongoid/matchers/gte_spec.rb +0 -49
  198. data/spec/unit/mongoid/matchers/in_spec.rb +0 -27
  199. data/spec/unit/mongoid/matchers/lt_spec.rb +0 -39
  200. data/spec/unit/mongoid/matchers/lte_spec.rb +0 -49
  201. data/spec/unit/mongoid/matchers/ne_spec.rb +0 -27
  202. data/spec/unit/mongoid/matchers/nin_spec.rb +0 -27
  203. data/spec/unit/mongoid/matchers/size_spec.rb +0 -27
  204. data/spec/unit/mongoid/matchers_spec.rb +0 -329
  205. data/spec/unit/mongoid/memoization_spec.rb +0 -75
  206. data/spec/unit/mongoid/named_scope_spec.rb +0 -123
  207. data/spec/unit/mongoid/observable_spec.rb +0 -46
  208. data/spec/unit/mongoid/paths_spec.rb +0 -124
  209. data/spec/unit/mongoid/scope_spec.rb +0 -240
  210. data/spec/unit/mongoid/state_spec.rb +0 -83
  211. data/spec/unit/mongoid/timestamps_spec.rb +0 -25
  212. data/spec/unit/mongoid/validations/associated_spec.rb +0 -103
  213. data/spec/unit/mongoid/validations/uniqueness_spec.rb +0 -47
  214. data/spec/unit/mongoid/validations_spec.rb +0 -190
  215. data/spec/unit/mongoid/versioning_spec.rb +0 -41
  216. data/spec/unit/mongoid_spec.rb +0 -46
@@ -15,11 +15,10 @@ module Mongoid
15
15
  #
16
16
  # <tt>Contexts.context_for(criteria)</tt>
17
17
  def self.context_for(criteria)
18
- if criteria.klass.embedded
18
+ if criteria.klass.embedded?
19
19
  return Contexts::Enumerable.new(criteria)
20
20
  end
21
21
  Contexts::Mongo.new(criteria)
22
22
  end
23
-
24
23
  end
25
24
  end
@@ -6,7 +6,7 @@ module Mongoid #:nodoc:
6
6
  attr_reader :criteria
7
7
 
8
8
  delegate :blank?, :empty?, :first, :last, :to => :execute
9
- delegate :documents, :options, :selector, :to => :criteria
9
+ delegate :klass, :documents, :options, :selector, :to => :criteria
10
10
 
11
11
  # Return aggregation counts of the grouped documents. This will count by
12
12
  # the first field provided in the fields array.
@@ -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 && Mongoid.persist_types
136
+ if klass.hereditary
137
137
  criteria.in(:_type => criteria.klass._types)
138
138
  end
139
139
  criteria.enslave if klass.enslaved?
@@ -6,12 +6,20 @@ module Mongoid #:nodoc:
6
6
  #
7
7
  # Example:
8
8
  #
9
- # <tt>context.paginate</tt>
9
+ # <tt>context.paginate(:page => 6, :per_page => 25)</tt>
10
10
  #
11
11
  # Returns:
12
12
  #
13
13
  # A collection of documents paginated.
14
- def paginate
14
+ # All previous <tt>limit</tt> and <tt>skip</tt> call will be ignored.
15
+ def paginate(pager_options={})
16
+ if pager_options[:per_page]
17
+ options[:limit] = pager_options[:per_page].to_i
18
+ if pager_options[:page]
19
+ options[:skip] = (pager_options[:page].to_i - 1) * pager_options[:per_page].to_i
20
+ end
21
+ end
22
+
15
23
  @collection ||= execute(true)
16
24
  WillPaginate::Collection.create(page, per_page, count) do |pager|
17
25
  pager.replace(@collection.to_a)
@@ -28,25 +28,9 @@ module Mongoid #:nodoc:
28
28
  attr_reader :collection, :ids, :klass, :options, :selector
29
29
  attr_accessor :documents
30
30
 
31
- delegate \
32
- :aggregate,
33
- :avg,
34
- :blank?,
35
- :count,
36
- :distinct,
37
- :empty?,
38
- :execute,
39
- :first,
40
- :group,
41
- :id_criteria,
42
- :last,
43
- :max,
44
- :min,
45
- :one,
46
- :page,
47
- :paginate,
48
- :per_page,
49
- :sum, :to => :context
31
+ delegate :aggregate, :avg, :blank?, :count, :distinct, :empty?,
32
+ :execute, :first, :group, :id_criteria, :last, :max,
33
+ :min, :one, :page, :paginate, :per_page, :sum, :to => :context
50
34
 
51
35
  # Concatinate the criteria with another enumerable. If the other is a
52
36
  # +Criteria+ then it needs to get the collection from it.
@@ -190,14 +174,14 @@ module Mongoid #:nodoc:
190
174
  klass = args[0]
191
175
  params = args[1] || {}
192
176
  unless params.is_a?(Hash)
193
- return new(klass).id_criteria(params)
177
+ return klass.criteria.id_criteria(params)
194
178
  end
195
179
  conditions = params.delete(:conditions) || {}
196
180
  if conditions.include?(:id)
197
181
  conditions[:_id] = conditions[:id]
198
182
  conditions.delete(:id)
199
183
  end
200
- return new(klass).where(conditions).extras(params)
184
+ return klass.criteria.where(conditions).extras(params)
201
185
  end
202
186
 
203
187
  protected
@@ -233,7 +217,14 @@ module Mongoid #:nodoc:
233
217
  #
234
218
  # <tt>criteria.update_selector({ :field => "value" }, "$in")</tt>
235
219
  def update_selector(attributes, operator)
236
- attributes.each { |key, value| @selector[key] = { operator => value } }; self
220
+ attributes.each do |key, value|
221
+ unless @selector[key]
222
+ @selector[key] = { operator => value }
223
+ else
224
+ new_value = @selector[key].values.first + value
225
+ @selector[key] = { operator => new_value }
226
+ end
227
+ end; self
237
228
  end
238
229
  end
239
230
  end
@@ -30,7 +30,7 @@ module Mongoid #:nodoc:
30
30
  #
31
31
  # Options:
32
32
  #
33
- # exclusions: A +Hash+ where the key is the field name and the value is an
33
+ # attributes: A +Hash+ where the key is the field name and the value is an
34
34
  # +Array+ of values that none can match.
35
35
  #
36
36
  # Example:
@@ -40,8 +40,8 @@ module Mongoid #:nodoc:
40
40
  # <tt>criteria.not_in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
41
41
  #
42
42
  # Returns: <tt>self</tt>
43
- def not_in(exclusions)
44
- exclusions.each { |key, value| @selector[key] = { "$nin" => value } }; self
43
+ def not_in(attributes)
44
+ update_selector(attributes, "$nin")
45
45
  end
46
46
 
47
47
  # Adds a criterion to the +Criteria+ that specifies the fields that will
@@ -64,6 +64,23 @@ module Mongoid #:nodoc:
64
64
  end
65
65
  alias :any_in :in
66
66
 
67
+ # Adds a criterion to the +Criteria+ that specifies values to do
68
+ # geospacial searches by. The field must be indexed with the "2d" option.
69
+ #
70
+ # Options:
71
+ #
72
+ # attributes: A +Hash+ where the keys are the field names and the values are
73
+ # +Arrays+ of [latitude, longitude] pairs.
74
+ #
75
+ # Example:
76
+ #
77
+ # <tt>criteria.near(:field1 => [30, -44])</tt>
78
+ #
79
+ # Returns: <tt>self</tt>
80
+ def near(attributes = {})
81
+ update_selector(attributes, "$near")
82
+ end
83
+
67
84
  # Adds a criterion to the +Criteria+ that specifies values that must
68
85
  # be matched in order to return results. This is similar to a SQL "WHERE"
69
86
  # clause. This is the actual selector that will be provided to MongoDB,
@@ -62,7 +62,7 @@ module Mongoid #:nodoc:
62
62
  #
63
63
  # Options:
64
64
  #
65
- # object_id: A +String+ representation of a <tt>Mongo::ObjectID</tt>
65
+ # object_id: A +String+ representation of a <tt>BSON::ObjectID</tt>
66
66
  #
67
67
  # Example:
68
68
  #
@@ -0,0 +1,253 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Dirty #:nodoc:
4
+ extend ActiveSupport::Concern
5
+ module InstanceMethods #:nodoc:
6
+ # Gets the changes for a specific field.
7
+ #
8
+ # Example:
9
+ #
10
+ # person = Person.new(:title => "Sir")
11
+ # person.title = "Madam"
12
+ # person.attribute_change("title") # [ "Sir", "Madam" ]
13
+ #
14
+ # Returns:
15
+ #
16
+ # An +Array+ containing the old and new values.
17
+ def attribute_change(name)
18
+ modifications[name]
19
+ end
20
+
21
+ # Determines if a specific field has chaged.
22
+ #
23
+ # Example:
24
+ #
25
+ # person = Person.new(:title => "Sir")
26
+ # person.title = "Madam"
27
+ # person.attribute_changed?("title") # true
28
+ #
29
+ # Returns:
30
+ #
31
+ # +true+ if changed, +false+ if not.
32
+ def attribute_changed?(name)
33
+ modifications.include?(name)
34
+ end
35
+
36
+ # Gets the old value for a specific field.
37
+ #
38
+ # Example:
39
+ #
40
+ # person = Person.new(:title => "Sir")
41
+ # person.title = "Madam"
42
+ # person.attribute_was("title") # "Sir"
43
+ #
44
+ # Returns:
45
+ #
46
+ # The old field value.
47
+ def attribute_was(name)
48
+ change = modifications[name]
49
+ change ? change[0] : nil
50
+ end
51
+
52
+ # Gets the names of all the fields that have changed in the document.
53
+ #
54
+ # Example:
55
+ #
56
+ # person = Person.new(:title => "Sir")
57
+ # person.title = "Madam"
58
+ # person.changed # returns [ "title" ]
59
+ #
60
+ # Returns:
61
+ #
62
+ # An +Array+ of changed field names.
63
+ def changed
64
+ modifications.keys
65
+ end
66
+
67
+ # Alerts to whether the document has been modified or not.
68
+ #
69
+ # Example:
70
+ #
71
+ # person = Person.new(:title => "Sir")
72
+ # person.title = "Madam"
73
+ # person.changed? # returns true
74
+ #
75
+ # Returns:
76
+ #
77
+ # +true+ if changed, +false+ if not.
78
+ def changed?
79
+ !modifications.empty?
80
+ end
81
+
82
+ # Gets all the modifications that have happened to the object as a +Hash+
83
+ # with the keys being the names of the fields, and the values being an
84
+ # +Array+ with the old value and new value.
85
+ #
86
+ # Example:
87
+ #
88
+ # person = Person.new(:title => "Sir")
89
+ # person.title = "Madam"
90
+ # person.changes # returns { "title" => [ "Sir", "Madam" ] }
91
+ #
92
+ # Returns:
93
+ #
94
+ # A +Hash+ of changes.
95
+ def changes
96
+ modifications
97
+ end
98
+
99
+ # Call this method after save, so the changes can be properly switched.
100
+ #
101
+ # Example:
102
+ #
103
+ # <tt>person.move_changes</tt>
104
+ def move_changes
105
+ @previous_modifications = modifications.dup
106
+ @modifications = {}
107
+ end
108
+
109
+ # Gets all the new values for each of the changed fields, to be passed to
110
+ # a MongoDB $set modifier.
111
+ #
112
+ # Example:
113
+ #
114
+ # person = Person.new(:title => "Sir")
115
+ # person.title = "Madam"
116
+ # person.setters # returns { "title" => "Madam" }
117
+ #
118
+ # Returns:
119
+ #
120
+ # A +Hash+ of new values.
121
+ def setters
122
+ modifications.inject({}) do |sets, (field, changes)|
123
+ key = embedded? ? "#{_position}.#{field}" : field
124
+ sets[key] = changes[1]; sets
125
+ end
126
+ end
127
+
128
+ # Gets all the modifications that have happened to the object before the
129
+ # object was saved.
130
+ #
131
+ # Example:
132
+ #
133
+ # person = Person.new(:title => "Sir")
134
+ # person.title = "Madam"
135
+ # person.save!
136
+ # person.previous_changes # returns { "title" => [ "Sir", "Madam" ] }
137
+ #
138
+ # Returns:
139
+ #
140
+ # A +Hash+ of changes before save.
141
+ def previous_changes
142
+ @previous_modifications
143
+ end
144
+
145
+ # Resets a changed field back to its old value.
146
+ #
147
+ # Example:
148
+ #
149
+ # person = Person.new(:title => "Sir")
150
+ # person.title = "Madam"
151
+ # person.reset_attribute!("title")
152
+ # person.title # "Sir"
153
+ #
154
+ # Returns:
155
+ #
156
+ # The old field value.
157
+ def reset_attribute!(name)
158
+ value = attribute_was(name)
159
+ if value
160
+ @attributes[name] = value
161
+ modifications.delete(name)
162
+ end
163
+ end
164
+
165
+ # Sets up the modifications hash. This occurs just after the document is
166
+ # instantiated.
167
+ #
168
+ # Example:
169
+ #
170
+ # <tt>document.setup_notifications</tt>
171
+ def setup_modifications
172
+ @accessed ||= {}
173
+ @modifications ||= {}
174
+ @previous_modifications ||= {}
175
+ end
176
+
177
+ # Reset all modifications for the document. This will wipe all the marked
178
+ # changes, but not reset the values.
179
+ #
180
+ # Example:
181
+ #
182
+ # <tt>document.reset_modifications</tt>
183
+ def reset_modifications
184
+ @accessed = {}
185
+ @modifications = {}
186
+ end
187
+
188
+ protected
189
+
190
+ # Audit the original value for a field that can be modified in place.
191
+ #
192
+ # Example:
193
+ #
194
+ # <tt>person.accessed("aliases", [ "007" ])</tt>
195
+ def accessed(name, value)
196
+ @accessed ||= {}
197
+ @accessed[name] = value.dup if (value.is_a?(Array) || value.is_a?(Hash)) && !@accessed.has_key?(name)
198
+ value
199
+ end
200
+
201
+ # Get all normal modifications plus in place potential changes.
202
+ #
203
+ # Example:
204
+ #
205
+ # <tt>person.modifications</tt>
206
+ #
207
+ # Returns:
208
+ #
209
+ # All changes to the document.
210
+ def modifications
211
+ @accessed.each_pair do |field, value|
212
+ current = @attributes[field]
213
+ @modifications[field] = [ value, current ] if current != value
214
+ end
215
+ @accessed.clear
216
+ @modifications
217
+ end
218
+
219
+ # Audit the change of a field's value.
220
+ #
221
+ # Example:
222
+ #
223
+ # <tt>person.modify("name", "Jack", "John")</tt>
224
+ def modify(name, old_value, new_value)
225
+ @attributes[name] = new_value
226
+ if @modifications && (old_value != new_value)
227
+ original = @modifications[name].first if @modifications[name]
228
+ @modifications[name] = [ (original || old_value), new_value ]
229
+ end
230
+ end
231
+ end
232
+
233
+ module ClassMethods #:nodoc:
234
+ # Add the dynamic dirty methods. These are custom methods defined on a
235
+ # field by field basis that wrap the dirty attribute methods.
236
+ #
237
+ # Example:
238
+ #
239
+ # person = Person.new(:title => "Sir")
240
+ # person.title = "Madam"
241
+ # person.title_change # [ "Sir", "Madam" ]
242
+ # person.title_changed? # true
243
+ # person.title_was # "Sir"
244
+ # person.reset_title!
245
+ def add_dirty_methods(name)
246
+ define_method("#{name}_change") { attribute_change(name) }
247
+ define_method("#{name}_changed?") { attribute_changed?(name) }
248
+ define_method("#{name}_was") { attribute_was(name) }
249
+ define_method("reset_#{name}!") { reset_attribute!(name) }
250
+ end
251
+ end
252
+ end
253
+ end
@@ -5,16 +5,13 @@ module Mongoid #:nodoc:
5
5
  included do
6
6
  include Mongoid::Components
7
7
 
8
- cattr_accessor :_collection, :collection_name, :embedded, :primary_key, :hereditary
9
-
10
- self.embedded = false
8
+ cattr_accessor :primary_key, :hereditary
11
9
  self.hereditary = false
12
- self.collection_name = self.name.collectionize
13
10
 
14
11
  attr_accessor :association_name, :_parent
15
12
  attr_reader :new_record
16
13
 
17
- delegate :collection, :db, :embedded, :primary_key, :to => "self.class"
14
+ delegate :db, :primary_key, :to => "self.class"
18
15
  end
19
16
 
20
17
  module ClassMethods
@@ -23,17 +20,6 @@ module Mongoid #:nodoc:
23
20
  collection.db
24
21
  end
25
22
 
26
- # Returns the collection associated with this +Document+. If the
27
- # document is embedded, there will be no collection associated
28
- # with it.
29
- #
30
- # Returns: <tt>Mongo::Collection</tt>
31
- def collection
32
- raise Errors::InvalidCollection.new(self) if embedded
33
- self._collection ||= Mongoid::Collection.new(self, self.collection_name)
34
- add_indexes; self._collection
35
- end
36
-
37
23
  # Perform default behavior but mark the hierarchy as being hereditary.
38
24
  def inherited(subclass)
39
25
  super(subclass)
@@ -51,6 +37,7 @@ module Mongoid #:nodoc:
51
37
  if attributes["_id"] || allocating
52
38
  document = allocate
53
39
  document.instance_variable_set(:@attributes, attributes)
40
+ document.setup_modifications
54
41
  return document
55
42
  else
56
43
  return new(attrs)
@@ -70,17 +57,7 @@ module Mongoid #:nodoc:
70
57
  # end
71
58
  def key(*fields)
72
59
  self.primary_key = fields
73
- before_save :identify
74
- end
75
-
76
- # Macro for setting the collection name to store in.
77
- #
78
- # Example:
79
- #
80
- # <tt>Person.store_in :populdation</tt>
81
- def store_in(name)
82
- self.collection_name = name.to_s
83
- self._collection = Mongoid::Collection.new(self, name.to_s)
60
+ set_callback :save, :before, :identify
84
61
  end
85
62
 
86
63
  # Returns all types to query for when using this class as the base.
@@ -91,7 +68,6 @@ module Mongoid #:nodoc:
91
68
  # return the list of subclassses for an object
92
69
  def subclasses_of(*superclasses) #:nodoc:
93
70
  subclasses = []
94
-
95
71
  superclasses.each do |sup|
96
72
  ObjectSpace.each_object(class << sup; self; end) do |k|
97
73
  if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
@@ -99,18 +75,16 @@ module Mongoid #:nodoc:
99
75
  end
100
76
  end
101
77
  end
102
-
103
78
  subclasses
104
79
  end
105
80
  end
106
81
 
107
82
  module InstanceMethods
108
- # Performs equality checking on the attributes. For now we chack against
109
- # all attributes excluding timestamps on the object.
83
+ # Performs equality checking on the document ids. For more robust
84
+ # equality checking please override this method.
110
85
  def ==(other)
111
86
  return false unless other.is_a?(Document)
112
- attributes.except(:modified_at).except(:created_at) ==
113
- other.attributes.except(:modified_at).except(:created_at)
87
+ id == other.id
114
88
  end
115
89
 
116
90
  # Delegates to ==
@@ -124,6 +98,15 @@ module Mongoid #:nodoc:
124
98
  id.hash
125
99
  end
126
100
 
101
+ # Is inheritance in play here?
102
+ #
103
+ # Returns:
104
+ #
105
+ # <tt>true</tt> if inheritance used, <tt>false</tt> if not.
106
+ def hereditary?
107
+ !!self.hereditary
108
+ end
109
+
127
110
  # Introduces a child object into the +Document+ object graph. This will
128
111
  # set up the relationships between the parent and child and update the
129
112
  # attributes of the parent +Document+.
@@ -157,24 +140,27 @@ module Mongoid #:nodoc:
157
140
  # an empty +Hash+.
158
141
  #
159
142
  # If a primary key is defined, the document's id will be set to that key,
160
- # otherwise it will be set to a fresh +Mongo::ObjectID+ string.
143
+ # otherwise it will be set to a fresh +BSON::ObjectID+ string.
161
144
  #
162
145
  # Options:
163
146
  #
164
147
  # attrs: The attributes +Hash+ to set up the document with.
165
148
  def initialize(attrs = nil)
166
- @attributes = {}
149
+ @attributes = default_attributes
167
150
  process(attrs)
168
- @attributes = attributes_with_defaults(@attributes)
169
- @new_record = true if id.nil?
151
+ @new_record = true
170
152
  document = yield self if block_given?
171
153
  identify
172
154
  end
173
155
 
174
156
  # Returns the class name plus its attributes.
175
157
  def inspect
176
- attrs = fields.map { |name, field| "#{name}: #{@attributes[name].inspect}" } * ", "
177
- "#<#{self.class.name} _id: #{id}, #{attrs}>"
158
+ attrs = fields.map { |name, field| "#{name}: #{@attributes[name].inspect}" }
159
+ if Mongoid.allow_dynamic_fields
160
+ dynamic_keys = @attributes.keys - fields.keys - associations.keys - ["_id", "_type"]
161
+ attrs += dynamic_keys.map { |name| "#{name}: #{@attributes[name].inspect}" }
162
+ end
163
+ "#<#{self.class.name} _id: #{id}, #{attrs * ', '}>"
178
164
  end
179
165
 
180
166
  # Notify observers of an update.
@@ -211,8 +197,12 @@ module Mongoid #:nodoc:
211
197
 
212
198
  # Reloads the +Document+ attributes from the database.
213
199
  def reload
214
- @attributes = collection.find_one(:_id => id)
215
- self
200
+ reloaded = collection.find_one(:_id => id)
201
+ if Mongoid.raise_not_found_error
202
+ raise Errors::DocumentNotFound.new(self.class, id) if reloaded.nil?
203
+ end
204
+ @attributes = {}.merge(reloaded || {})
205
+ self.associations.keys.each { |association_name| unmemoize(association_name) }; self
216
206
  end
217
207
 
218
208
  # Remove a child document from this parent +Document+. Will reset the
@@ -236,39 +226,9 @@ module Mongoid #:nodoc:
236
226
  [ self ]
237
227
  end
238
228
 
239
- # Return this document as a JSON string. Nothing special is required here
240
- # since Mongoid bubbles up all the child associations to the parent
241
- # attribute +Hash+ using observers throughout the +Document+ lifecycle.
242
- #
243
- # Example:
244
- #
245
- # <tt>person.to_json</tt>
246
- def to_json(options = nil)
247
- attributes.to_json(options)
248
- end
249
-
250
- # Return an object to be encoded into a JSON string.
251
- # Used by Rails 3's object->JSON chain to create JSON
252
- # in a backend-agnostic way
253
- #
254
- # Example:
255
- #
256
- # <tt>person.as_json</tt>
257
- def as_json(options = nil)
258
- attributes
259
- end
260
-
261
- # Return this document as an object to be encoded as JSON,
262
- # with any particular items modified on a per-encoder basis.
263
- # Nothing special is required here since Mongoid bubbles up
264
- # all the child associations to the parent attribute +Hash+
265
- # using observers throughout the +Document+ lifecycle.
266
- #
267
- # Example:
268
- #
269
- # <tt>person.encode_json(encoder)</tt>
270
- def encode_json(encoder)
271
- attributes
229
+ # Returns nil if document is new, or an array of primary keys if not.
230
+ def to_key
231
+ new_record? ? nil : [ id ]
272
232
  end
273
233
 
274
234
  # Returns the id of the Document, used in Rails compatibility.
@@ -287,20 +247,15 @@ module Mongoid #:nodoc:
287
247
  #
288
248
  # This will also cause the observing +Document+ to notify it's parent if
289
249
  # there is any.
290
- def update(child, clear = false)
250
+ def observe(child, clear = false)
291
251
  name = child.association_name
292
252
  attrs = child.instance_variable_get(:@attributes)
293
- clear ? @attributes.delete(name) : @attributes.insert(name, attrs)
294
- notify
295
- end
296
-
297
- protected
298
- # apply default values to attributes - calling procs as required
299
- def attributes_with_defaults(attributes = {})
300
- default_values = defaults.merge(attributes)
301
- default_values.each_pair do |key, val|
302
- default_values[key] = val.call if val.respond_to?(:call)
253
+ if clear
254
+ @attributes.delete(name)
255
+ else
256
+ @attributes.insert(name, attrs) unless @attributes[name] && @attributes[name].include?(attrs)
303
257
  end
258
+ notify
304
259
  end
305
260
  end
306
261
  end