mongoid-rails2 1.9.3
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.
- data/MIT_LICENSE +20 -0
- data/README.rdoc +49 -0
- data/lib/mongoid/associations/belongs_to_related.rb +58 -0
- data/lib/mongoid/associations/embedded_in.rb +72 -0
- data/lib/mongoid/associations/embeds_many.rb +254 -0
- data/lib/mongoid/associations/embeds_one.rb +96 -0
- data/lib/mongoid/associations/has_many_related.rb +181 -0
- data/lib/mongoid/associations/has_one_related.rb +85 -0
- data/lib/mongoid/associations/meta_data.rb +29 -0
- data/lib/mongoid/associations/options.rb +57 -0
- data/lib/mongoid/associations/proxy.rb +24 -0
- data/lib/mongoid/associations.rb +300 -0
- data/lib/mongoid/attributes.rb +204 -0
- data/lib/mongoid/callbacks.rb +23 -0
- data/lib/mongoid/collection.rb +120 -0
- data/lib/mongoid/collections/cyclic_iterator.rb +34 -0
- data/lib/mongoid/collections/master.rb +29 -0
- data/lib/mongoid/collections/operations.rb +41 -0
- data/lib/mongoid/collections/slaves.rb +45 -0
- data/lib/mongoid/collections.rb +41 -0
- data/lib/mongoid/components.rb +27 -0
- data/lib/mongoid/concern.rb +31 -0
- data/lib/mongoid/config.rb +191 -0
- data/lib/mongoid/contexts/enumerable.rb +151 -0
- data/lib/mongoid/contexts/ids.rb +25 -0
- data/lib/mongoid/contexts/mongo.rb +285 -0
- data/lib/mongoid/contexts/paging.rb +50 -0
- data/lib/mongoid/contexts.rb +25 -0
- data/lib/mongoid/criteria.rb +249 -0
- data/lib/mongoid/criterion/complex.rb +21 -0
- data/lib/mongoid/criterion/exclusion.rb +65 -0
- data/lib/mongoid/criterion/inclusion.rb +110 -0
- data/lib/mongoid/criterion/optional.rb +136 -0
- data/lib/mongoid/cursor.rb +81 -0
- data/lib/mongoid/deprecation.rb +22 -0
- data/lib/mongoid/dirty.rb +253 -0
- data/lib/mongoid/document.rb +311 -0
- data/lib/mongoid/errors.rb +108 -0
- data/lib/mongoid/extensions/array/accessors.rb +17 -0
- data/lib/mongoid/extensions/array/aliasing.rb +4 -0
- data/lib/mongoid/extensions/array/assimilation.rb +26 -0
- data/lib/mongoid/extensions/array/conversions.rb +29 -0
- data/lib/mongoid/extensions/array/parentization.rb +13 -0
- data/lib/mongoid/extensions/big_decimal/conversions.rb +19 -0
- data/lib/mongoid/extensions/binary/conversions.rb +17 -0
- data/lib/mongoid/extensions/boolean/conversions.rb +22 -0
- data/lib/mongoid/extensions/date/conversions.rb +24 -0
- data/lib/mongoid/extensions/datetime/conversions.rb +12 -0
- data/lib/mongoid/extensions/float/conversions.rb +20 -0
- data/lib/mongoid/extensions/hash/accessors.rb +38 -0
- data/lib/mongoid/extensions/hash/assimilation.rb +39 -0
- data/lib/mongoid/extensions/hash/conversions.rb +45 -0
- data/lib/mongoid/extensions/hash/criteria_helpers.rb +21 -0
- data/lib/mongoid/extensions/hash/scoping.rb +12 -0
- data/lib/mongoid/extensions/integer/conversions.rb +20 -0
- data/lib/mongoid/extensions/nil/assimilation.rb +17 -0
- data/lib/mongoid/extensions/object/conversions.rb +33 -0
- data/lib/mongoid/extensions/objectid/conversions.rb +15 -0
- data/lib/mongoid/extensions/proc/scoping.rb +12 -0
- data/lib/mongoid/extensions/string/conversions.rb +15 -0
- data/lib/mongoid/extensions/string/inflections.rb +97 -0
- data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
- data/lib/mongoid/extensions/time_conversions.rb +35 -0
- data/lib/mongoid/extensions.rb +101 -0
- data/lib/mongoid/extras.rb +61 -0
- data/lib/mongoid/factory.rb +20 -0
- data/lib/mongoid/field.rb +59 -0
- data/lib/mongoid/fields.rb +65 -0
- data/lib/mongoid/finders.rb +144 -0
- data/lib/mongoid/identity.rb +39 -0
- data/lib/mongoid/indexes.rb +30 -0
- data/lib/mongoid/javascript/functions.yml +37 -0
- data/lib/mongoid/javascript.rb +21 -0
- data/lib/mongoid/matchers/all.rb +11 -0
- data/lib/mongoid/matchers/default.rb +26 -0
- data/lib/mongoid/matchers/exists.rb +13 -0
- data/lib/mongoid/matchers/gt.rb +11 -0
- data/lib/mongoid/matchers/gte.rb +11 -0
- data/lib/mongoid/matchers/in.rb +11 -0
- data/lib/mongoid/matchers/lt.rb +11 -0
- data/lib/mongoid/matchers/lte.rb +11 -0
- data/lib/mongoid/matchers/ne.rb +11 -0
- data/lib/mongoid/matchers/nin.rb +11 -0
- data/lib/mongoid/matchers/size.rb +11 -0
- data/lib/mongoid/matchers.rb +36 -0
- data/lib/mongoid/memoization.rb +33 -0
- data/lib/mongoid/named_scope.rb +37 -0
- data/lib/mongoid/observable.rb +30 -0
- data/lib/mongoid/paths.rb +62 -0
- data/lib/mongoid/persistence/command.rb +39 -0
- data/lib/mongoid/persistence/insert.rb +50 -0
- data/lib/mongoid/persistence/insert_embedded.rb +38 -0
- data/lib/mongoid/persistence/remove.rb +39 -0
- data/lib/mongoid/persistence/remove_all.rb +37 -0
- data/lib/mongoid/persistence/remove_embedded.rb +50 -0
- data/lib/mongoid/persistence/update.rb +63 -0
- data/lib/mongoid/persistence.rb +222 -0
- data/lib/mongoid/scope.rb +75 -0
- data/lib/mongoid/state.rb +39 -0
- data/lib/mongoid/timestamps.rb +27 -0
- data/lib/mongoid/version.rb +4 -0
- data/lib/mongoid/versioning.rb +27 -0
- data/lib/mongoid.rb +122 -0
- metadata +298 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc:
|
|
3
|
+
class Deprecation #:nodoc
|
|
4
|
+
include Singleton
|
|
5
|
+
|
|
6
|
+
# Alert of a deprecation. This will delegate to the logger and call warn on
|
|
7
|
+
# it.
|
|
8
|
+
#
|
|
9
|
+
# Example:
|
|
10
|
+
#
|
|
11
|
+
# <tt>deprecation.alert("Method no longer used")</tt>
|
|
12
|
+
def alert(message)
|
|
13
|
+
@logger.warn("Deprecation: #{message}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
# Instantiate a new logger to stdout or a rails logger if available.
|
|
18
|
+
def initialize
|
|
19
|
+
@logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc:
|
|
3
|
+
module Document
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
include Mongoid::Components
|
|
6
|
+
included do
|
|
7
|
+
include Mongoid::Components
|
|
8
|
+
|
|
9
|
+
cattr_accessor :primary_key, :hereditary
|
|
10
|
+
self.hereditary = false
|
|
11
|
+
|
|
12
|
+
attr_accessor :association_name, :_parent
|
|
13
|
+
|
|
14
|
+
delegate :db, :primary_key, :to => "self.class"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
# Return the database associated with this class.
|
|
19
|
+
def db
|
|
20
|
+
collection.db
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Perform default behavior but mark the hierarchy as being hereditary.
|
|
24
|
+
def inherited(subclass)
|
|
25
|
+
super(subclass)
|
|
26
|
+
self.hereditary = true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Returns a human readable version of the class.
|
|
30
|
+
#
|
|
31
|
+
# Example:
|
|
32
|
+
#
|
|
33
|
+
# <tt>MixedDrink.human_name # returns "Mixed Drink"</tt>
|
|
34
|
+
def human_name
|
|
35
|
+
name.labelize
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Instantiate a new object, only when loaded from the database or when
|
|
39
|
+
# the attributes have already been typecast.
|
|
40
|
+
#
|
|
41
|
+
# Example:
|
|
42
|
+
#
|
|
43
|
+
# <tt>Person.instantiate(:title => "Sir", :age => 30)</tt>
|
|
44
|
+
def instantiate(attrs = nil, allocating = false)
|
|
45
|
+
attributes = attrs || {}
|
|
46
|
+
if attributes["_id"] || allocating
|
|
47
|
+
document = allocate
|
|
48
|
+
document.instance_variable_set(:@attributes, attributes)
|
|
49
|
+
document.setup_modifications
|
|
50
|
+
return document
|
|
51
|
+
else
|
|
52
|
+
return new(attrs)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Defines the field that will be used for the id of this +Document+. This
|
|
57
|
+
# set the id of this +Document+ before save to a parameterized version of
|
|
58
|
+
# the field that was supplied. This is good for use for readable URLS in
|
|
59
|
+
# web applications.
|
|
60
|
+
#
|
|
61
|
+
# Example:
|
|
62
|
+
#
|
|
63
|
+
# class Person
|
|
64
|
+
# include Mongoid::Document
|
|
65
|
+
# key :first_name, :last_name
|
|
66
|
+
# end
|
|
67
|
+
def key(*fields)
|
|
68
|
+
self.primary_key = fields
|
|
69
|
+
before_save :identify
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns all types to query for when using this class as the base.
|
|
73
|
+
def _types
|
|
74
|
+
@_type ||= (self.subclasses + [ self.name ])
|
|
75
|
+
end
|
|
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
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
module InstanceMethods
|
|
92
|
+
# Performs equality checking on the document ids. For more robust
|
|
93
|
+
# equality checking please override this method.
|
|
94
|
+
def ==(other)
|
|
95
|
+
return false unless other.is_a?(Document)
|
|
96
|
+
id == other.id
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Delegates to ==
|
|
100
|
+
def eql?(comparison_object)
|
|
101
|
+
self == (comparison_object)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
|
105
|
+
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
|
|
106
|
+
def hash
|
|
107
|
+
id.hash
|
|
108
|
+
end
|
|
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
|
+
|
|
119
|
+
# Introduces a child object into the +Document+ object graph. This will
|
|
120
|
+
# set up the relationships between the parent and child and update the
|
|
121
|
+
# attributes of the parent +Document+.
|
|
122
|
+
#
|
|
123
|
+
# Options:
|
|
124
|
+
#
|
|
125
|
+
# parent: The +Document+ to assimilate with.
|
|
126
|
+
# options: The association +Options+ for the child.
|
|
127
|
+
def assimilate(parent, options)
|
|
128
|
+
parentize(parent, options.name); notify; self
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Return the attributes hash with indifferent access.
|
|
132
|
+
def attributes
|
|
133
|
+
@attributes.with_indifferent_access
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Clone the current +Document+. This will return all attributes with the
|
|
137
|
+
# exception of the document's id and versions.
|
|
138
|
+
def clone
|
|
139
|
+
self.class.instantiate(@attributes.except("_id").except("versions").dup, true)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Generate an id for this +Document+.
|
|
143
|
+
def identify
|
|
144
|
+
Identity.create(self)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Instantiate a new +Document+, setting the Document's attributes if
|
|
148
|
+
# given. If no attributes are provided, they will be initialized with
|
|
149
|
+
# an empty +Hash+.
|
|
150
|
+
#
|
|
151
|
+
# If a primary key is defined, the document's id will be set to that key,
|
|
152
|
+
# otherwise it will be set to a fresh +BSON::ObjectId+ string.
|
|
153
|
+
#
|
|
154
|
+
# Options:
|
|
155
|
+
#
|
|
156
|
+
# attrs: The attributes +Hash+ to set up the document with.
|
|
157
|
+
def initialize(attrs = nil)
|
|
158
|
+
@attributes = default_attributes
|
|
159
|
+
process(attrs)
|
|
160
|
+
@new_record = true
|
|
161
|
+
document = yield self if block_given?
|
|
162
|
+
identify
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Returns the class name plus its attributes.
|
|
166
|
+
def inspect
|
|
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 * ', '}>"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Notify observers of an update.
|
|
176
|
+
#
|
|
177
|
+
# Example:
|
|
178
|
+
#
|
|
179
|
+
# <tt>person.notify</tt>
|
|
180
|
+
def notify
|
|
181
|
+
notify_observers(self)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Sets up a child/parent association. This is used for newly created
|
|
185
|
+
# objects so they can be properly added to the graph and have the parent
|
|
186
|
+
# observers set up properly.
|
|
187
|
+
#
|
|
188
|
+
# Options:
|
|
189
|
+
#
|
|
190
|
+
# abject: The parent object that needs to be set for the child.
|
|
191
|
+
# association_name: The name of the association for the child.
|
|
192
|
+
#
|
|
193
|
+
# Example:
|
|
194
|
+
#
|
|
195
|
+
# <tt>address.parentize(person, :addresses)</tt>
|
|
196
|
+
def parentize(object, association_name)
|
|
197
|
+
self._parent = object
|
|
198
|
+
self.association_name = association_name.to_s
|
|
199
|
+
add_observer(object)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Return the attributes hash.
|
|
203
|
+
def raw_attributes
|
|
204
|
+
@attributes
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Reloads the +Document+ attributes from the database.
|
|
208
|
+
def reload
|
|
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
|
+
end
|
|
216
|
+
|
|
217
|
+
# Remove a child document from this parent +Document+. Will reset the
|
|
218
|
+
# memoized association and notify the parent of the change.
|
|
219
|
+
def remove(child)
|
|
220
|
+
name = child.association_name
|
|
221
|
+
reset(name) { @attributes.remove(name, child.raw_attributes) }
|
|
222
|
+
notify
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Return the root +Document+ in the object graph. If the current +Document+
|
|
226
|
+
# is the root object in the graph it will return self.
|
|
227
|
+
def _root
|
|
228
|
+
object = self
|
|
229
|
+
while (object._parent) do object = object._parent; end
|
|
230
|
+
object || self
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Return an array with this +Document+ only in it.
|
|
234
|
+
def to_a
|
|
235
|
+
[ self ]
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Return this document as a JSON string. Nothing special is required here
|
|
239
|
+
# since Mongoid bubbles up all the child associations to the parent
|
|
240
|
+
# attribute +Hash+ using observers throughout the +Document+ lifecycle.
|
|
241
|
+
#
|
|
242
|
+
# Example:
|
|
243
|
+
#
|
|
244
|
+
# <tt>person.to_json</tt>
|
|
245
|
+
def to_json(options = nil)
|
|
246
|
+
attributes.to_json(options)
|
|
247
|
+
end
|
|
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
|
+
|
|
273
|
+
# Returns the id of the Document, used in Rails compatibility.
|
|
274
|
+
def to_param
|
|
275
|
+
id
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Observe a notify call from a child +Document+. This will either update
|
|
279
|
+
# existing attributes on the +Document+ or clear them out for the child if
|
|
280
|
+
# the clear boolean is provided.
|
|
281
|
+
#
|
|
282
|
+
# Options:
|
|
283
|
+
#
|
|
284
|
+
# child: The child +Document+ that sent the notification.
|
|
285
|
+
# clear: Will clear out the child's attributes if set to true.
|
|
286
|
+
#
|
|
287
|
+
# This will also cause the observing +Document+ to notify it's parent if
|
|
288
|
+
# there is any.
|
|
289
|
+
def observe(child, clear = false)
|
|
290
|
+
name = child.association_name
|
|
291
|
+
attrs = child.instance_variable_get(:@attributes)
|
|
292
|
+
if clear
|
|
293
|
+
@attributes.delete(name)
|
|
294
|
+
else
|
|
295
|
+
@attributes.insert(name, attrs) unless @attributes[name] && @attributes[name].include?(attrs)
|
|
296
|
+
end
|
|
297
|
+
notify
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
protected
|
|
301
|
+
# apply default values to attributes - calling procs as required
|
|
302
|
+
def attributes_with_defaults(attributes = {})
|
|
303
|
+
default_values = defaults
|
|
304
|
+
default_values.each_pair do |key, val|
|
|
305
|
+
default_values[key] = val.call if val.respond_to?(:call)
|
|
306
|
+
end
|
|
307
|
+
default_values.merge(attributes)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc
|
|
3
|
+
module Errors #:nodoc
|
|
4
|
+
|
|
5
|
+
# Raised when querying the database for a document by a specific id which
|
|
6
|
+
# does not exist. If multiple ids were passed then it will display all of
|
|
7
|
+
# those.
|
|
8
|
+
#
|
|
9
|
+
# Example:
|
|
10
|
+
#
|
|
11
|
+
# <tt>DocumentNotFound.new(Person, ["1", "2"])</tt>
|
|
12
|
+
class DocumentNotFound < RuntimeError
|
|
13
|
+
def initialize(klass, ids)
|
|
14
|
+
@klass, @identifier = klass, ids.is_a?(Array) ? ids.join(", ") : ids
|
|
15
|
+
end
|
|
16
|
+
def message
|
|
17
|
+
"Document not found for class #{@klass} and id(s) #{@identifier}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Raised when invalid options are passed into a constructor or method.
|
|
22
|
+
#
|
|
23
|
+
# Example:
|
|
24
|
+
#
|
|
25
|
+
# <tt>InvalidOptions.new</tt>
|
|
26
|
+
class InvalidOptions < RuntimeError; end
|
|
27
|
+
|
|
28
|
+
# Raised when the database connection has not been set up properly, either
|
|
29
|
+
# by attempting to set an object on the db that is not a +Mongo::DB+, or
|
|
30
|
+
# not setting anything at all.
|
|
31
|
+
#
|
|
32
|
+
# Example:
|
|
33
|
+
#
|
|
34
|
+
# <tt>InvalidDatabase.new("Not a DB")</tt>
|
|
35
|
+
class InvalidDatabase < RuntimeError
|
|
36
|
+
def initialize(database)
|
|
37
|
+
@database = database
|
|
38
|
+
end
|
|
39
|
+
def message
|
|
40
|
+
"Database should be a Mongo::DB, not #{@database.class.name}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Raised when the database version is not supported by Mongoid.
|
|
45
|
+
#
|
|
46
|
+
# Example:
|
|
47
|
+
#
|
|
48
|
+
# <tt>UnsupportedVersion.new(Mongo::ServerVersion.new("1.3.1"))</tt>
|
|
49
|
+
class UnsupportedVersion < RuntimeError
|
|
50
|
+
def initialize(version)
|
|
51
|
+
@version = version
|
|
52
|
+
end
|
|
53
|
+
def message
|
|
54
|
+
"MongoDB #{@version} not supported, please upgrade to #{Mongoid::MONGODB_VERSION}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Raised when a persisence method ending in ! fails validation. The message
|
|
59
|
+
# will contain the full error messages from the +Document+ in question.
|
|
60
|
+
#
|
|
61
|
+
# Example:
|
|
62
|
+
#
|
|
63
|
+
# <tt>Validations.new(person.errors)</tt>
|
|
64
|
+
class Validations < RuntimeError
|
|
65
|
+
def initialize(errors)
|
|
66
|
+
@errors = errors
|
|
67
|
+
end
|
|
68
|
+
def message
|
|
69
|
+
"Validation Failed: #{@errors.full_messages.join(", ")}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# This error is raised when trying to access a Mongo::Collection from an
|
|
74
|
+
# embedded document.
|
|
75
|
+
#
|
|
76
|
+
# Example:
|
|
77
|
+
#
|
|
78
|
+
# <tt>InvalidCollection.new(Address)</tt>
|
|
79
|
+
class InvalidCollection < RuntimeError
|
|
80
|
+
def initialize(klass)
|
|
81
|
+
@klass = klass
|
|
82
|
+
end
|
|
83
|
+
def message
|
|
84
|
+
"Access to the collection for #{@klass.name} is not allowed " +
|
|
85
|
+
"since it is an embedded document, please access a collection from " +
|
|
86
|
+
"the root document"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# This error is raised when trying to create a field that conflicts with
|
|
91
|
+
# a Mongoid internal attribute or method.
|
|
92
|
+
#
|
|
93
|
+
# Example:
|
|
94
|
+
#
|
|
95
|
+
# <tt>InvalidField.new('collection')</tt>
|
|
96
|
+
class InvalidField < RuntimeError
|
|
97
|
+
def initialize(name)
|
|
98
|
+
@name = name
|
|
99
|
+
end
|
|
100
|
+
def message
|
|
101
|
+
"Defining a field named '#{@name}' is not allowed. " +
|
|
102
|
+
"Do not define fields that conflict with Mongoid internal attributes " +
|
|
103
|
+
"or method names. Use Document#instance_methods to see what " +
|
|
104
|
+
"names this includes."
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc:
|
|
3
|
+
module Extensions #:nodoc:
|
|
4
|
+
module Array #:nodoc:
|
|
5
|
+
module Accessors #:nodoc:
|
|
6
|
+
# If the attributes already exists in the array then they will be
|
|
7
|
+
# updated, otherwise they will be appended.
|
|
8
|
+
def update(attributes)
|
|
9
|
+
delete_if { |e| attributes["_id"] && (e["_id"] == attributes["_id"]) }
|
|
10
|
+
self.<< attributes
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
alias :merge! :update
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|