mongoid 1.2.14 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (200) hide show
  1. data/lib/mongoid.rb +10 -3
  2. data/lib/mongoid/associations.rb +133 -97
  3. data/lib/mongoid/associations/belongs_to_related.rb +2 -3
  4. data/lib/mongoid/associations/{belongs_to.rb → embedded_in.rb} +14 -6
  5. data/lib/mongoid/associations/{has_many.rb → embeds_many.rb} +89 -31
  6. data/lib/mongoid/associations/{has_one.rb → embeds_one.rb} +8 -7
  7. data/lib/mongoid/associations/has_many_related.rb +52 -7
  8. data/lib/mongoid/associations/has_one_related.rb +8 -4
  9. data/lib/mongoid/associations/meta_data.rb +2 -1
  10. data/lib/mongoid/associations/options.rb +6 -1
  11. data/lib/mongoid/associations/proxy.rb +14 -21
  12. data/lib/mongoid/attributes.rb +27 -12
  13. data/lib/mongoid/collection.rb +4 -3
  14. data/lib/mongoid/collections.rb +41 -0
  15. data/lib/mongoid/collections/master.rb +3 -2
  16. data/lib/mongoid/collections/slaves.rb +3 -2
  17. data/lib/mongoid/components.rb +21 -19
  18. data/lib/mongoid/concern.rb +31 -0
  19. data/lib/mongoid/config.rb +117 -12
  20. data/lib/mongoid/contexts.rb +1 -1
  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/criterion/inclusion.rb +17 -0
  25. data/lib/mongoid/criterion/optional.rb +1 -1
  26. data/lib/mongoid/dirty.rb +253 -0
  27. data/lib/mongoid/document.rb +81 -52
  28. data/lib/mongoid/errors.rb +32 -1
  29. data/lib/mongoid/extensions.rb +11 -9
  30. data/lib/mongoid/extensions/big_decimal/conversions.rb +2 -2
  31. data/lib/mongoid/extensions/boolean/conversions.rb +8 -2
  32. data/lib/mongoid/extensions/date/conversions.rb +13 -4
  33. data/lib/mongoid/extensions/datetime/conversions.rb +1 -6
  34. data/lib/mongoid/extensions/float/conversions.rb +5 -1
  35. data/lib/mongoid/extensions/hash/assimilation.rb +12 -3
  36. data/lib/mongoid/extensions/hash/conversions.rb +34 -4
  37. data/lib/mongoid/extensions/integer/conversions.rb +5 -1
  38. data/lib/mongoid/extensions/nil/assimilation.rb +4 -0
  39. data/lib/mongoid/extensions/object/conversions.rb +1 -1
  40. data/lib/mongoid/extensions/string/conversions.rb +1 -1
  41. data/lib/mongoid/extensions/symbol/inflections.rb +1 -1
  42. data/lib/mongoid/extensions/time_conversions.rb +35 -0
  43. data/lib/mongoid/extras.rb +6 -9
  44. data/lib/mongoid/factory.rb +2 -1
  45. data/lib/mongoid/field.rb +9 -2
  46. data/lib/mongoid/fields.rb +1 -0
  47. data/lib/mongoid/identity.rb +3 -3
  48. data/lib/mongoid/indexes.rb +3 -3
  49. data/lib/mongoid/memoization.rb +8 -2
  50. data/lib/mongoid/named_scope.rb +0 -3
  51. data/lib/mongoid/observable.rb +30 -0
  52. data/lib/mongoid/paths.rb +62 -0
  53. data/lib/mongoid/persistence.rb +222 -0
  54. data/lib/mongoid/persistence/command.rb +39 -0
  55. data/lib/mongoid/persistence/insert.rb +50 -0
  56. data/lib/mongoid/persistence/insert_embedded.rb +38 -0
  57. data/lib/mongoid/persistence/remove.rb +39 -0
  58. data/lib/mongoid/persistence/remove_all.rb +37 -0
  59. data/lib/mongoid/persistence/remove_embedded.rb +50 -0
  60. data/lib/mongoid/persistence/update.rb +63 -0
  61. data/lib/mongoid/state.rb +28 -21
  62. data/lib/mongoid/timestamps.rb +5 -8
  63. data/lib/mongoid/version.rb +4 -0
  64. data/lib/mongoid/versioning.rb +6 -7
  65. metadata +81 -300
  66. data/.gitignore +0 -6
  67. data/.watchr +0 -29
  68. data/Rakefile +0 -53
  69. data/VERSION +0 -1
  70. data/caliper.yml +0 -4
  71. data/lib/mongoid/collections/mimic.rb +0 -46
  72. data/lib/mongoid/commands.rb +0 -174
  73. data/lib/mongoid/commands/create.rb +0 -21
  74. data/lib/mongoid/commands/delete.rb +0 -16
  75. data/lib/mongoid/commands/delete_all.rb +0 -23
  76. data/lib/mongoid/commands/deletion.rb +0 -18
  77. data/lib/mongoid/commands/destroy.rb +0 -19
  78. data/lib/mongoid/commands/destroy_all.rb +0 -23
  79. data/lib/mongoid/commands/save.rb +0 -27
  80. data/lib/mongoid/extensions/time/conversions.rb +0 -18
  81. data/mongoid.gemspec +0 -395
  82. data/perf/benchmark.rb +0 -77
  83. data/spec/integration/mongoid/associations_spec.rb +0 -340
  84. data/spec/integration/mongoid/attributes_spec.rb +0 -22
  85. data/spec/integration/mongoid/commands_spec.rb +0 -218
  86. data/spec/integration/mongoid/contexts/enumerable_spec.rb +0 -33
  87. data/spec/integration/mongoid/criteria_spec.rb +0 -272
  88. data/spec/integration/mongoid/document_spec.rb +0 -593
  89. data/spec/integration/mongoid/extensions_spec.rb +0 -26
  90. data/spec/integration/mongoid/finders_spec.rb +0 -119
  91. data/spec/integration/mongoid/inheritance_spec.rb +0 -137
  92. data/spec/integration/mongoid/named_scope_spec.rb +0 -46
  93. data/spec/models/address.rb +0 -39
  94. data/spec/models/animal.rb +0 -6
  95. data/spec/models/callbacks.rb +0 -18
  96. data/spec/models/comment.rb +0 -8
  97. data/spec/models/country_code.rb +0 -6
  98. data/spec/models/employer.rb +0 -5
  99. data/spec/models/game.rb +0 -7
  100. data/spec/models/inheritance.rb +0 -56
  101. data/spec/models/location.rb +0 -5
  102. data/spec/models/mixed_drink.rb +0 -4
  103. data/spec/models/name.rb +0 -13
  104. data/spec/models/namespacing.rb +0 -11
  105. data/spec/models/patient.rb +0 -4
  106. data/spec/models/person.rb +0 -99
  107. data/spec/models/pet.rb +0 -7
  108. data/spec/models/pet_owner.rb +0 -6
  109. data/spec/models/phone.rb +0 -7
  110. data/spec/models/post.rb +0 -15
  111. data/spec/models/translation.rb +0 -5
  112. data/spec/models/vet_visit.rb +0 -5
  113. data/spec/spec.opts +0 -3
  114. data/spec/spec_helper.rb +0 -31
  115. data/spec/unit/mongoid/associations/belongs_to_related_spec.rb +0 -145
  116. data/spec/unit/mongoid/associations/belongs_to_spec.rb +0 -193
  117. data/spec/unit/mongoid/associations/has_many_related_spec.rb +0 -420
  118. data/spec/unit/mongoid/associations/has_many_spec.rb +0 -519
  119. data/spec/unit/mongoid/associations/has_one_related_spec.rb +0 -179
  120. data/spec/unit/mongoid/associations/has_one_spec.rb +0 -282
  121. data/spec/unit/mongoid/associations/meta_data_spec.rb +0 -88
  122. data/spec/unit/mongoid/associations/options_spec.rb +0 -192
  123. data/spec/unit/mongoid/associations_spec.rb +0 -595
  124. data/spec/unit/mongoid/attributes_spec.rb +0 -507
  125. data/spec/unit/mongoid/callbacks_spec.rb +0 -55
  126. data/spec/unit/mongoid/collection_spec.rb +0 -187
  127. data/spec/unit/mongoid/collections/cyclic_iterator_spec.rb +0 -75
  128. data/spec/unit/mongoid/collections/master_spec.rb +0 -41
  129. data/spec/unit/mongoid/collections/mimic_spec.rb +0 -43
  130. data/spec/unit/mongoid/collections/slaves_spec.rb +0 -81
  131. data/spec/unit/mongoid/commands/create_spec.rb +0 -30
  132. data/spec/unit/mongoid/commands/delete_all_spec.rb +0 -58
  133. data/spec/unit/mongoid/commands/delete_spec.rb +0 -38
  134. data/spec/unit/mongoid/commands/destroy_all_spec.rb +0 -23
  135. data/spec/unit/mongoid/commands/destroy_spec.rb +0 -50
  136. data/spec/unit/mongoid/commands/save_spec.rb +0 -105
  137. data/spec/unit/mongoid/commands_spec.rb +0 -275
  138. data/spec/unit/mongoid/config_spec.rb +0 -172
  139. data/spec/unit/mongoid/contexts/enumerable_spec.rb +0 -421
  140. data/spec/unit/mongoid/contexts/mongo_spec.rb +0 -682
  141. data/spec/unit/mongoid/contexts_spec.rb +0 -25
  142. data/spec/unit/mongoid/criteria_spec.rb +0 -824
  143. data/spec/unit/mongoid/criterion/complex_spec.rb +0 -19
  144. data/spec/unit/mongoid/criterion/exclusion_spec.rb +0 -91
  145. data/spec/unit/mongoid/criterion/inclusion_spec.rb +0 -219
  146. data/spec/unit/mongoid/criterion/optional_spec.rb +0 -319
  147. data/spec/unit/mongoid/cursor_spec.rb +0 -74
  148. data/spec/unit/mongoid/deprecation_spec.rb +0 -24
  149. data/spec/unit/mongoid/document_spec.rb +0 -952
  150. data/spec/unit/mongoid/errors_spec.rb +0 -103
  151. data/spec/unit/mongoid/extensions/array/accessors_spec.rb +0 -50
  152. data/spec/unit/mongoid/extensions/array/assimilation_spec.rb +0 -24
  153. data/spec/unit/mongoid/extensions/array/conversions_spec.rb +0 -35
  154. data/spec/unit/mongoid/extensions/array/parentization_spec.rb +0 -20
  155. data/spec/unit/mongoid/extensions/big_decimal/conversions_spec.rb +0 -22
  156. data/spec/unit/mongoid/extensions/binary/conversions_spec.rb +0 -22
  157. data/spec/unit/mongoid/extensions/boolean/conversions_spec.rb +0 -49
  158. data/spec/unit/mongoid/extensions/date/conversions_spec.rb +0 -102
  159. data/spec/unit/mongoid/extensions/datetime/conversions_spec.rb +0 -70
  160. data/spec/unit/mongoid/extensions/float/conversions_spec.rb +0 -61
  161. data/spec/unit/mongoid/extensions/hash/accessors_spec.rb +0 -184
  162. data/spec/unit/mongoid/extensions/hash/assimilation_spec.rb +0 -46
  163. data/spec/unit/mongoid/extensions/hash/conversions_spec.rb +0 -21
  164. data/spec/unit/mongoid/extensions/hash/criteria_helpers_spec.rb +0 -17
  165. data/spec/unit/mongoid/extensions/hash/scoping_spec.rb +0 -14
  166. data/spec/unit/mongoid/extensions/integer/conversions_spec.rb +0 -61
  167. data/spec/unit/mongoid/extensions/nil/assimilation_spec.rb +0 -24
  168. data/spec/unit/mongoid/extensions/object/conversions_spec.rb +0 -57
  169. data/spec/unit/mongoid/extensions/proc/scoping_spec.rb +0 -34
  170. data/spec/unit/mongoid/extensions/string/conversions_spec.rb +0 -17
  171. data/spec/unit/mongoid/extensions/string/inflections_spec.rb +0 -208
  172. data/spec/unit/mongoid/extensions/symbol/inflections_spec.rb +0 -91
  173. data/spec/unit/mongoid/extensions/time/conversions_spec.rb +0 -70
  174. data/spec/unit/mongoid/extras_spec.rb +0 -102
  175. data/spec/unit/mongoid/factory_spec.rb +0 -31
  176. data/spec/unit/mongoid/field_spec.rb +0 -143
  177. data/spec/unit/mongoid/fields_spec.rb +0 -181
  178. data/spec/unit/mongoid/finders_spec.rb +0 -404
  179. data/spec/unit/mongoid/identity_spec.rb +0 -109
  180. data/spec/unit/mongoid/indexes_spec.rb +0 -93
  181. data/spec/unit/mongoid/javascript_spec.rb +0 -48
  182. data/spec/unit/mongoid/matchers/all_spec.rb +0 -27
  183. data/spec/unit/mongoid/matchers/default_spec.rb +0 -27
  184. data/spec/unit/mongoid/matchers/exists_spec.rb +0 -56
  185. data/spec/unit/mongoid/matchers/gt_spec.rb +0 -39
  186. data/spec/unit/mongoid/matchers/gte_spec.rb +0 -49
  187. data/spec/unit/mongoid/matchers/in_spec.rb +0 -27
  188. data/spec/unit/mongoid/matchers/lt_spec.rb +0 -39
  189. data/spec/unit/mongoid/matchers/lte_spec.rb +0 -49
  190. data/spec/unit/mongoid/matchers/ne_spec.rb +0 -27
  191. data/spec/unit/mongoid/matchers/nin_spec.rb +0 -27
  192. data/spec/unit/mongoid/matchers/size_spec.rb +0 -27
  193. data/spec/unit/mongoid/matchers_spec.rb +0 -329
  194. data/spec/unit/mongoid/memoization_spec.rb +0 -75
  195. data/spec/unit/mongoid/named_scope_spec.rb +0 -123
  196. data/spec/unit/mongoid/scope_spec.rb +0 -240
  197. data/spec/unit/mongoid/state_spec.rb +0 -83
  198. data/spec/unit/mongoid/timestamps_spec.rb +0 -25
  199. data/spec/unit/mongoid/versioning_spec.rb +0 -41
  200. data/spec/unit/mongoid_spec.rb +0 -46
