dm-core 0.9.5 → 0.9.6
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/Manifest.txt +3 -0
- data/lib/dm-core.rb +14 -20
- data/lib/dm-core/adapters.rb +18 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +17 -10
- data/lib/dm-core/adapters/data_objects_adapter.rb +17 -22
- data/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +1 -1
- data/lib/dm-core/adapters/postgres_adapter.rb +2 -2
- data/lib/dm-core/adapters/sqlite3_adapter.rb +1 -1
- data/lib/dm-core/associations.rb +3 -2
- data/lib/dm-core/associations/many_to_many.rb +3 -3
- data/lib/dm-core/associations/one_to_many.rb +10 -2
- data/lib/dm-core/associations/relationship.rb +20 -16
- data/lib/dm-core/auto_migrations.rb +5 -4
- data/lib/dm-core/collection.rb +10 -6
- data/lib/dm-core/dependency_queue.rb +2 -1
- data/lib/dm-core/identity_map.rb +3 -6
- data/lib/dm-core/model.rb +48 -27
- data/lib/dm-core/property.rb +57 -37
- data/lib/dm-core/property_set.rb +29 -22
- data/lib/dm-core/query.rb +57 -49
- data/lib/dm-core/repository.rb +3 -3
- data/lib/dm-core/resource.rb +17 -15
- data/lib/dm-core/scope.rb +7 -7
- data/lib/dm-core/support/kernel.rb +6 -2
- data/lib/dm-core/transaction.rb +7 -7
- data/lib/dm-core/version.rb +1 -1
- data/script/performance.rb +114 -22
- data/spec/integration/association_spec.rb +31 -2
- data/spec/integration/association_through_spec.rb +2 -0
- data/spec/integration/associations/many_to_many_spec.rb +152 -0
- data/spec/integration/associations/one_to_many_spec.rb +40 -3
- data/spec/integration/dependency_queue_spec.rb +0 -12
- data/spec/integration/postgres_adapter_spec.rb +1 -1
- data/spec/integration/property_spec.rb +3 -3
- data/spec/integration/query_spec.rb +39 -8
- data/spec/integration/resource_spec.rb +10 -6
- data/spec/integration/sti_spec.rb +22 -0
- data/spec/integration/strategic_eager_loading_spec.rb +21 -6
- data/spec/integration/type_spec.rb +1 -0
- data/spec/lib/model_loader.rb +10 -1
- data/spec/models/content.rb +16 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/unit/adapters/data_objects_adapter_spec.rb +11 -11
- data/spec/unit/adapters/in_memory_adapter_spec.rb +98 -0
- data/spec/unit/associations/many_to_many_spec.rb +16 -1
- data/spec/unit/model_spec.rb +0 -16
- data/spec/unit/property_set_spec.rb +8 -1
- data/spec/unit/property_spec.rb +476 -240
- data/spec/unit/query_spec.rb +41 -0
- data/spec/unit/resource_spec.rb +75 -56
- data/tasks/ci.rb +4 -36
- data/tasks/dm.rb +3 -3
- metadata +5 -2
data/lib/dm-core/property_set.rb
CHANGED
@@ -4,10 +4,11 @@ module DataMapper
|
|
4
4
|
include Enumerable
|
5
5
|
|
6
6
|
def [](name)
|
7
|
-
|
7
|
+
property_for(name) || raise(ArgumentError, "Unknown property '#{name}'", caller)
|
8
8
|
end
|
9
9
|
|
10
10
|
def []=(name, property)
|
11
|
+
@key, @defaults = nil
|
11
12
|
if existing_property = detect { |p| p.name == name }
|
12
13
|
property.hash
|
13
14
|
@entries[@entries.index(existing_property)] = property
|
@@ -18,14 +19,23 @@ module DataMapper
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def has_property?(name)
|
21
|
-
|
22
|
+
!!property_for(name)
|
22
23
|
end
|
23
24
|
|
24
25
|
def slice(*names)
|
25
|
-
@
|
26
|
+
@key, @defaults = nil
|
27
|
+
names.map do |name|
|
28
|
+
property_for(name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear
|
33
|
+
@key, @defaults = nil
|
34
|
+
@entries.clear
|
26
35
|
end
|
27
36
|
|
28
37
|
def add(*properties)
|
38
|
+
@key, @defaults = nil
|
29
39
|
@entries.push(*properties)
|
30
40
|
properties.each { |property| property.hash }
|
31
41
|
self
|
@@ -47,11 +57,11 @@ module DataMapper
|
|
47
57
|
end
|
48
58
|
|
49
59
|
def defaults
|
50
|
-
reject { |property| property.lazy? }
|
60
|
+
@defaults ||= reject { |property| property.lazy? }
|
51
61
|
end
|
52
62
|
|
53
63
|
def key
|
54
|
-
select { |property| property.key? }
|
64
|
+
@key ||= select { |property| property.key? }
|
55
65
|
end
|
56
66
|
|
57
67
|
def indexes
|
@@ -68,10 +78,6 @@ module DataMapper
|
|
68
78
|
index_hash
|
69
79
|
end
|
70
80
|
|
71
|
-
def inheritance_property
|
72
|
-
detect { |property| property.type == DataMapper::Types::Discriminator }
|
73
|
-
end
|
74
|
-
|
75
81
|
def get(resource)
|
76
82
|
map { |property| property.get(resource) }
|
77
83
|
end
|
@@ -93,7 +99,7 @@ module DataMapper
|
|
93
99
|
end
|
94
100
|
|
95
101
|
def lazy_context(name)
|
96
|
-
lazy_contexts[name]
|
102
|
+
lazy_contexts[name] ||= []
|
97
103
|
end
|
98
104
|
|
99
105
|
def lazy_load_context(names)
|
@@ -128,25 +134,17 @@ module DataMapper
|
|
128
134
|
assert_kind_of 'properties', properties, Enumerable
|
129
135
|
|
130
136
|
@entries = properties
|
131
|
-
@property_for =
|
137
|
+
@property_for = {}
|
132
138
|
end
|
133
139
|
|
134
140
|
def initialize_copy(orig)
|
141
|
+
@key, @defaults = nil
|
135
142
|
@entries = orig.entries.dup
|
136
|
-
@property_for =
|
137
|
-
end
|
138
|
-
|
139
|
-
def hash_for_property_for
|
140
|
-
Hash.new do |h,k|
|
141
|
-
ksym = k.to_sym
|
142
|
-
if property = detect { |property| property.name == ksym }
|
143
|
-
h[ksym] = h[k.to_s] = property
|
144
|
-
end
|
145
|
-
end
|
143
|
+
@property_for = {}
|
146
144
|
end
|
147
145
|
|
148
146
|
def lazy_contexts
|
149
|
-
@lazy_contexts ||=
|
147
|
+
@lazy_contexts ||= {}
|
150
148
|
end
|
151
149
|
|
152
150
|
def parse_index(index, property, index_hash)
|
@@ -158,5 +156,14 @@ module DataMapper
|
|
158
156
|
when Enumerable then index.each { |idx| parse_index(idx, property, index_hash) }
|
159
157
|
end
|
160
158
|
end
|
159
|
+
|
160
|
+
def property_for(name)
|
161
|
+
unless @property_for[name]
|
162
|
+
property = detect { |property| property.name == name.to_sym }
|
163
|
+
@property_for[name.to_s] = @property_for[name.to_sym] = property if property
|
164
|
+
end
|
165
|
+
@property_for[name]
|
166
|
+
end
|
167
|
+
|
161
168
|
end # class PropertySet
|
162
169
|
end # module DataMapper
|
data/lib/dm-core/query.rb
CHANGED
@@ -99,9 +99,12 @@ module DataMapper
|
|
99
99
|
bind_values
|
100
100
|
end
|
101
101
|
|
102
|
-
|
103
|
-
|
104
|
-
|
102
|
+
def inheritance_property
|
103
|
+
fields.detect { |property| property.type == DataMapper::Types::Discriminator }
|
104
|
+
end
|
105
|
+
|
106
|
+
def inheritance_property_index
|
107
|
+
fields.index(inheritance_property)
|
105
108
|
end
|
106
109
|
|
107
110
|
# TODO: spec this
|
@@ -167,7 +170,7 @@ module DataMapper
|
|
167
170
|
@unique = options.fetch :unique, false # must be true or false
|
168
171
|
@offset = options.fetch :offset, 0 # must be an Integer greater than or equal to 0
|
169
172
|
@limit = options.fetch :limit, nil # must be an Integer greater than or equal to 1
|
170
|
-
@order = options.fetch :order, model.default_order # must be an Array of Symbol, DM::Query::Direction or DM::Property
|
173
|
+
@order = options.fetch :order, model.default_order(@repository.name) # must be an Array of Symbol, DM::Query::Direction or DM::Property
|
171
174
|
@add_reversed = options.fetch :add_reversed, false # must be true or false
|
172
175
|
@fields = options.fetch :fields, @properties.defaults # must be an Array of Symbol, String or DM::Property
|
173
176
|
@links = options.fetch :links, [] # must be an Array of Tuples - Tuple [DM::Query,DM::Assoc::Relationship]
|
@@ -190,7 +193,7 @@ module DataMapper
|
|
190
193
|
@includes = normalize_includes(@includes)
|
191
194
|
|
192
195
|
# treat all non-options as conditions
|
193
|
-
(options.keys - OPTIONS
|
196
|
+
(options.keys - OPTIONS).each do |k|
|
194
197
|
append_condition(k, options[k])
|
195
198
|
end
|
196
199
|
|
@@ -218,53 +221,50 @@ module DataMapper
|
|
218
221
|
|
219
222
|
# validate the options
|
220
223
|
def assert_valid_options(options)
|
221
|
-
#
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
value = options[attribute]
|
231
|
-
assert_kind_of "options[:#{attribute}]", value, Integer
|
232
|
-
end
|
233
|
-
|
234
|
-
if options.has_key?(:offset) && options[:offset] < 0
|
235
|
-
raise ArgumentError, "+options[:offset]+ must be greater than or equal to 0, but was #{options[:offset].inspect}", caller(2)
|
236
|
-
end
|
237
|
-
|
238
|
-
if options.has_key?(:limit) && options[:limit] < 1
|
239
|
-
raise ArgumentError, "+options[:limit]+ must be greater than or equal to 1, but was #{options[:limit].inspect}", caller(2)
|
240
|
-
end
|
224
|
+
# [DB] This might look more ugly now, but it's 2x as fast as the old code
|
225
|
+
# [DB] This is one of the heavy spots for Query.new I found during profiling.
|
226
|
+
options.each_pair do |attribute, value|
|
227
|
+
|
228
|
+
# validate the reload option and unique option
|
229
|
+
if [:reload, :unique].include? attribute
|
230
|
+
if value != true && value != false
|
231
|
+
raise ArgumentError, "+options[:#{attribute}]+ must be true or false, but was #{value.inspect}", caller(2)
|
232
|
+
end
|
241
233
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
234
|
+
# validate the offset and limit options
|
235
|
+
elsif [:offset, :limit].include? attribute
|
236
|
+
assert_kind_of "options[:#{attribute}]", value, Integer
|
237
|
+
if attribute == :offset && value < 0
|
238
|
+
raise ArgumentError, "+options[:offset]+ must be greater than or equal to 0, but was #{value.inspect}", caller(2)
|
239
|
+
elsif attribute == :limit && value < 1
|
240
|
+
raise ArgumentError, "+options[:limit]+ must be greater than or equal to 1, but was #{options[:limit].inspect}", caller(2)
|
241
|
+
end
|
246
242
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
243
|
+
# validate the :order, :fields, :links and :includes options
|
244
|
+
elsif [ :order, :fields, :links, :includes ].include? attribute
|
245
|
+
assert_kind_of "options[:#{attribute}]", value, Array
|
246
|
+
|
247
|
+
if value.empty?
|
248
|
+
if attribute == :fields
|
249
|
+
if options[:unique] == false
|
250
|
+
raise ArgumentError, '+options[:fields]+ cannot be empty if +options[:unique] is false', caller(2)
|
251
|
+
end
|
252
|
+
elsif attribute == :order
|
253
|
+
if options[:fields] && options[:fields].any? { |p| !p.kind_of?(Operator) }
|
254
|
+
raise ArgumentError, '+options[:order]+ cannot be empty if +options[:fields] contains a non-operator', caller(2)
|
255
|
+
end
|
256
|
+
else
|
257
|
+
raise ArgumentError, "+options[:#{attribute}]+ cannot be empty", caller(2)
|
255
258
|
end
|
256
|
-
else
|
257
|
-
raise ArgumentError, "+options[:#{attribute}]+ cannot be empty", caller(2)
|
258
259
|
end
|
259
|
-
end
|
260
|
-
end
|
261
260
|
|
262
|
-
|
263
|
-
|
264
|
-
|
261
|
+
# validates the :conditions option
|
262
|
+
elsif :conditions == attribute
|
263
|
+
assert_kind_of 'options[:conditions]', value, Hash, Array
|
265
264
|
|
266
|
-
|
267
|
-
|
265
|
+
if value.empty?
|
266
|
+
raise ArgumentError, '+options[:conditions]+ cannot be empty', caller(2)
|
267
|
+
end
|
268
268
|
end
|
269
269
|
end
|
270
270
|
end
|
@@ -402,6 +402,7 @@ module DataMapper
|
|
402
402
|
clause
|
403
403
|
when Operator
|
404
404
|
operator = clause.operator
|
405
|
+
return if operator == :not && bind_value == []
|
405
406
|
if clause.target.is_a?(Symbol)
|
406
407
|
@properties[clause.target]
|
407
408
|
elsif clause.target.is_a?(Query::Path)
|
@@ -438,7 +439,7 @@ module DataMapper
|
|
438
439
|
dump_custom_value(property_or_path.property, bind_value)
|
439
440
|
when Property
|
440
441
|
if property_or_path.custom?
|
441
|
-
property_or_path.type.dump(bind_value, property_or_path)
|
442
|
+
property_or_path.type.dump(bind_value, property_or_path)
|
442
443
|
else
|
443
444
|
bind_value
|
444
445
|
end
|
@@ -463,10 +464,11 @@ module DataMapper
|
|
463
464
|
|
464
465
|
# build an index of conditions by the property and operator to
|
465
466
|
# avoid nested looping
|
466
|
-
conditions_index =
|
467
|
+
conditions_index = {}
|
467
468
|
@conditions.each do |condition|
|
468
469
|
operator, property = *condition
|
469
470
|
next if :raw == operator
|
471
|
+
conditions_index[property] ||= {}
|
470
472
|
conditions_index[property][operator] = condition
|
471
473
|
end
|
472
474
|
|
@@ -476,6 +478,7 @@ module DataMapper
|
|
476
478
|
other_operator, other_property, other_bind_value = *other_condition
|
477
479
|
|
478
480
|
unless :raw == other_operator
|
481
|
+
conditions_index[other_property] ||= {}
|
479
482
|
if condition = conditions_index[other_property][other_operator]
|
480
483
|
operator, property, bind_value = *condition
|
481
484
|
|
@@ -587,6 +590,11 @@ module DataMapper
|
|
587
590
|
@property ? @property.field(*args) : nil
|
588
591
|
end
|
589
592
|
|
593
|
+
# more duck typing
|
594
|
+
def to_sym
|
595
|
+
@property ? @property.name.to_sym : @model.storage_name(@repository).to_sym
|
596
|
+
end
|
597
|
+
|
590
598
|
private
|
591
599
|
|
592
600
|
def initialize(repository, relationships, model, property_name = nil)
|
@@ -608,7 +616,7 @@ module DataMapper
|
|
608
616
|
end
|
609
617
|
|
610
618
|
if @model.properties(@repository.name)[method]
|
611
|
-
@property = @model.properties(@repository.name)[method]
|
619
|
+
@property = @model.properties(@repository.name)[method] unless @property
|
612
620
|
return self
|
613
621
|
end
|
614
622
|
|
data/lib/dm-core/repository.rb
CHANGED
@@ -33,11 +33,11 @@ module DataMapper
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def identity_map(model)
|
36
|
-
@identity_maps[model]
|
36
|
+
@identity_maps[model] ||= IdentityMap.new
|
37
37
|
end
|
38
38
|
|
39
39
|
# TODO: spec this
|
40
|
-
def scope
|
40
|
+
def scope
|
41
41
|
Repository.context << self
|
42
42
|
|
43
43
|
begin
|
@@ -97,7 +97,7 @@ module DataMapper
|
|
97
97
|
assert_kind_of 'name', name, Symbol
|
98
98
|
|
99
99
|
@name = name
|
100
|
-
@identity_maps =
|
100
|
+
@identity_maps = {}
|
101
101
|
end
|
102
102
|
|
103
103
|
# TODO: move to dm-more/dm-migrations
|
data/lib/dm-core/resource.rb
CHANGED
@@ -40,6 +40,10 @@ module DataMapper
|
|
40
40
|
model.const_set('Resource', self) unless model.const_defined?('Resource')
|
41
41
|
extra_inclusions.each { |inclusion| model.send(:include, inclusion) }
|
42
42
|
descendants << model
|
43
|
+
class << model
|
44
|
+
@_valid_model = false
|
45
|
+
attr_reader :_valid_model
|
46
|
+
end
|
43
47
|
end
|
44
48
|
|
45
49
|
# Return all classes that include the DataMapper::Resource module
|
@@ -53,7 +57,7 @@ module DataMapper
|
|
53
57
|
# include DataMapper::Resource
|
54
58
|
# end
|
55
59
|
#
|
56
|
-
# DataMapper
|
60
|
+
# DataMapper::Resource.descendants.to_a.first == Foo
|
57
61
|
#
|
58
62
|
# -
|
59
63
|
# @api semipublic
|
@@ -273,9 +277,7 @@ module DataMapper
|
|
273
277
|
associations_saved = false
|
274
278
|
child_associations.each { |a| associations_saved |= a.save }
|
275
279
|
|
276
|
-
saved =
|
277
|
-
new_record? ? create : update
|
278
|
-
end
|
280
|
+
saved = new_record? ? create : update
|
279
281
|
|
280
282
|
if saved
|
281
283
|
original_values.clear
|
@@ -377,20 +379,15 @@ module DataMapper
|
|
377
379
|
property = properties[name]
|
378
380
|
new_value = property.get!(self)
|
379
381
|
|
380
|
-
if property.custom?
|
381
|
-
new_value = property.type.dump(new_value, property)
|
382
|
-
old_value = property.type.dump(old_value, property)
|
383
|
-
end
|
384
|
-
|
385
382
|
dirty = case property.track
|
386
|
-
|
387
|
-
|
388
|
-
|
383
|
+
when :hash then old_value != new_value.hash
|
384
|
+
else
|
385
|
+
property.value(old_value) != property.value(new_value)
|
389
386
|
end
|
390
387
|
|
391
388
|
if dirty
|
392
389
|
property.hash
|
393
|
-
dirty_attributes[property] = new_value
|
390
|
+
dirty_attributes[property] = property.value(new_value)
|
394
391
|
end
|
395
392
|
end
|
396
393
|
|
@@ -547,6 +544,8 @@ module DataMapper
|
|
547
544
|
|
548
545
|
# Needs to be a protected method so that it is hookable
|
549
546
|
def create
|
547
|
+
# Can't create a resource that is not dirty and doesn't have serial keys
|
548
|
+
return false if new_record? && !dirty? && !model.key.any? { |p| p.serial? }
|
550
549
|
# set defaults for new resource
|
551
550
|
properties.each do |property|
|
552
551
|
next if attribute_loaded?(property.name)
|
@@ -578,6 +577,7 @@ module DataMapper
|
|
578
577
|
end
|
579
578
|
|
580
579
|
def assert_valid_model # :nodoc:
|
580
|
+
return if self.class._valid_model
|
581
581
|
properties = self.properties
|
582
582
|
|
583
583
|
if properties.empty? && relationships.empty?
|
@@ -587,6 +587,8 @@ module DataMapper
|
|
587
587
|
if properties.key.empty?
|
588
588
|
raise IncompleteResourceError, "#{model.name} must have a key."
|
589
589
|
end
|
590
|
+
|
591
|
+
self.class.instance_variable_set("@_valid_model", true)
|
590
592
|
end
|
591
593
|
|
592
594
|
# TODO document
|
@@ -625,8 +627,8 @@ module DataMapper
|
|
625
627
|
# @api public
|
626
628
|
#
|
627
629
|
# TODO: move to dm-more/dm-transactions
|
628
|
-
def transaction
|
629
|
-
model.transaction(
|
630
|
+
def transaction
|
631
|
+
model.transaction { |*block_args| yield(*block_args) }
|
630
632
|
end
|
631
633
|
end # module Transaction
|
632
634
|
|
data/lib/dm-core/scope.rb
CHANGED
@@ -5,8 +5,8 @@ module DataMapper
|
|
5
5
|
# @api private
|
6
6
|
def default_scope(repository_name = nil)
|
7
7
|
repository_name = self.default_repository_name if repository_name == :default || repository_name.nil?
|
8
|
-
@default_scope ||=
|
9
|
-
@default_scope[repository_name]
|
8
|
+
@default_scope ||= {}
|
9
|
+
@default_scope[repository_name] ||= {}
|
10
10
|
end
|
11
11
|
|
12
12
|
# @api private
|
@@ -17,13 +17,13 @@ module DataMapper
|
|
17
17
|
protected
|
18
18
|
|
19
19
|
# @api semipublic
|
20
|
-
def with_scope(query
|
20
|
+
def with_scope(query)
|
21
21
|
# merge the current scope with the passed in query
|
22
|
-
with_exclusive_scope(self.query ? self.query.merge(query) : query
|
22
|
+
with_exclusive_scope(self.query ? self.query.merge(query) : query) {|*block_args| yield(*block_args) }
|
23
23
|
end
|
24
24
|
|
25
25
|
# @api semipublic
|
26
|
-
def with_exclusive_scope(query
|
26
|
+
def with_exclusive_scope(query)
|
27
27
|
query = DataMapper::Query.new(repository, self, query) if query.kind_of?(Hash)
|
28
28
|
|
29
29
|
scope_stack << query
|
@@ -44,8 +44,8 @@ module DataMapper
|
|
44
44
|
|
45
45
|
# @api private
|
46
46
|
def scope_stack
|
47
|
-
scope_stack_for = Thread.current[:dm_scope_stack] ||=
|
48
|
-
scope_stack_for[self]
|
47
|
+
scope_stack_for = Thread.current[:dm_scope_stack] ||= {}
|
48
|
+
scope_stack_for[self] ||= []
|
49
49
|
end
|
50
50
|
|
51
51
|
# @api private
|
@@ -1,7 +1,11 @@
|
|
1
1
|
module Kernel
|
2
2
|
# Delegates to DataMapper::repository.
|
3
3
|
# Will not overwrite if a method of the same name is pre-defined.
|
4
|
-
def repository(*args
|
5
|
-
|
4
|
+
def repository(*args)
|
5
|
+
if block_given?
|
6
|
+
DataMapper.repository(*args) { |*block_args| yield(*block_args) }
|
7
|
+
else
|
8
|
+
DataMapper.repository(*args)
|
9
|
+
end
|
6
10
|
end
|
7
11
|
end # module Kernel
|