rod 0.6.2 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|