@@ -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)
@@ -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
@@ -1,23 +1,17 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc:
3
3
  module Document
4
- def self.included(base)
5
- base.class_eval do
6
- include Components
7
- include InstanceMethods
8
- extend ClassMethods
4
+ extend ActiveSupport::Concern
5
+ include Mongoid::Components
6
+ included do
7
+ include Mongoid::Components
9
8
 
10
- cattr_accessor :_collection, :collection_name, :embedded, :primary_key, :hereditary
9
+ cattr_accessor :primary_key, :hereditary
10
+ self.hereditary = false
11
11
 
12
- self.embedded = false
13
- self.hereditary = false
14
- self.collection_name = self.name.collectionize
12
+ attr_accessor :association_name, :_parent
15
13
 
16
- attr_accessor :association_name, :_parent
17
- attr_reader :new_record
18
-
19
- delegate :collection, :db, :embedded, :primary_key, :to => "self.class"
20
- end
14
+ delegate :db, :primary_key, :to => "self.class"
21
15
  end
22
16
 
23
17
  module ClassMethods
@@ -26,17 +20,6 @@ module Mongoid #:nodoc:
26
20
  collection.db
27
21
  end
28
22
 
29
- # Returns the collection associated with this +Document+. If the
30
- # document is embedded, there will be no collection associated
31
- # with it.
32
- #
33
- # Returns: <tt>Mongo::Collection</tt>
34
- def collection
35
- raise Errors::InvalidCollection.new(self) if embedded
36
- self._collection ||= Mongoid::Collection.new(self, self.collection_name)
37
- add_indexes; self._collection
38
- end
39
-
40
23
  # Perform default behavior but mark the hierarchy as being hereditary.
