dm-mongo-adapter 0.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/LICENSE +20 -0
- data/README.rdoc +130 -0
- data/Rakefile +36 -0
- data/TODO +33 -0
- data/VERSION.yml +5 -0
- data/bin/console +31 -0
- data/dm-mongo-adapter.gemspec +154 -0
- data/lib/mongo_adapter.rb +71 -0
- data/lib/mongo_adapter/adapter.rb +255 -0
- data/lib/mongo_adapter/aggregates.rb +21 -0
- data/lib/mongo_adapter/conditions.rb +100 -0
- data/lib/mongo_adapter/embedded_model.rb +187 -0
- data/lib/mongo_adapter/embedded_resource.rb +134 -0
- data/lib/mongo_adapter/embedments/one_to_many.rb +139 -0
- data/lib/mongo_adapter/embedments/one_to_one.rb +53 -0
- data/lib/mongo_adapter/embedments/relationship.rb +258 -0
- data/lib/mongo_adapter/migrations.rb +45 -0
- data/lib/mongo_adapter/model.rb +89 -0
- data/lib/mongo_adapter/model/embedment.rb +215 -0
- data/lib/mongo_adapter/modifier.rb +85 -0
- data/lib/mongo_adapter/query.rb +252 -0
- data/lib/mongo_adapter/query/java_script.rb +145 -0
- data/lib/mongo_adapter/resource.rb +147 -0
- data/lib/mongo_adapter/types/date.rb +28 -0
- data/lib/mongo_adapter/types/date_time.rb +28 -0
- data/lib/mongo_adapter/types/db_ref.rb +65 -0
- data/lib/mongo_adapter/types/discriminator.rb +32 -0
- data/lib/mongo_adapter/types/object_id.rb +72 -0
- data/lib/mongo_adapter/types/objects.rb +31 -0
- data/script/performance.rb +260 -0
- data/spec/legacy/README +6 -0
- data/spec/legacy/adapter_shared_spec.rb +299 -0
- data/spec/legacy/adapter_spec.rb +174 -0
- data/spec/legacy/associations_spec.rb +140 -0
- data/spec/legacy/embedded_resource_spec.rb +157 -0
- data/spec/legacy/embedments_spec.rb +177 -0
- data/spec/legacy/modifier_spec.rb +81 -0
- data/spec/legacy/property_spec.rb +51 -0
- data/spec/legacy/spec_helper.rb +3 -0
- data/spec/legacy/sti_spec.rb +53 -0
- data/spec/lib/cleanup_models.rb +32 -0
- data/spec/lib/raw_connections.rb +11 -0
- data/spec/public/embedded_collection_spec.rb +61 -0
- data/spec/public/embedded_resource_spec.rb +220 -0
- data/spec/public/model/embedment_spec.rb +186 -0
- data/spec/public/model_spec.rb +37 -0
- data/spec/public/resource_spec.rb +564 -0
- data/spec/public/shared/model_embedments_spec.rb +338 -0
- data/spec/public/shared/object_id_shared_spec.rb +56 -0
- data/spec/public/types/df_ref_spec.rb +6 -0
- data/spec/public/types/discriminator_spec.rb +118 -0
- data/spec/public/types/embedded_array_spec.rb +55 -0
- data/spec/public/types/embedded_hash_spec.rb +83 -0
- data/spec/public/types/object_id_spec.rb +6 -0
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/embedded_model_spec.rb +43 -0
- data/spec/semipublic/model/embedment_spec.rb +42 -0
- data/spec/semipublic/resource_spec.rb +70 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +45 -0
- data/tasks/spec.rake +37 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +21 -0
- metadata +215 -0
@@ -0,0 +1,255 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
class Adapter < DataMapper::Adapters::AbstractAdapter
|
4
|
+
class ConnectionError < StandardError; end
|
5
|
+
|
6
|
+
# Persists one or more new resources
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# adapter.create(collection) # => 1
|
10
|
+
#
|
11
|
+
# @param [Enumerable<Resource>] resources
|
12
|
+
# The list of resources (model instances) to create
|
13
|
+
#
|
14
|
+
# @return [Integer]
|
15
|
+
# The number of records that were actually saved into the data-store
|
16
|
+
#
|
17
|
+
# @api semipublic
|
18
|
+
def create(resources)
|
19
|
+
resources.map do |resource|
|
20
|
+
with_collection(resource.model) do |collection|
|
21
|
+
resource.model.key.set(resource, [collection.insert(attributes_as_fields(resource))])
|
22
|
+
end
|
23
|
+
end.size
|
24
|
+
end
|
25
|
+
|
26
|
+
# Reads one or many resources from a datastore
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# adapter.read(query) # => [ { 'name' => 'Dan Kubb' } ]
|
30
|
+
#
|
31
|
+
# @param [Query] query
|
32
|
+
# The query to match resources in the datastore
|
33
|
+
#
|
34
|
+
# @return [Enumerable<Hash>]
|
35
|
+
# An array of hashes to become resources
|
36
|
+
#
|
37
|
+
# @api semipublic
|
38
|
+
def read(query)
|
39
|
+
with_collection(query.model) do |collection|
|
40
|
+
Query.new(collection, query).read
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Updates one or many existing resources
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# adapter.update(attributes, collection) # => 1
|
48
|
+
#
|
49
|
+
# @param [Hash(Property => Object)] attributes
|
50
|
+
# Hash of attribute values to set, keyed by Property
|
51
|
+
# @param [Collection] resources
|
52
|
+
# Collection of records to be updated
|
53
|
+
#
|
54
|
+
# @return [Integer]
|
55
|
+
# The number of records updated
|
56
|
+
#
|
57
|
+
# @api semipublic
|
58
|
+
def update(attributes, resources)
|
59
|
+
with_collection(resources.query.model) do |collection|
|
60
|
+
resources.each do |resource|
|
61
|
+
collection.update(key(resource),
|
62
|
+
attributes_as_fields(resource).merge(attributes_as_fields(attributes)))
|
63
|
+
end.size
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Deletes one or many existing resources
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# adapter.delete(collection) # => 1
|
71
|
+
#
|
72
|
+
# @param [Collection] resources
|
73
|
+
# Collection of records to be deleted
|
74
|
+
#
|
75
|
+
# @return [Integer]
|
76
|
+
# The number of records deleted
|
77
|
+
#
|
78
|
+
# @api semipublic
|
79
|
+
def delete(resources)
|
80
|
+
with_collection(resources.query.model) do |collection|
|
81
|
+
resources.each do |resource|
|
82
|
+
collection.remove(key(resource))
|
83
|
+
end.size
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# TODO: document
|
88
|
+
# @api semipublic
|
89
|
+
def execute(resources, document, options={})
|
90
|
+
resources.map do |resource|
|
91
|
+
with_collection(resource.model) do |collection|
|
92
|
+
collection.update(key(resource), document, options)
|
93
|
+
end
|
94
|
+
end.size
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def initialize(name, options = {})
|
100
|
+
# When giving a repository URI rather than a hash, the database name
|
101
|
+
# is :path, with a leading slash.
|
102
|
+
if options[:path] && options[:database].nil?
|
103
|
+
options[:database] = options[:path].sub(/^\//, '')
|
104
|
+
end
|
105
|
+
|
106
|
+
super
|
107
|
+
end
|
108
|
+
|
109
|
+
# Retrieves the key for a given resource as a hash.
|
110
|
+
#
|
111
|
+
# @param [Resource] resource
|
112
|
+
# The resource whose key is to be retrieved
|
113
|
+
#
|
114
|
+
# @return [Hash{Symbol => Object}]
|
115
|
+
# Returns a hash where each hash key/value corresponds to a key name
|
116
|
+
# and value on the resource.
|
117
|
+
#
|
118
|
+
# @api private
|
119
|
+
def key(resource)
|
120
|
+
resource.model.key(name).map{ |key| [key.field, key.value(resource.__send__(key.name))] }.to_hash
|
121
|
+
end
|
122
|
+
|
123
|
+
# Retrieves all of a records attributes and returns them as a Hash.
|
124
|
+
#
|
125
|
+
# The resulting hash can then be used with the Mongo library for
|
126
|
+
# inserting new -- and updating existing -- documents in the database.
|
127
|
+
#
|
128
|
+
# @param [Resource, Hash] record
|
129
|
+
# A DataMapper resource, or a hash containing fields and values.
|
130
|
+
#
|
131
|
+
# @return [Hash]
|
132
|
+
# Returns a hash containing the values for each of the fields in the
|
133
|
+
# given resource as raw (dumped) values suitable for use with the
|
134
|
+
# Mongo library.
|
135
|
+
#
|
136
|
+
# @api private
|
137
|
+
def attributes_as_fields(record)
|
138
|
+
attributes = case record
|
139
|
+
when DataMapper::Resource
|
140
|
+
attributes_from_resource(record)
|
141
|
+
when Hash
|
142
|
+
attributes_from_properties_hash(record)
|
143
|
+
end
|
144
|
+
|
145
|
+
attributes.except('_id') unless attributes.nil?
|
146
|
+
end
|
147
|
+
|
148
|
+
# TODO: document
|
149
|
+
def attributes_from_resource(record)
|
150
|
+
attributes = {}
|
151
|
+
|
152
|
+
model = record.model
|
153
|
+
|
154
|
+
model.properties.each do |property|
|
155
|
+
name = property.name
|
156
|
+
if model.public_method_defined?(name)
|
157
|
+
value = record.__send__(name)
|
158
|
+
attributes[property.field] = property.custom? ?
|
159
|
+
property.type.dump(value, property) : value
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
if model.respond_to?(:embedments)
|
164
|
+
model.embedments.each do |name, embedment|
|
165
|
+
if model.public_method_defined?(name)
|
166
|
+
value = record.__send__(name)
|
167
|
+
|
168
|
+
if embedment.kind_of?(Embedments::OneToMany::Relationship)
|
169
|
+
attributes[name] = value.map{ |resource| attributes_as_fields(resource) }
|
170
|
+
else
|
171
|
+
attributes[name] = attributes_as_fields(value)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
attributes
|
178
|
+
end
|
179
|
+
|
180
|
+
# TODO: document
|
181
|
+
def attributes_from_properties_hash(record)
|
182
|
+
attributes = {}
|
183
|
+
|
184
|
+
record.each do |key, value|
|
185
|
+
case key
|
186
|
+
when DataMapper::Property
|
187
|
+
attributes[key.field] = key.custom? ? key.type.dump(value, key) : value
|
188
|
+
when Embedments::Relationship
|
189
|
+
attributes[key.name] = attributes_as_fields(value)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
attributes
|
194
|
+
end
|
195
|
+
|
196
|
+
# Runs the given block within the context of a Mongo collection.
|
197
|
+
#
|
198
|
+
# @param [Model] model
|
199
|
+
# The model whose collection is to be scoped.
|
200
|
+
#
|
201
|
+
# @yieldparam [Mongo::Collection]
|
202
|
+
# The Mongo::Collection instance for the given model
|
203
|
+
#
|
204
|
+
# @api private
|
205
|
+
def with_collection(model)
|
206
|
+
begin
|
207
|
+
yield database.collection(model.storage_name(name))
|
208
|
+
rescue Exception => exception
|
209
|
+
DataMapper.logger.error(exception.to_s)
|
210
|
+
raise exception
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Returns the Mongo::DB instance for this process.
|
215
|
+
#
|
216
|
+
# @return [Mongo::DB]
|
217
|
+
#
|
218
|
+
# @raise [ConnectionError]
|
219
|
+
# If the database requires you to authenticate, and the given username
|
220
|
+
# or password was not correct, a ConnectionError exception will be
|
221
|
+
# raised.
|
222
|
+
#
|
223
|
+
# @api semipublic
|
224
|
+
def database
|
225
|
+
unless defined?(@database)
|
226
|
+
@database = connection.db(@options[:database])
|
227
|
+
|
228
|
+
if @options[:username] and not @database.authenticate(
|
229
|
+
@options[:username], @options[:password])
|
230
|
+
raise ConnectionError,
|
231
|
+
'MongoDB did not recognize the given username and/or ' \
|
232
|
+
'password; see the server logs for more information'
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
@database
|
237
|
+
end
|
238
|
+
|
239
|
+
# Returns the Mongo::Connection instance for this process
|
240
|
+
#
|
241
|
+
# @todo Reconnect if the connection has timed out, or if the process has
|
242
|
+
# been forked.
|
243
|
+
#
|
244
|
+
# @return [Mongo::Connection]
|
245
|
+
#
|
246
|
+
# @api semipublic
|
247
|
+
def connection
|
248
|
+
@connection ||= ::Mongo::Connection.new(*@options.values_at(:host, :port))
|
249
|
+
end
|
250
|
+
end # Adapter
|
251
|
+
end # Mongo
|
252
|
+
|
253
|
+
Adapters::MongoAdapter = DataMapper::Mongo::Adapter
|
254
|
+
Adapters.const_added(:MongoAdapter)
|
255
|
+
end # DataMapper
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module Aggregates
|
4
|
+
# TODO: document
|
5
|
+
# @api semipublic
|
6
|
+
def aggregate(query)
|
7
|
+
operator = if query.fields.size == 1 && query.fields.first.target == :all
|
8
|
+
:count
|
9
|
+
else
|
10
|
+
:group
|
11
|
+
end
|
12
|
+
|
13
|
+
with_collection(query.model) do |collection|
|
14
|
+
Query.new(collection, query).send(operator)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end # class Aggregates
|
18
|
+
end # module Mongo
|
19
|
+
|
20
|
+
Aggregates::MongoAdapter = DataMapper::Mongo::Aggregates
|
21
|
+
end # module DataMapper
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
# Used to filter records from Mongo::Collection according to the
|
4
|
+
# conditions defined in a DataMapper::Query. Warns when attempting to use
|
5
|
+
# operations which aren't supported by MongoDB.
|
6
|
+
class Conditions
|
7
|
+
include DataMapper::Query::Conditions
|
8
|
+
|
9
|
+
# Returns the query operation
|
10
|
+
#
|
11
|
+
# @return [DataMapper::Query::AbstractOperation]
|
12
|
+
# The operation which will be used to filter the collection.
|
13
|
+
#
|
14
|
+
# @api semipublic
|
15
|
+
attr_reader :operation
|
16
|
+
|
17
|
+
# Creates a new Conditions instance
|
18
|
+
#
|
19
|
+
# @param [DataMapper::Query::AbstractOperation] query_operation
|
20
|
+
# The top-level operation from DataMapper::Query#conditions
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
def initialize(query_operation)
|
24
|
+
@operation = verify_operation(query_operation)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Filters a collection according to the Conditions
|
28
|
+
#
|
29
|
+
# @param [Enumerable<Hash>] collection
|
30
|
+
# A collection of hashes which correspond to resource values
|
31
|
+
#
|
32
|
+
# @return [Enumerable<Hash>]
|
33
|
+
# Returns the collection without modification if the condition has no
|
34
|
+
# operations, otherwise it returns a copy of the collection with only
|
35
|
+
# the matching records.
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
def filter_collection!(collection)
|
39
|
+
@operation.operands.empty? ? collection : collection.delete_if {|record| !@operation.matches?(record)}
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Returns a copy of the operation, removing _from the original_ any
|
45
|
+
# operands which are incompatible with MongoDB.
|
46
|
+
#
|
47
|
+
# @param [DataMapper::Query::AbstractOperation] query_operation
|
48
|
+
# The top-level operation from DataMapper::Query#conditions
|
49
|
+
#
|
50
|
+
# @return [DataMapper::Query::AbstractOperation]
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
def verify_operation(query_operation)
|
54
|
+
operation = query_operation.dup.clear
|
55
|
+
|
56
|
+
query_operation.each do |operand|
|
57
|
+
if not_supported?(operand)
|
58
|
+
query_operation.operands.delete(operand)
|
59
|
+
operation << operand
|
60
|
+
elsif operand.kind_of?(AbstractOperation)
|
61
|
+
operation << verify_operation(operand)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
operation
|
66
|
+
end
|
67
|
+
|
68
|
+
# Checks if a given operand is supported by MongoDB.
|
69
|
+
#
|
70
|
+
# Comparisons not current supported are:
|
71
|
+
#
|
72
|
+
# * $nin with range
|
73
|
+
# * negated regexp comparison (see: http://jira.mongodb.org/browse/SERVER-251)
|
74
|
+
#
|
75
|
+
# @param [DataMapper::Query::Conditions::AbstractOperation, DataMapper::Query::Conditions::AbstractComparison] operand
|
76
|
+
# An operation to be made suitable for use with Mongo
|
77
|
+
#
|
78
|
+
# @return [Boolean]
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
def not_supported?(operand)
|
82
|
+
case operand
|
83
|
+
when OrOperation
|
84
|
+
true
|
85
|
+
when RegexpComparison
|
86
|
+
if operand.negated?
|
87
|
+
true
|
88
|
+
end
|
89
|
+
when InclusionComparison
|
90
|
+
if operand.negated?
|
91
|
+
true
|
92
|
+
end
|
93
|
+
else
|
94
|
+
false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end # Conditions
|
99
|
+
end # Mongo
|
100
|
+
end # DataMapper
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module EmbeddedModel
|
4
|
+
extend Chainable
|
5
|
+
include DataMapper::Model
|
6
|
+
|
7
|
+
# Creates a new Model class with default_storage_name +storage_name+
|
8
|
+
#
|
9
|
+
# If a block is passed, it will be eval'd in the context of the new Model
|
10
|
+
#
|
11
|
+
# @param [Proc] block
|
12
|
+
# a block that will be eval'd in the context of the new Model class
|
13
|
+
#
|
14
|
+
# @return [Model]
|
15
|
+
# the newly created Model class
|
16
|
+
#
|
17
|
+
# @api semipublic
|
18
|
+
def self.new(&block)
|
19
|
+
model = Class.new
|
20
|
+
|
21
|
+
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
22
|
+
include DataMapper::Mongo::EmbeddedResource
|
23
|
+
|
24
|
+
def self.name
|
25
|
+
to_s
|
26
|
+
end
|
27
|
+
RUBY
|
28
|
+
|
29
|
+
model.instance_eval(&block) if block
|
30
|
+
model
|
31
|
+
end
|
32
|
+
|
33
|
+
# Methods copied from DataMapper::Model
|
34
|
+
|
35
|
+
# Return all models that extend the Model module
|
36
|
+
#
|
37
|
+
# class Foo
|
38
|
+
# include DataMapper::Resource
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# DataMapper::Model.descendants.first #=> Foo
|
42
|
+
#
|
43
|
+
# @return [DescendantSet]
|
44
|
+
# Set containing the descendant models
|
45
|
+
#
|
46
|
+
# @api semipublic
|
47
|
+
def self.descendants
|
48
|
+
@descendants ||= DescendantSet.new
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return all models that inherit from a Model
|
52
|
+
#
|
53
|
+
# class Foo
|
54
|
+
# include DataMapper::Resource
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# class Bar < Foo
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# Foo.descendants.first #=> Bar
|
61
|
+
#
|
62
|
+
# @return [Set]
|
63
|
+
# Set containing the descendant classes
|
64
|
+
#
|
65
|
+
# @api semipublic
|
66
|
+
attr_reader :descendants
|
67
|
+
|
68
|
+
# Appends a module for inclusion into the model class after Resource.
|
69
|
+
#
|
70
|
+
# This is a useful way to extend Resource while still retaining a
|
71
|
+
# self.included method.
|
72
|
+
#
|
73
|
+
# @param [Module] inclusions
|
74
|
+
# the module that is to be appended to the module after Resource
|
75
|
+
#
|
76
|
+
# @return [Boolean]
|
77
|
+
# true if the inclusions have been successfully appended to the list
|
78
|
+
#
|
79
|
+
# @api semipublic
|
80
|
+
def self.append_inclusions(*inclusions)
|
81
|
+
extra_inclusions.concat inclusions
|
82
|
+
|
83
|
+
# Add the inclusion to existing descendants
|
84
|
+
descendants.each do |model|
|
85
|
+
inclusions.each { |inclusion| model.send :include, inclusion }
|
86
|
+
end
|
87
|
+
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
# The current registered extra inclusions
|
92
|
+
#
|
93
|
+
# @return [Set]
|
94
|
+
#
|
95
|
+
# @api private
|
96
|
+
def self.extra_inclusions
|
97
|
+
@extra_inclusions ||= []
|
98
|
+
end
|
99
|
+
|
100
|
+
# Extends the model with this module after Resource has been included.
|
101
|
+
#
|
102
|
+
# This is a useful way to extend Model while still retaining a self.extended method.
|
103
|
+
#
|
104
|
+
# @param [Module] extensions
|
105
|
+
# List of modules that will extend the model after it is extended by Model
|
106
|
+
#
|
107
|
+
# @return [Boolean]
|
108
|
+
# whether or not the inclusions have been successfully appended to the list
|
109
|
+
#
|
110
|
+
# @api semipublic
|
111
|
+
def self.append_extensions(*extensions)
|
112
|
+
extra_extensions.concat extensions
|
113
|
+
|
114
|
+
# Add the extension to existing descendants
|
115
|
+
descendants.each do |model|
|
116
|
+
extensions.each { |extension| model.extend(extension) }
|
117
|
+
end
|
118
|
+
|
119
|
+
true
|
120
|
+
end
|
121
|
+
|
122
|
+
# The current registered extra extensions
|
123
|
+
#
|
124
|
+
# @return [Set]
|
125
|
+
#
|
126
|
+
# @api private
|
127
|
+
def self.extra_extensions
|
128
|
+
@extra_extensions ||= []
|
129
|
+
end
|
130
|
+
|
131
|
+
# @api private
|
132
|
+
def self.extended(model)
|
133
|
+
descendants = self.descendants
|
134
|
+
|
135
|
+
descendants << model
|
136
|
+
|
137
|
+
model.instance_variable_set(:@valid, false)
|
138
|
+
model.instance_variable_set(:@base_model, model)
|
139
|
+
model.instance_variable_set(:@storage_names, {})
|
140
|
+
model.instance_variable_set(:@default_order, {})
|
141
|
+
model.instance_variable_set(:@descendants, descendants.class.new(model, descendants))
|
142
|
+
|
143
|
+
extra_extensions.each { |mod| model.extend(mod) }
|
144
|
+
extra_inclusions.each { |mod| model.send(:include, mod) }
|
145
|
+
end
|
146
|
+
|
147
|
+
# @api private
|
148
|
+
chainable do
|
149
|
+
def inherited(model)
|
150
|
+
descendants = self.descendants
|
151
|
+
|
152
|
+
descendants << model
|
153
|
+
|
154
|
+
model.instance_variable_set(:@valid, false)
|
155
|
+
model.instance_variable_set(:@base_model, base_model)
|
156
|
+
model.instance_variable_set(:@storage_names, @storage_names.dup)
|
157
|
+
model.instance_variable_set(:@default_order, @default_order.dup)
|
158
|
+
model.instance_variable_set(:@descendants, descendants.class.new(model, descendants))
|
159
|
+
|
160
|
+
# TODO: move this into dm-validations
|
161
|
+
if respond_to?(:validators)
|
162
|
+
validators.contexts.each do |context, validators|
|
163
|
+
model.validators.context(context).concat(validators)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# end of DataMapper::Model methods
|
170
|
+
|
171
|
+
append_extensions DataMapper::Model::Hook
|
172
|
+
append_extensions DataMapper::Model::Property
|
173
|
+
append_extensions DataMapper::Model::Relationship
|
174
|
+
|
175
|
+
# @overrides DataMapper::Model#assert_valid
|
176
|
+
def assert_valid
|
177
|
+
return if @valid
|
178
|
+
@valid = true
|
179
|
+
|
180
|
+
if properties.empty?
|
181
|
+
raise IncompleteModelError, "#{self.name} must have at least one property to be valid"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
end # EmbeddedModel
|
186
|
+
end # Mongo
|
187
|
+
end # DataMapper
|