forked from mongodb/mongoid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
document.rb
192 lines (172 loc) · 5.68 KB
/
document.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# encoding: utf-8
module Mongoid #:nodoc:
module Document
extend ActiveSupport::Concern
include Mongoid::Components
included do
attr_accessor :association_name
attr_reader :new_record
end
module ClassMethods #:nodoc:
# Instantiate a new object, only when loaded from the database or when
# the attributes have already been typecast.
#
# Example:
#
# <tt>Person.instantiate(:title => "Sir", :age => 30)</tt>
def instantiate(attrs = nil, allocating = false)
attributes = attrs || {}
if attributes["_id"] || allocating
document = allocate
document.instance_variable_set(:@attributes, attributes)
document.setup_modifications
document
else
new(attrs)
end
end
# Returns all types to query for when using this class as the base.
# *subclasses* is from activesupport. Note that a bug in *subclasses*
# causes the first call to only return direct children, hence
# the double call and unique.
def _types
@_type ||= [descendants + [self]].flatten.uniq.map(&:to_s)
end
end
# Performs equality checking on the document ids. For more robust
# equality checking please override this method.
def ==(other)
return false unless other.is_a?(Document)
id == other.id
end
# Delegates to ==
def eql?(comparison_object)
self == (comparison_object)
end
# Delegates to id in order to allow two records of the same type and id to
# work with something like:
# [ Person.find(1),
# Person.find(2),
# Person.find(3) ] &
# [ Person.find(1),
# Person.find(4) ] # => [ Person.find(1) ]
def hash
id.hash
end
# Introduces a child object into the +Document+ object graph. This will
# set up the relationships between the parent and child and update the
# attributes of the parent +Document+.
#
# Options:
#
# parent: The +Document+ to assimilate with.
# options: The association +Options+ for the child.
def assimilate(parent, options)
parentize(parent, options.name); notify; self
end
# Return the attributes hash with indifferent access.
def attributes
@attributes.with_indifferent_access
end
# Clone the current +Document+. This will return all attributes with the
# exception of the document's id and versions.
def clone
self.class.instantiate(@attributes.except("_id").except("versions").dup, true)
end
# Generate an id for this +Document+.
def identify
Identity.new(self).create
end
# Instantiate a new +Document+, setting the Document's attributes if
# given. If no attributes are provided, they will be initialized with
# an empty +Hash+.
#
# If a primary key is defined, the document's id will be set to that key,
# otherwise it will be set to a fresh +BSON::ObjectId+ string.
#
# Options:
#
# attrs: The attributes +Hash+ to set up the document with.
def initialize(attrs = nil)
@new_record = true
@attributes = default_attributes
process(attrs)
document = yield self if block_given?
identify
run_callbacks(:initialize) do
document
end
end
# Returns the class name plus its attributes.
def inspect
attrs = fields.map { |name, field| "#{name}: #{@attributes[name].inspect}" }
if Mongoid.allow_dynamic_fields
dynamic_keys = @attributes.keys - fields.keys - associations.keys - ["_id", "_type"]
attrs += dynamic_keys.map { |name| "#{name}: #{@attributes[name].inspect}" }
end
"#<#{self.class.name} _id: #{id}, #{attrs * ', '}>"
end
# Notify parent of an update.
#
# Example:
#
# <tt>person.notify</tt>
def notify
_parent.update_child(self) if _parent
end
# Return the attributes hash.
def raw_attributes
@attributes
end
# Reloads the +Document+ attributes from the database.
def reload
reloaded = collection.find_one(:_id => id)
if Mongoid.raise_not_found_error
raise Errors::DocumentNotFound.new(self.class, id) if reloaded.nil?
end
@attributes = {}.merge(reloaded || {})
self.associations.keys.each { |association_name| unmemoize(association_name) }; self
end
# Remove a child document from this parent +Document+. Will reset the
# memoized association and notify the parent of the change.
def remove(child)
name = child.association_name
if @building_nested
@attributes.remove(name, child.raw_attributes)
else
reset(name) do
@attributes.remove(name, child.raw_attributes)
@attributes[name]
end
notify
end
end
# def remove_without_reset
# name = child.association_name
# @attributes.remove(name, child.raw_attributes)
# notify
# end
# Return an array with this +Document+ only in it.
def to_a
[ self ]
end
# Recieve a notify call from a child +Document+. This will either update
# existing attributes on the +Document+ or clear them out for the child if
# the clear boolean is provided.
#
# Options:
#
# child: The child +Document+ that sent the notification.
# clear: Will clear out the child's attributes if set to true.
def update_child(child, clear = false)
name = child.association_name
attrs = child.instance_variable_get(:@attributes)
if clear
@attributes.delete(name)
else
# check good for array only
@attributes.insert(name, attrs) unless @attributes[name] && @attributes[name].include?(attrs)
end
end
end
end