41
24
  def inherited(subclass)
42
25
  super(subclass)
@@ -63,6 +46,7 @@ module Mongoid #:nodoc:
63
46
  if attributes["_id"] || allocating
64
47
  document = allocate
65
48
  document.instance_variable_set(:@attributes, attributes)
49
+ document.setup_modifications
66
50
  return document
67
51
  else
68
52
  return new(attrs)
@@ -85,30 +69,31 @@ module Mongoid #:nodoc:
85
69
  before_save :identify
86
70
  end
87
71
 
88
- # Macro for setting the collection name to store in.
89
- #
90
- # Example:
91
- #
92
- # <tt>Person.store_in :populdation</tt>
93
- def store_in(name)
94
- self.collection_name = name.to_s
95
- self._collection = Mongoid::Collection.new(self, name.to_s)
96
- end
97
-
98
72
  # Returns all types to query for when using this class as the base.
99
73
  def _types
100
74
  @_type ||= (self.subclasses + [ self.name ])
101
75
  end
102
76
 
77
+ # return the list of subclassses for an object
78
+ def subclasses_of(*superclasses) #:nodoc:
79
+ subclasses = []
80
+ superclasses.each do |sup|
81
+ ObjectSpace.each_object(class << sup; self; end) do |k|
82
+ if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
83
+ subclasses << k
84
+ end
85
+ end
86
+ end
87
+ subclasses
88
+ end
103
89
  end
