rod 0.6.2 → 0.6.3
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/.travis.yml +1 -0
- data/README.rdoc +10 -9
- data/Rakefile +15 -5
- data/changelog.txt +18 -0
- data/features/append.feature +0 -2
- data/features/basic.feature +7 -7
- data/features/collection_proxy.feature +140 -0
- data/features/flat_indexing.feature +9 -8
- data/features/{fred.feature → persistence.feature} +5 -8
- data/features/{assoc_indexing.feature → relationship_indexing.feature} +36 -0
- data/features/segmented_indexing.feature +6 -6
- data/features/steps/collection_proxy.rb +89 -0
- data/features/steps/model.rb +15 -3
- data/features/steps/rod.rb +1 -1
- data/features/support/mocha.rb +16 -0
- data/features/update.feature +263 -0
- data/lib/rod.rb +10 -2
- data/lib/rod/abstract_database.rb +49 -111
- data/lib/rod/abstract_model.rb +26 -6
- data/lib/rod/collection_proxy.rb +235 -34
- data/lib/rod/constants.rb +1 -1
- data/lib/rod/database.rb +5 -6
- data/lib/rod/exception.rb +1 -1
- data/lib/rod/index/base.rb +97 -0
- data/lib/rod/index/flat_index.rb +72 -0
- data/lib/rod/index/segmented_index.rb +100 -0
- data/lib/rod/model.rb +172 -185
- data/lib/rod/reference_updater.rb +85 -0
- data/lib/rod/utils.rb +29 -0
- data/rod.gemspec +4 -1
- data/tests/migration_create.rb +33 -12
- data/tests/migration_migrate.rb +24 -7
- data/tests/migration_model1.rb +5 -0
- data/tests/migration_model2.rb +36 -0
- data/tests/migration_verify.rb +49 -42
- data/tests/missing_class_create.rb +21 -0
- data/tests/missing_class_verify.rb +20 -0
- data/tests/properties_order_create.rb +16 -0
- data/tests/properties_order_verify.rb +17 -0
- data/tests/unit/abstract_database.rb +13 -0
- data/tests/unit/model_tests.rb +3 -3
- data/utils/convert_index.rb +1 -1
- metadata +62 -18
- data/lib/rod/segmented_index.rb +0 -85
data/lib/rod.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
require 'inline'
|
2
2
|
require 'english/inflect'
|
3
|
-
require 'active_model'
|
3
|
+
require 'active_model/deprecated_error_methods'
|
4
|
+
require 'active_model/validator'
|
5
|
+
require 'active_model/naming'
|
6
|
+
require 'active_model/translation'
|
7
|
+
require 'active_model/validations'
|
8
|
+
require 'active_model/dirty'
|
4
9
|
require 'active_support/dependencies'
|
5
10
|
|
6
11
|
# XXX This should be done in a different way, since a library should not
|
@@ -17,6 +22,9 @@ require 'rod/join_element'
|
|
17
22
|
require 'rod/cache'
|
18
23
|
require 'rod/collection_proxy'
|
19
24
|
require 'rod/model'
|
25
|
+
require 'rod/reference_updater'
|
20
26
|
require 'rod/string_element'
|
21
27
|
require 'rod/string_ex'
|
22
|
-
require 'rod/
|
28
|
+
require 'rod/index/base'
|
29
|
+
require 'rod/index/flat_index'
|
30
|
+
require 'rod/index/segmented_index'
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'singleton'
|
2
2
|
require 'yaml'
|
3
|
-
require 'rod/
|
4
|
-
require '
|
3
|
+
require 'rod/index/base'
|
4
|
+
require 'rod/utils'
|
5
5
|
|
6
6
|
module Rod
|
7
7
|
# This class implements the database abstraction, i.e. it
|
@@ -13,9 +13,14 @@ module Rod
|
|
13
13
|
# a given model (set of classes).
|
14
14
|
include Singleton
|
15
15
|
|
16
|
+
include Utils
|
17
|
+
|
16
18
|
# The meta-data of the DataBase.
|
17
19
|
attr_reader :metadata
|
18
20
|
|
21
|
+
# The path which the database instance is located on.
|
22
|
+
attr_reader :path
|
23
|
+
|
19
24
|
# Initializes the classes linked with this database and the handler.
|
20
25
|
def initialize
|
21
26
|
@classes ||= self.special_classes
|
@@ -58,12 +63,7 @@ module Rod
|
|
58
63
|
klass.send(:build_structure)
|
59
64
|
remove_file(klass.path_for_data(@path))
|
60
65
|
klass.indexed_properties.each do |property,options|
|
61
|
-
|
62
|
-
if test(?d,path)
|
63
|
-
remove_files(path + "*")
|
64
|
-
elsif test(?f,path)
|
65
|
-
remove_file(path)
|
66
|
-
end
|
66
|
+
klass.index_for(property,options).destroy
|
67
67
|
end
|
68
68
|
next if special_class?(klass)
|
69
69
|
remove_files_but(klass.inline_library)
|
@@ -116,17 +116,20 @@ module Rod
|
|
116
116
|
end
|
117
117
|
generate_c_code(@path, self.classes)
|
118
118
|
@handler = _init_handler(@path)
|
119
|
+
metadata_copy = @metadata.dup
|
120
|
+
metadata_copy.delete("Rod")
|
119
121
|
self.classes.each do |klass|
|
120
|
-
meta =
|
122
|
+
meta = metadata_copy.delete(klass.name)
|
121
123
|
if meta.nil?
|
122
124
|
# new class
|
123
125
|
next
|
124
126
|
end
|
125
|
-
unless klass.compatible?(meta
|
127
|
+
unless klass.compatible?(meta) || options[:generate] || options[:migrate]
|
126
128
|
raise IncompatibleVersion.
|
127
129
|
new("Incompatible definition of '#{klass.name}' class.\n" +
|
128
|
-
"Database and runtime versions are different:\n" +
|
129
|
-
|
130
|
+
"Database and runtime versions are different:\n " +
|
131
|
+
klass.difference(meta).
|
132
|
+
map{|e1,e2| "DB: #{e1} vs. RT: #{e2}"}.join("\n "))
|
130
133
|
end
|
131
134
|
set_count(klass,meta[:count])
|
132
135
|
file_size = File.new(klass.path_for_data(@path)).size
|
@@ -142,15 +145,20 @@ module Rod
|
|
142
145
|
set_page_count(new_class,pages)
|
143
146
|
end
|
144
147
|
end
|
148
|
+
if metadata_copy.size > 0
|
149
|
+
@handler = nil
|
150
|
+
raise DatabaseError.new("The following classes are missing in runtime:\n - " +
|
151
|
+
metadata_copy.keys.join("\n - "))
|
152
|
+
end
|
145
153
|
_open(@handler)
|
146
154
|
if options[:migrate]
|
147
155
|
empty_data = "\0" * _page_size
|
148
156
|
self.classes.each do |klass|
|
149
157
|
next unless klass.to_s =~ LEGACY_RE
|
150
158
|
new_class = klass.name.sub(LEGACY_RE,"").constantize
|
151
|
-
old_metadata = klass.metadata
|
159
|
+
old_metadata = klass.metadata
|
152
160
|
old_metadata.merge!({:superclass => old_metadata[:superclass].sub(LEGACY_RE,"")})
|
153
|
-
unless new_class.compatible?(old_metadata
|
161
|
+
unless new_class.compatible?(old_metadata)
|
154
162
|
File.open(new_class.path_for_data(@path),"w") do |out|
|
155
163
|
send("_#{new_class.struct_name}_page_count",@handler).
|
156
164
|
times{|i| out.print(empty_data)}
|
@@ -267,6 +275,18 @@ module Rod
|
|
267
275
|
class_id, @handler)
|
268
276
|
end
|
269
277
|
|
278
|
+
# Allocates space for polymorphic join elements.
|
279
|
+
def allocate_polymorphic_join_elements(size)
|
280
|
+
raise DatabaseError.new("Readonly database.") if readonly_data?
|
281
|
+
_allocate_polymorphic_join_elements(size,@handler)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Allocates space for join elements.
|
285
|
+
def allocate_join_elements(size)
|
286
|
+
raise DatabaseError.new("Readonly database.") if readonly_data?
|
287
|
+
_allocate_join_elements(size,@handler)
|
288
|
+
end
|
289
|
+
|
270
290
|
# Returns the string of given +length+ starting at given +offset+.
|
271
291
|
# Options:
|
272
292
|
# * +:skip_encoding+ - if set to +true+, the string is left as ASCII-8BIT
|
@@ -305,25 +325,6 @@ module Rod
|
|
305
325
|
send("_#{klass.struct_name}_page_count=",@handler,value)
|
306
326
|
end
|
307
327
|
|
308
|
-
# Reads index of +field+ (with +options+) for +klass+.
|
309
|
-
def read_index(klass,field,options)
|
310
|
-
case options[:index]
|
311
|
-
when :flat,true
|
312
|
-
begin
|
313
|
-
File.open(klass.path_for_index(@path,field,options)) do |input|
|
314
|
-
return {} if input.size == 0
|
315
|
-
return Marshal.load(input)
|
316
|
-
end
|
317
|
-
rescue Errno::ENOENT
|
318
|
-
return {}
|
319
|
-
end
|
320
|
-
when :segmented
|
321
|
-
return SegmentedIndex.new(klass.path_for_index(@path,field,options))
|
322
|
-
else
|
323
|
-
raise RodException.new("Invalid index type '#{options[:index]}'.")
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
328
|
# Store index of +field+ (with +options+) of +klass+ in the database.
|
328
329
|
# There are two types of indices:
|
329
330
|
# * +:flat+ - marshalled index is stored in one file
|
@@ -331,68 +332,25 @@ module Rod
|
|
331
332
|
def write_index(klass,property,options)
|
332
333
|
raise DatabaseError.new("Readonly database.") if readonly_data?
|
333
334
|
class_index = klass.index_for(property,options)
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
else
|
340
|
-
proxy = ids
|
341
|
-
end
|
342
|
-
offset = _allocate_join_elements(proxy.size,@handler)
|
343
|
-
proxy.each_id.with_index do |rod_id,index|
|
344
|
-
set_join_element_id(offset, index, rod_id)
|
345
|
-
end
|
346
|
-
class_index[key] = [offset,proxy.size]
|
347
|
-
end
|
348
|
-
end
|
349
|
-
case options[:index]
|
350
|
-
when :flat,true
|
351
|
-
File.open(klass.path_for_index(@path,property,options),"w") do |out|
|
352
|
-
out.puts(Marshal.dump(class_index))
|
353
|
-
end
|
354
|
-
when :segmented
|
355
|
-
path = klass.path_for_index(@path,property,options)
|
356
|
-
if class_index.is_a?(Hash)
|
357
|
-
index = SegmentedIndex.new(path)
|
358
|
-
class_index.each{|k,v| index[k] = v}
|
359
|
-
else
|
360
|
-
index = class_index
|
361
|
-
end
|
362
|
-
index.save
|
363
|
-
index = nil
|
335
|
+
if options[:convert]
|
336
|
+
# Only convert the index, without (re)storing the values.
|
337
|
+
index = Index::Base.create(klass.path_for_index(@path,property),klass,options)
|
338
|
+
index.copy(class_index)
|
339
|
+
class_index = index
|
364
340
|
else
|
365
|
-
|
341
|
+
class_index.each do |key,proxy|
|
342
|
+
proxy.save
|
343
|
+
end
|
366
344
|
end
|
345
|
+
class_index.save
|
346
|
+
class_index = nil
|
367
347
|
end
|
368
348
|
|
369
349
|
# Store the object in the database.
|
370
350
|
def store(klass,object)
|
371
351
|
raise DatabaseError.new("Readonly database.") if readonly_data?
|
372
|
-
|
373
|
-
if new_object
|
352
|
+
if object.new?
|
374
353
|
send("_store_" + klass.struct_name,object,@handler)
|
375
|
-
# set fields' values
|
376
|
-
object.class.fields.each do |name,options|
|
377
|
-
# rod_id is set during _store
|
378
|
-
object.update_field(name) unless name == "rod_id"
|
379
|
-
end
|
380
|
-
# set ids of objects referenced via singular associations
|
381
|
-
object.class.singular_associations.each do |name,options|
|
382
|
-
object.update_singular_association(name,object.send(name))
|
383
|
-
end
|
384
|
-
end
|
385
|
-
# set ids of objects referenced via plural associations
|
386
|
-
# TODO should be disabled, when there are no new elements
|
387
|
-
object.class.plural_associations.each do |name,options|
|
388
|
-
elements = object.send(name) || []
|
389
|
-
if options[:polymorphic]
|
390
|
-
offset = _allocate_polymorphic_join_elements(elements.size,@handler)
|
391
|
-
else
|
392
|
-
offset = _allocate_join_elements(elements.size,@handler)
|
393
|
-
end
|
394
|
-
object.update_count_and_offset(name,elements.size,offset)
|
395
|
-
object.update_plural_association(name,elements)
|
396
354
|
end
|
397
355
|
end
|
398
356
|
|
@@ -430,7 +388,8 @@ module Rod
|
|
430
388
|
|
431
389
|
# Retruns the path to the DB as a name of a directory.
|
432
390
|
def canonicalize_path(path)
|
433
|
-
path
|
391
|
+
path += "/" unless path[-1] == "/"
|
392
|
+
path
|
434
393
|
end
|
435
394
|
|
436
395
|
# Special classes used by the database.
|
@@ -533,28 +492,6 @@ module Rod
|
|
533
492
|
generate_classes(legacy_module)
|
534
493
|
end
|
535
494
|
|
536
|
-
# Removes single file.
|
537
|
-
def remove_file(file_name)
|
538
|
-
if test(?f,file_name)
|
539
|
-
File.delete(file_name)
|
540
|
-
puts "Removing #{file_name}" if $ROD_DEBUG
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
# Remove all files matching the +pattern+.
|
545
|
-
# If +skip+ given, the file with the given name is not deleted.
|
546
|
-
def remove_files(pattern,skip=nil)
|
547
|
-
Dir.glob(pattern).each do |file_name|
|
548
|
-
remove_file(file_name) unless file_name == skip
|
549
|
-
end
|
550
|
-
end
|
551
|
-
|
552
|
-
# Removes all files which are similar (i.e. are generated
|
553
|
-
# by RubyInline for the same class) to +name+
|
554
|
-
# excluding the file with exactly the name given.
|
555
|
-
def remove_files_but(name)
|
556
|
-
remove_files(name.sub(INLINE_PATTERN_RE,"*"),name)
|
557
|
-
end
|
558
495
|
|
559
496
|
# Writes the metadata to the database.yml file.
|
560
497
|
def write_metadata
|
@@ -564,7 +501,8 @@ module Rod
|
|
564
501
|
rod_data[:created_at] = self.metadata["Rod"][:created_at] || Time.now
|
565
502
|
rod_data[:updated_at] = Time.now
|
566
503
|
self.classes.each do |klass|
|
567
|
-
metadata[klass.name] = klass.metadata
|
504
|
+
metadata[klass.name] = klass.metadata
|
505
|
+
metadata[klass.name][:count] = self.count(klass)
|
568
506
|
end
|
569
507
|
File.open(@path + DATABASE_FILE,"w") do |out|
|
570
508
|
out.puts(YAML::dump(metadata))
|
data/lib/rod/abstract_model.rb
CHANGED
@@ -46,20 +46,40 @@ module Rod
|
|
46
46
|
end
|
47
47
|
|
48
48
|
# Returns meta-data (in the form of a hash) for the model.
|
49
|
-
def self.metadata
|
49
|
+
def self.metadata
|
50
50
|
meta = {}
|
51
|
-
meta[:count] = database.count(self)
|
52
51
|
meta[:superclass] = self.superclass.name
|
53
52
|
meta
|
54
53
|
end
|
55
54
|
|
56
55
|
# Checks if the +metadata+ are compatible with the class definition.
|
57
|
-
def self.compatible?(metadata
|
58
|
-
|
56
|
+
def self.compatible?(metadata)
|
57
|
+
self.difference(metadata).empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Calculates the difference between the classes metadata
|
61
|
+
# and the +metadata+ provided.
|
62
|
+
def self.difference(metadata)
|
63
|
+
my_metadata = self.metadata
|
59
64
|
other_metadata = metadata.dup
|
60
|
-
self_metadata.delete(:count)
|
61
65
|
other_metadata.delete(:count)
|
62
|
-
|
66
|
+
result = []
|
67
|
+
my_metadata.each do |type,values|
|
68
|
+
# TODO #161 the order of properties should be preserved for the
|
69
|
+
# whole class, not only for each type of properties.
|
70
|
+
if [:fields,:has_one,:has_many].include?(type)
|
71
|
+
values.to_a.zip(other_metadata[type].to_a) do |meta1,meta2|
|
72
|
+
if meta1 != meta2
|
73
|
+
result << [meta2,meta1]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
else
|
77
|
+
if other_metadata[type] != values
|
78
|
+
result << [other_metadata[type],values]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
result
|
63
83
|
end
|
64
84
|
|
65
85
|
end
|
data/lib/rod/collection_proxy.rb
CHANGED
@@ -1,26 +1,35 @@
|
|
1
|
+
require 'bsearch'
|
2
|
+
require 'rod/reference_updater'
|
3
|
+
|
1
4
|
module Rod
|
2
|
-
# This class allows for lazy fetching the
|
3
|
-
# a collection of Rod objects.
|
4
|
-
# called returns the object with given index.
|
5
|
+
# This class allows for lazy fetching the elements from
|
6
|
+
# a collection of Rod objects.
|
5
7
|
class CollectionProxy
|
6
8
|
include Enumerable
|
7
|
-
attr_reader :size
|
9
|
+
attr_reader :size, :offset
|
8
10
|
alias count size
|
9
11
|
|
10
|
-
# Intializes the proxy with +size+
|
11
|
-
#
|
12
|
+
# Intializes the proxy with its +size+, +database+ it is connected
|
13
|
+
# to, the +offset+ of join elements and the +klass+ of stored
|
14
|
+
# objects. If the klass is nil, the collection holds polymorphic
|
15
|
+
# objects.
|
12
16
|
def initialize(size,database,offset,klass)
|
13
17
|
@size = size
|
14
18
|
@original_size = size
|
15
19
|
@database = database
|
16
20
|
@klass = klass
|
17
21
|
@offset = offset
|
18
|
-
|
22
|
+
#@commands = []
|
23
|
+
@added = []
|
24
|
+
@deleted = []
|
25
|
+
@map = {}
|
19
26
|
end
|
20
27
|
|
21
28
|
# Returns an object with given +index+.
|
29
|
+
# The +index+ have to be positive and smaller from the collection size.
|
30
|
+
# Otherwise +nil+ is returned.
|
22
31
|
def [](index)
|
23
|
-
return nil if index >= @size
|
32
|
+
return nil if index >= @size || index < 0
|
24
33
|
rod_id = id_for(index)
|
25
34
|
if rod_id.is_a?(Model)
|
26
35
|
rod_id
|
@@ -33,14 +42,98 @@ module Rod
|
|
33
42
|
|
34
43
|
# Appends element to the end of the collection.
|
35
44
|
def <<(element)
|
36
|
-
if element.
|
37
|
-
|
45
|
+
if element.nil?
|
46
|
+
pair = [0,NilClass]
|
38
47
|
else
|
39
|
-
|
48
|
+
if element.new?
|
49
|
+
pair = [element,element.class]
|
50
|
+
else
|
51
|
+
pair = [element.rod_id,element.class]
|
52
|
+
end
|
40
53
|
end
|
54
|
+
index = @size
|
55
|
+
@map[index] = @added.size
|
56
|
+
@added << pair
|
57
|
+
#@commands << [:append, pair]
|
41
58
|
@size += 1
|
42
59
|
end
|
43
60
|
|
61
|
+
# Inserts the +element+ at given +index+.
|
62
|
+
# So far the +index+ has to be positive, smaller or equal to size
|
63
|
+
# and only one pair of values is accepted. If these assumptions
|
64
|
+
# are not met, nil is returned.
|
65
|
+
def insert(index,element)
|
66
|
+
return nil if index < 0 || index > @size
|
67
|
+
if element.new?
|
68
|
+
pair = [element,element.class]
|
69
|
+
else
|
70
|
+
pair = [element.rod_id,element.class]
|
71
|
+
end
|
72
|
+
@map.keys.sort.reverse.each do |key|
|
73
|
+
if key >= index
|
74
|
+
value = @map.delete(key)
|
75
|
+
@map[key+1] = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@map[index] = @added.size
|
79
|
+
@added << pair
|
80
|
+
#@commands << [:insert,pair]
|
81
|
+
@size += 1
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
# Removes the +element+ from the collection.
|
86
|
+
def delete(element)
|
87
|
+
indices = []
|
88
|
+
self.each.with_index{|e,i| indices << i if e == element}
|
89
|
+
if indices.empty?
|
90
|
+
if block_given?
|
91
|
+
return yield
|
92
|
+
else
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
#@commands << [:delete,indices]
|
97
|
+
indices.each.with_index do |index,offset|
|
98
|
+
self.delete_at(index-offset)
|
99
|
+
end
|
100
|
+
element
|
101
|
+
end
|
102
|
+
|
103
|
+
# Removes the element at +index+ from the colelction.
|
104
|
+
# So far the +index+ has to be positive.
|
105
|
+
def delete_at(index)
|
106
|
+
return nil if index >= @size || index < 0
|
107
|
+
element = self[index]
|
108
|
+
if direct_index = @map[index]
|
109
|
+
@added.delete_at(direct_index)
|
110
|
+
@map.delete(index)
|
111
|
+
@map.keys.sort.each do |key|
|
112
|
+
if key > index
|
113
|
+
value = @map.delete(key)
|
114
|
+
value -= 1 if value > direct_index
|
115
|
+
@map[key-1] = value
|
116
|
+
else
|
117
|
+
if (value = @map[key]) > direct_index
|
118
|
+
@map[key] -= 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
else
|
123
|
+
lazy_index = lazy_index(index)
|
124
|
+
position = @deleted.bsearch_upper_boundary{|e| e <=> lazy_index }
|
125
|
+
@deleted.insert(position,lazy_index)
|
126
|
+
@map.keys.sort.each do |key|
|
127
|
+
if key > index
|
128
|
+
@map[key-1] = @map.delete(key)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
#@commands << [:delete,[index]]
|
133
|
+
@size -= 1
|
134
|
+
element
|
135
|
+
end
|
136
|
+
|
44
137
|
# Simple each implementation.
|
45
138
|
def each
|
46
139
|
if block_given?
|
@@ -52,50 +145,158 @@ module Rod
|
|
52
145
|
end
|
53
146
|
end
|
54
147
|
|
55
|
-
#
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
148
|
+
# Returns a collection of added items.
|
149
|
+
def added
|
150
|
+
@added.map do |id_or_object,klass|
|
151
|
+
if id_or_object.is_a?(Model)
|
152
|
+
id_or_object
|
153
|
+
else
|
154
|
+
id_or_object == 0 ? nil : klass.find_by_rod_id(id_or_object)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns a collection of deleted items.
|
160
|
+
def deleted
|
161
|
+
@deleted.map do |index|
|
162
|
+
if polymorphic?
|
163
|
+
rod_id = @database.polymorphic_join_index(@offset,index)
|
164
|
+
if rod_id != 0
|
165
|
+
klass = Model.get_class(@database.polymorphic_join_class(@offset,index))
|
64
166
|
end
|
167
|
+
else
|
168
|
+
klass = @klass
|
169
|
+
rod_id = @database.join_index(@offset,index)
|
65
170
|
end
|
66
|
-
|
67
|
-
enum_for(:each_id)
|
171
|
+
rod_id == 0 ? nil : klass.find_by_rod_id(rod_id)
|
68
172
|
end
|
69
173
|
end
|
70
174
|
|
71
|
-
# String representation.
|
175
|
+
# String representation of the collection proxy. Displays only the actual
|
176
|
+
# and the original size.
|
72
177
|
def to_s
|
73
|
-
"
|
178
|
+
"Collection:[#{@size}][#{@original_size}]"
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns true if the collection is empty.
|
182
|
+
def empty?
|
183
|
+
self.count == 0
|
184
|
+
end
|
185
|
+
|
186
|
+
# Saves to collection proxy into disk and returns the collection
|
187
|
+
# proxy's +offset+.
|
188
|
+
# If no element was added nor deleted, nothing happes.
|
189
|
+
def save
|
190
|
+
unless @added.empty? && @deleted.empty?
|
191
|
+
# We cannot reuse the allocated space, since the data
|
192
|
+
# that is copied would be destroyed.
|
193
|
+
if polymorphic?
|
194
|
+
offset = @database.allocate_polymorphic_join_elements(@size)
|
195
|
+
else
|
196
|
+
offset = @database.allocate_join_elements(@size)
|
197
|
+
end
|
198
|
+
pairs =
|
199
|
+
@size.times.map do |index|
|
200
|
+
rod_id = id_for(index)
|
201
|
+
if rod_id.is_a?(Model)
|
202
|
+
object = rod_id
|
203
|
+
if object.new?
|
204
|
+
if polymorphic?
|
205
|
+
object.reference_updaters <<
|
206
|
+
ReferenceUpdater.for_plural(self,index,@database)
|
207
|
+
else
|
208
|
+
object.reference_updaters <<
|
209
|
+
ReferenceUpdater.for_plural(self,index,@database)
|
210
|
+
end
|
211
|
+
next
|
212
|
+
else
|
213
|
+
rod_id = object.rod_id
|
214
|
+
end
|
215
|
+
end
|
216
|
+
[rod_id,index]
|
217
|
+
end.compact
|
218
|
+
if polymorphic?
|
219
|
+
pairs.each do |rod_id,index|
|
220
|
+
class_id = (rod_id == 0 ? 0 : class_for(index).name_hash)
|
221
|
+
@database.set_polymorphic_join_element_id(offset,index,rod_id,class_id)
|
222
|
+
end
|
223
|
+
else
|
224
|
+
pairs.each do |rod_id,index|
|
225
|
+
@database.set_join_element_id(offset,index,rod_id)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
@offset = offset
|
229
|
+
@added.clear
|
230
|
+
@deleted.clear
|
231
|
+
@map.clear
|
232
|
+
@original_size = @size
|
233
|
+
end
|
234
|
+
@offset
|
74
235
|
end
|
75
236
|
|
76
237
|
protected
|
238
|
+
# Returns true if the collection proxy is polymorphic, i.e. each
|
239
|
+
# element in the collection might be an instance of a different class.
|
240
|
+
def polymorphic?
|
241
|
+
@klass.nil?
|
242
|
+
end
|
243
|
+
|
244
|
+
# Updates in the database the +rod_id+ of the referenced object,
|
245
|
+
# which is stored at given +index+.
|
246
|
+
def update_reference_id(rod_id,index)
|
247
|
+
if polymorphic?
|
248
|
+
class_id = object.class.name_hash
|
249
|
+
@database.set_polymorphic_join_element_id(@offset, index, rod_id, class_id)
|
250
|
+
else
|
251
|
+
@database.set_join_element_id(@offset, index, rod_id)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns the +rod_id+ of the element for given +index+. The
|
256
|
+
# id is taken from the DB or from in-memory map, depending
|
257
|
+
# on the fact if the collection were modified.
|
77
258
|
def id_for(index)
|
78
|
-
if
|
79
|
-
@
|
259
|
+
if direct_index = @map[index]
|
260
|
+
@added[direct_index][0]
|
80
261
|
else
|
81
|
-
if
|
82
|
-
@database.polymorphic_join_index(@offset,index)
|
262
|
+
if polymorphic?
|
263
|
+
@database.polymorphic_join_index(@offset,lazy_index(index))
|
83
264
|
else
|
84
|
-
@database.join_index(@offset,index)
|
265
|
+
@database.join_index(@offset,lazy_index(index))
|
85
266
|
end
|
86
267
|
end
|
87
268
|
end
|
88
269
|
|
270
|
+
# Returns the +class_id+ of the element for given +index+. The
|
271
|
+
# id is taken from the DB or from in-memory map, depending
|
272
|
+
# on the fact if the collection were modified.
|
89
273
|
def class_for(index)
|
90
|
-
if
|
91
|
-
|
274
|
+
if polymorphic?
|
275
|
+
if direct_index = @map[index]
|
276
|
+
@added[direct_index][1]
|
277
|
+
else
|
278
|
+
Model.get_class(@database.polymorphic_join_class(@offset,lazy_index(index)))
|
279
|
+
end
|
92
280
|
else
|
93
|
-
|
94
|
-
|
281
|
+
@klass
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Returns the index in the database corresponding to the given
|
286
|
+
# +index+ of the collection.
|
287
|
+
def lazy_index(index)
|
288
|
+
index -= @map.keys.select{|e| e < index}.size
|
289
|
+
result = 0
|
290
|
+
@deleted.each do |deleted_index|
|
291
|
+
if deleted_index - result > index
|
292
|
+
return result + index
|
95
293
|
else
|
96
|
-
|
294
|
+
index -= deleted_index - result
|
295
|
+
result = deleted_index + 1
|
97
296
|
end
|
98
297
|
end
|
298
|
+
result + index
|
99
299
|
end
|
100
300
|
end
|
101
301
|
end
|
302
|
+
|