mongoid 2.0.0.alpha → 2.0.0.beta.5

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