104
90
 
105
91
  module InstanceMethods
106
- # Performs equality checking on the attributes. For now we chack against
107
- # all attributes excluding timestamps on the object.
92
+ # Performs equality checking on the document ids. For more robust
93
+ # equality checking please override this method.
108
94
  def ==(other)
109
95
  return false unless other.is_a?(Document)
110
- attributes.except(:modified_at).except(:created_at) ==
111
- other.attributes.except(:modified_at).except(:created_at)
96
+ id == other.id
112
97
  end
113
98
 
114
99
  # Delegates to ==
@@ -122,6 +107,15 @@ module Mongoid #:nodoc:
122
107
  id.hash
123
108
  end
124
109
 
110
+ # Is inheritance in play here?
111
+ #
112
+ # Returns:
113
+ #
114
+ # <tt>true</tt> if inheritance used, <tt>false</tt> if not.
115
+ def hereditary?
116
+ !!self.hereditary
117
+ end
118
+
125
119
  # Introduces a child object into the +Document+ object graph. This will
126
120
  # set up the relationships between the parent and child and update the
127
121
  # attributes of the parent +Document+.
@@ -155,33 +149,35 @@ module Mongoid #:nodoc:
155
149
  # an empty +Hash+.
156
150
  #
157
151
  # If a primary key is defined, the document's id will be set to that key,
