mongoid 1.2.14 → 1.9.0

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 (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