dm-mongo-adapter 0.2.0.pre1
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/.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
|