158
- # otherwise it will be set to a fresh +Mongo::ObjectID+ string.
152
+ # otherwise it will be set to a fresh +BSON::ObjectID+ string.
159
153
  #
160
154
  # Options:
161
155
  #
162
156
  # attrs: The attributes +Hash+ to set up the document with.
163
157
  def initialize(attrs = nil)
164
- @attributes = {}
158
+ @attributes = default_attributes
165
159
  process(attrs)
166
- @attributes = attributes_with_defaults(@attributes)
167
- @new_record = true if id.nil?
160
+ @new_record = true
168
161
  document = yield self if block_given?
169
162
  identify
170
163
  end
171
164
 
172
165
  # Returns the class name plus its attributes.
173
166
  def inspect
174
- attrs = fields.map { |name, field| "#{name}: #{@attributes[name].inspect}" } * ", "
175
- "#<#{self.class.name} _id: #{id}, #{attrs}>"
167
+ attrs = fields.map { |name, field| "#{name}: #{@attributes[name].inspect}" }
168
+ if Mongoid.allow_dynamic_fields
169
+ dynamic_keys = @attributes.keys - fields.keys - associations.keys - ["_id", "_type"]
170
+ attrs += dynamic_keys.map { |name| "#{name}: #{@attributes[name].inspect}" }
171
+ end
172
+ "#<#{self.class.name} _id: #{id}, #{attrs * ', '}>"
176
173
  end
