dm-core 0.9.5 → 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|