177
174
 
178
- # Set the changed state of the +Document+ then notify observers that it has changed.
175
+ # Notify observers of an update.
179
176
  #
180
177
  # Example:
181
178
  #
182
179
  # <tt>person.notify</tt>
183
180
  def notify
184
- changed(true)
185
181
  notify_observers(self)
186
182
  end
187
183
 
@@ -210,8 +206,12 @@ module Mongoid #:nodoc:
210
206
 
211
207
  # Reloads the +Document+ attributes from the database.
212
208
  def reload
213
- @attributes = collection.find_one(:_id => id)
214
- self
209
+ reloaded = collection.find_one(:_id => id)
210
+ if Mongoid.raise_not_found_error
211
+ raise Errors::DocumentNotFound.new(self.class, id) if reloaded.nil?
212
+ end
213
+ @attributes = {}.merge(reloaded || {})
214
+ self.associations.keys.each { |association_name| unmemoize(association_name) }; self
215
215
  end
216
216
 
217
217
  # Remove a child document from this parent +Document+. Will reset the
@@ -246,6 +246,30 @@ module Mongoid #:nodoc:
246
246
  attributes.to_json(options)
247
247
  end
248
248
 
249
+ # Return an object to be encoded into a JSON string.
250
+ # Used by Rails 3's object->JSON chain to create JSON
251
+ # in a backend-agnostic way
252
+ #
253
+ # Example:
254
+ #
255
+ # <tt>person.as_json</tt>
256
+ def as_json(options = nil)
257
+ attributes
258
+ end
259
+
260
+ # Return this document as an object to be encoded as JSON,
261
+ # with any particular items modified on a per-encoder basis.
262
+ # Nothing special is required here since Mongoid bubbles up
263
+ # all the child associations to the parent attribute +Hash+
264
+ # using observers throughout the +Document+ lifecycle.
265
+ #
266
+ # Example:
267
+ #
268
+ # <tt>person.encode_json(encoder)</tt>
269
+ def encode_json(encoder)
270
+ attributes
271
+ end
272
+
249
273
  # Returns the id of the Document, used in Rails compatibility.
250
274
  def to_param
251
275
  id
@@ -262,20 +286,25 @@ module Mongoid #:nodoc:
262
286
  #
263
287
  # This will also cause the observing +Document+ to notify it's parent if
264
288
  # there is any.
265
- def update(child, clear = false)
289
+ def observe(child, clear = false)
266
290
  name = child.association_name
267
291
  attrs = child.instance_variable_get(:@attributes)
268
- clear ? @attributes.delete(name) : @attributes.insert(name, attrs)
292
+ if clear
293
+ @attributes.delete(name)
294
+ else
295
+ @attributes.insert(name, attrs) unless @attributes[name] && @attributes[name].include?(attrs)
296
+ end
269
297
  notify
270
298
  end
271
299
 
272
300
  protected
273
301
  # apply default values to attributes - calling procs as required
274
302
  def attributes_with_defaults(attributes = {})
275
- default_values = defaults.merge(attributes)
303
+ default_values = defaults
276
304
  default_values.each_pair do |key, val|
277
305
  default_values[key] = val.call if val.respond_to?(:call)
278
306
  end
307
+ default_values.merge(attributes)
279
308
  end
280
309
  end
281
310
  end