rod 0.7.0 → 0.7.1

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.
@@ -1,3 +1,24 @@
1
+ 0.7.1
2
+ - #189 guard object load against empty strings.
3
+ - Don't close hash index when it is destroyed.
4
+ - #188 don't raise exception when HashIndex closed
5
+ - Make the HashIndex exceptions more informative.
6
+ - #187 collection proxy is cleared in assignment
7
+ - Tiny change of diagnostic messages
8
+ - #174 progress for fields migration
9
+ - #181 store only added/updated hash index pairs
10
+ - #180 save collections in index no in Abstract DB
11
+ - #179 raise exception during iteration
12
+ - Fix saving of empty collection proxy.
13
+ - #176 copy index file if its type is the same
14
+ - #168 copy unmodified classes during migration
15
+ - #166 true index type still supported
16
+ - #171 encode strings to utf-8 when searching in hash index
17
+ - #170 fix save for hash index
18
+ - #169 missing each implementation in hash index
19
+ - #167 copy special classes data to backup
20
+ - #165 fix invalid topological class sorting algorithm
21
+ - #164 invalid variable in load metadata
1
22
  0.7.0
2
23
  - #142 migrate indices during model migration
3
24
  - #134 Berkeley DB Hash for indexing properties
@@ -138,3 +138,8 @@ Feature: collection proxy specification
138
138
  When I delete an item at position 0 5 times
139
139
  Then the collection proxy should be empty
140
140
  And the collection proxy should behave like an array
141
+
142
+ Scenario: modifying collection during interation
143
+ Given the initial size of the collection proxy is 5
144
+ # Can't figure out anything better.
145
+ Then an exception should be raised when the collection is modified during iteration
@@ -87,3 +87,16 @@ Then /^the collection proxy should be empty$/ do
87
87
  @proxy.to_a.should be_empty
88
88
  end
89
89
 
90
+ #Then an exception should be raised when the collection is modified during iteration
91
+ Then /^an exception should be raised when the collection is modified during iteration$/ do
92
+ (lambda do
93
+ @proxy.each do |item|
94
+ @proxy << create_item(@offset)
95
+ end
96
+ end).should raise_exception
97
+ (lambda do
98
+ @proxy.each do |item|
99
+ @proxy.detete_at(0)
100
+ end
101
+ end).should raise_exception
102
+ end
data/lib/rod.rb CHANGED
@@ -7,6 +7,8 @@ require 'active_model/translation'
7
7
  require 'active_model/validations'
8
8
  require 'active_model/dirty'
9
9
  require 'active_support/dependencies'
10
+ require 'active_support/deprecation/behaviors'
11
+ require 'active_support/deprecation/reporting'
10
12
 
11
13
  # XXX This should be done in a different way, since a library should not
12
14
  # impose on a user of another library specific way of using it.
@@ -159,6 +159,12 @@ module Rod
159
159
  @metadata = load_metadata
160
160
  create_legacy_classes
161
161
  FileUtils.mkdir_p(@path + BACKUP_PREFIX)
162
+ # Copy special classes data.
163
+ special_classes.each do |klass|
164
+ file = klass.path_for_data(@path)
165
+ puts "Copying #{file} to #{@path + BACKUP_PREFIX}" if $ROD_DEBUG
166
+ FileUtils.cp(file,@path + BACKUP_PREFIX)
167
+ end
162
168
  Dir.glob(@path + "*").each do |file|
163
169
  # Don't move the directory itself and speciall classes data.
164
170
  unless file.to_s == @path + BACKUP_PREFIX[0..-2] ||
@@ -218,7 +224,7 @@ module Rod
218
224
  unless skip_indices
219
225
  self.classes.each do |klass|
220
226
  klass.indexed_properties.each do |property,options|
221
- write_index(klass,property,options)
227
+ klass.index_for(property,options).save
222
228
  end
223
229
  end
224
230
  end
@@ -357,27 +363,6 @@ module Rod
357
363
  send("_#{klass.struct_name}_page_count=",@handler,value)
358
364
  end
359
365
 
360
- # Store index of +field+ (with +options+) of +klass+ in the database.
361
- # There are two types of indices:
362
- # * +:flat+ - marshalled index is stored in one file
363
- # * +:segmented+ - marshalled index is stored in many files
364
- def write_index(klass,property,options)
365
- raise DatabaseError.new("Readonly database.") if readonly_data?
366
- class_index = klass.index_for(property,options)
367
- if options[:convert]
368
- # Only convert the index, without (re)storing the values.
369
- index = Index::Base.create(klass.path_for_index(@path,property),klass,options)
370
- index.copy(class_index)
371
- class_index = index
372
- else
373
- class_index.each do |key,proxy|
374
- proxy.save
375
- end
376
- end
377
- class_index.save
378
- class_index = nil
379
- end
380
-
381
366
  # Store the object in the database.
382
367
  def store(klass,object)
383
368
  raise DatabaseError.new("Readonly database.") if readonly_data?
@@ -411,7 +396,7 @@ module Rod
411
396
  end
412
397
  unless valid_version?(metadata["Rod"][:version])
413
398
  raise IncompatibleVersion.new("Incompatible versions - library #{VERSION} vs. " +
414
- "file #{metatdata["Rod"][:version]}")
399
+ "file #{metadata["Rod"][:version]}")
415
400
  end
416
401
  metadata
417
402
  end
@@ -467,29 +452,20 @@ module Rod
467
452
  special_names = special_classes.map{|k| k.name}
468
453
  special_names << "Rod"
469
454
  superclasses = {}
455
+ inverted_superclasses = {}
470
456
  @metadata.reject{|k,o| special_names.include?(k)}.each do |k,o|
471
457
  superclasses[k] = o[:superclass]
458
+ inverted_superclasses[o[:superclass]] ||= []
459
+ inverted_superclasses[o[:superclass]] << k
472
460
  end
473
- superclass_tree = {}
474
- superclasses.each do |klass,superclass|
475
- superclass_tree[klass] = []
476
- current_superclass = superclass
477
- loop do
478
- break if current_superclass.nil?
479
- superclass_tree[klass] << current_superclass
480
- break if current_superclass == "Rod::Model"
481
- current_superclass = superclasses[current_superclass]
482
- end
483
- end
484
- superclasses.keys.sort do |klass1,klass2|
485
- if superclass_tree[klass1].include?(klass2)
486
- 1
487
- elsif superclass_tree[klass2].include?(klass1)
488
- -1
489
- else
490
- klass1 <=> klass2
491
- end
492
- end.each do |klass_name|
461
+ queue = inverted_superclasses["Rod::Model"] || []
462
+ sorted = []
463
+ begin
464
+ klass = queue.shift
465
+ sorted << klass
466
+ queue.concat(inverted_superclasses[klass] || [])
467
+ end until queue.empty?
468
+ sorted.each do |klass_name|
493
469
  metadata = @metadata[klass_name]
494
470
  original_name = klass_name
495
471
  if module_instance != Object
@@ -14,11 +14,14 @@ module Rod
14
14
  # objects. If the klass is nil, the collection holds polymorphic
15
15
  # objects.
16
16
  def initialize(size,database,offset,klass)
17
+ raise InvalidArgument.new("collection size",nil) if size.nil?
17
18
  @size = size
18
19
  @original_size = size
20
+ raise InvalidArgument.new("collection database",nil) if database.nil?
19
21
  @database = database
20
- @klass = klass
22
+ raise InvalidArgument.new("collection offset",nil) if offset.nil?
21
23
  @offset = offset
24
+ @klass = klass
22
25
  #@commands = []
23
26
  @added = []
24
27
  @deleted = []
@@ -134,11 +137,26 @@ module Rod
134
137
  element
135
138
  end
136
139
 
137
- # Simple each implementation.
140
+ # Clears the contents of the collection proxy.
141
+ def clear
142
+ @deleted = @original_size.times.to_a
143
+ @added.clear
144
+ @map.clear
145
+ @size = 0
146
+ end
147
+
148
+ # Iterator implementation. It raises an exception when
149
+ # the collection is modified during iteration.
150
+ # WARNING: This is not compliant with an Array class!
138
151
  def each
139
152
  if block_given?
140
153
  @size.times do |index|
154
+ added_size = @added.size
155
+ deleted_size = @deleted.size
141
156
  yield self[index]
157
+ if added_size != @added.size || deleted_size != @deleted.size
158
+ raise "Can't modify collection during iteration!"
159
+ end
142
160
  end
143
161
  else
144
162
  enum_for(:each)
@@ -1,5 +1,5 @@
1
1
  module Rod
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.1"
3
3
 
4
4
  # The name of file containing the data base.
5
5
  DATABASE_FILE = "database.yml"
@@ -13,6 +13,10 @@ module Rod
13
13
  # which are used to retrive and assign the values respectively.
14
14
  class Base
15
15
  include Utils
16
+
17
+ # The path of the index.
18
+ attr_reader :path
19
+
16
20
  # Sets the class this index belongs to.
17
21
  def initialize(klass)
18
22
  @klass = klass
@@ -38,7 +42,7 @@ module Rod
38
42
  proxy = get(key)
39
43
  end
40
44
  if proxy.nil?
41
- proxy = CollectionProxy.new(0,@klass.database,nil,@klass)
45
+ proxy = CollectionProxy.new(0,@klass.database,0,@klass)
42
46
  else
43
47
  unless proxy.is_a?(CollectionProxy)
44
48
  offset, count = proxy
@@ -56,8 +60,10 @@ module Rod
56
60
 
57
61
  # Copies the values from +index+ to this index.
58
62
  def copy(index)
59
- index.each do |key,value|
60
- self.set(key,value)
63
+ index.each.with_index do |key_value,position|
64
+ self.set(key_value[0],key_value[1])
65
+ # TODO #182 implement size for index
66
+ # report_progress(position,index.size) if $ROD_DEBUG
61
67
  end
62
68
  end
63
69
 
@@ -74,6 +80,11 @@ module Rod
74
80
  set(key,proxy)
75
81
  end
76
82
 
83
+ # The default representation shows the index class and path.
84
+ def to_s
85
+ "#{self.class}@#{@path}"
86
+ end
87
+
77
88
  class << self
78
89
  # Creats the proper instance of Index or one of its sublcasses.
79
90
  # The +path+ is the path were the index is stored, while +index+ is the previous index instance.
@@ -89,6 +100,10 @@ module Rod
89
100
  SegmentedIndex.new(path,klass,options)
90
101
  when :hash
91
102
  HashIndex.new(path,klass,options)
103
+ when true
104
+ ActiveSupport::Deprecation.
105
+ warn("Index type 'true' is deprecated. It will be removed in ROD 0.8.0")
106
+ FlatIndex.new(path,klass,options)
92
107
  else
93
108
  raise RodException.new("Invalid index type #{type}")
94
109
  end
@@ -16,11 +16,18 @@ module Rod
16
16
 
17
17
  # Stores the index on disk.
18
18
  def save
19
+ # The index was not loaded nor modified.
20
+ return if @index.nil?
19
21
  File.open(@path,"w") do |out|
20
22
  proxy_index = {}
21
23
  #load_index unless loaded?
22
- @index.each{|k,col| proxy_index[k] = [col.offset,col.size]}
24
+ @index.each_key do |key|
25
+ col = self[key]
26
+ col.save
27
+ proxy_index[key] = [col.offset,col.size]
28
+ end
23
29
  out.puts(Marshal.dump(proxy_index))
30
+ @index = nil
24
31
  end
25
32
  end
26
33
 
@@ -6,7 +6,7 @@ module Rod
6
6
  # This implementation of index is based on the
7
7
  # Berkeley DB Hash access method.
8
8
  class HashIndex < Base
9
- # Wrapper class for the database struct.
9
+ # Wrapper class for the database C struct.
10
10
  class Handle
11
11
  end
12
12
 
@@ -15,23 +15,30 @@ module Rod
15
15
  def initialize(path,klass,options={})
16
16
  @path = path + ".db"
17
17
  @klass = klass
18
- _open(@path,:create => true)
19
- @index = {}
18
+ open(@path,:create => true)
20
19
  end
21
20
 
22
21
  # Stores the index on disk.
23
22
  def save
24
- @index.each do |key,collection|
23
+ raise RodException.new("The index #{self} is not opened!") unless opened?
24
+ if @index.empty?
25
+ close
26
+ return
27
+ end
28
+ @index.keys.each do |key|
29
+ collection = self[key]
30
+ key = key.encode("utf-8") if key.is_a?(String)
25
31
  key = Marshal.dump(key)
32
+ collection.save
26
33
  _put(key,collection.offset,collection.size)
27
34
  end
28
- _close()
35
+ close
29
36
  end
30
37
 
31
38
  # Clears the contents of the index.
32
39
  def destroy
33
- _close()
34
- _open(@path,:truncate => true)
40
+ close if opened?
41
+ open(@path,:truncate => true)
35
42
  end
36
43
 
37
44
  # Simple iterator.
@@ -40,7 +47,9 @@ module Rod
40
47
  @index.each do |key,value|
41
48
  yield key,value
42
49
  end
43
- _each do |key,value|
50
+ open(@path) unless opened?
51
+ _each_key do |key|
52
+ next if key.empty?
44
53
  key = Marshal.load(key)
45
54
  unless @index[key]
46
55
  yield key,self[key]
@@ -51,10 +60,46 @@ module Rod
51
60
  end
52
61
  end
53
62
 
63
+ # Copies the index from the given +index+.
64
+ # The index have to cleared before being copied.
65
+ def copy(index)
66
+ close if opened?
67
+ open(@path,:truncate => true)
68
+ super(index)
69
+ end
70
+
54
71
  protected
72
+ # Opens the index - initializes the index C structures
73
+ # and the cache.
74
+ # Options:
75
+ # * +:truncate+ - clears the contents of the index
76
+ # * +:create+ - creates the index if it doesn't exist
77
+ def open(path,options={})
78
+ raise RodException.new("The index #{@path} is already opened!") if opened?
79
+ _open(path,options)
80
+ @opened = true
81
+ @index = {} if @index.nil?
82
+ end
83
+
84
+ # Closes the disk - frees the C structure and clears the cache.
85
+ def close
86
+ return unless opened?
87
+ _close()
88
+ @opened = false
89
+ @index.clear
90
+ end
91
+
92
+ # Checks if the index is opened.
93
+ def opened?
94
+ @opened
95
+ end
96
+
97
+ # Returns a value of the index for a given +key+.
55
98
  def get(key)
56
99
  return @index[key] if @index.has_key?(key)
57
100
  begin
101
+ open(@path) unless opened?
102
+ key = key.encode("utf-8") if key.is_a?(String)
58
103
  value = _get(Marshal.dump(key))
59
104
  rescue Rod::KeyMissing => ex
60
105
  value = nil
@@ -62,11 +107,12 @@ module Rod
62
107
  @index[key] = value
63
108
  end
64
109
 
110
+ # Sets the +value+ for the +key+ in the internal cache.
65
111
  def set(key,value)
66
112
  @index[key] = value
67
113
  end
68
114
 
69
-
115
+ # C definition of the RodException.
70
116
  def self.rod_exception
71
117
  str =<<-END
72
118
  |VALUE rodException(){
@@ -78,6 +124,7 @@ module Rod
78
124
  str.margin
79
125
  end
80
126
 
127
+ # C definition of the index struct.
81
128
  def self.entry_struct
82
129
  str =<<-END
83
130
  |typedef struct rod_entry {
@@ -88,33 +135,24 @@ module Rod
88
135
  str.margin
89
136
  end
90
137
 
138
+ # Converts the key to the C representation.
91
139
  def self.convert_key
92
140
  str =<<-END
93
- |DBT _convert_key(VALUE key){
141
+ |void _convert_key(VALUE key, DBT *db_key_p){
94
142
  | long int_key;
95
143
  | double float_key;
96
144
  | DBT db_key;
97
145
  |
146
+ | db_key = *db_key_p;
98
147
  | memset(&db_key, 0, sizeof(DBT));
99
- | if(rb_obj_is_kind_of(key,rb_cInteger)){
100
- | int_key = NUM2LONG(key);
101
- | db_key.data = &int_key;
102
- | db_key.size = sizeof(long);
103
- | } else if(rb_obj_is_kind_of(key,rb_cFloat)){
104
- | float_key = NUM2DBL(key);
105
- | db_key.data = &float_key;
106
- | db_key.size = sizeof(double);
107
- | } else {
108
- | db_key.data = RSTRING_PTR(key);
109
- | db_key.size = RSTRING_LEN(key);
110
- | }
111
- | // is it legal?
112
- | return db_key;
148
+ | db_key.data = RSTRING_PTR(key);
149
+ | db_key.size = RSTRING_LEN(key);
113
150
  |}
114
151
  END
115
152
  str.margin
116
153
  end
117
154
 
155
+ # The C definition of the KeyMissing exception.
118
156
  def self.key_missing_exception
119
157
  str =<<-END
120
158
  |VALUE keyMissingException(){
@@ -191,8 +229,34 @@ module Rod
191
229
  builder.c(str.margin)
192
230
 
193
231
  str =<<-END
194
- |void _each(){
232
+ |void _each_key(){
233
+ | VALUE handle;
234
+ | DB *db_pointer;
235
+ | DBC *cursor;
236
+ | DBT db_key, db_value;
237
+ | int return_value;
238
+ | rod_entry_struct *entry;
239
+ | VALUE key;
195
240
  |
241
+ | handle = rb_iv_get(self,"@handle");
242
+ | Data_Get_Struct(handle,DB,db_pointer);
243
+ | if(db_pointer != NULL){
244
+ | db_pointer->cursor(db_pointer,NULL,&cursor,0);
245
+ | memset(&db_key, 0, sizeof(DBT));
246
+ | memset(&db_value, 0, sizeof(DBT));
247
+ | db_key.flags = DB_DBT_MALLOC;
248
+ | while((return_value = cursor->get(cursor, &db_key, &db_value, DB_NEXT)) == 0){
249
+ | key = rb_str_new((char *)db_key.data,db_key.size);
250
+ | free(db_key.data);
251
+ | rb_yield(key);
252
+ | }
253
+ | if(return_value != DB_NOTFOUND){
254
+ | rb_raise(rodException(),"%s",db_strerror(return_value));
255
+ | }
256
+ | cursor->close(cursor);
257
+ | } else {
258
+ | rb_raise(rodException(),"DB handle is NULL\\n");
259
+ | }
196
260
  |}
197
261
  END
198
262
  builder.c(str.margin)
@@ -210,10 +274,14 @@ module Rod
210
274
  | Data_Get_Struct(handle,DB,db_pointer);
211
275
  | if(db_pointer != NULL){
212
276
  | memset(&db_value, 0, sizeof(DBT));
213
- | db_key = _convert_key(key);
214
277
  | db_value.data = &entry;
215
278
  | db_value.ulen = sizeof(rod_entry_struct);
216
279
  | db_value.flags = DB_DBT_USERMEM;
280
+ |
281
+ | memset(&db_key, 0, sizeof(DBT));
282
+ | db_key.data = RSTRING_PTR(key);
283
+ | db_key.size = RSTRING_LEN(key);
284
+ |
217
285
  | return_value = db_pointer->get(db_pointer, NULL, &db_key, &db_value, 0);
218
286
  | if(return_value == DB_NOTFOUND){
219
287
  | rb_raise(keyMissingException(),"%s",db_strerror(return_value));
@@ -243,16 +311,18 @@ module Rod
243
311
  |
244
312
  | handle = rb_iv_get(self,"@handle");
245
313
  | Data_Get_Struct(handle,DB,db_pointer);
314
+ | memset(&db_key, 0, sizeof(DBT));
315
+ | db_key.data = RSTRING_PTR(key);
316
+ | db_key.size = RSTRING_LEN(key);
246
317
  | memset(&db_value, 0, sizeof(DBT));
247
318
  | entry.offset = offset;
248
319
  | entry.size = size;
249
- | db_key = _convert_key(key);
250
320
  | db_value.data = &entry;
251
321
  | db_value.size = sizeof(rod_entry_struct);
252
322
  | if(db_pointer != NULL){
253
323
  | return_value = db_pointer->put(db_pointer, NULL, &db_key, &db_value, 0);
254
324
  | if(return_value != 0){
255
- | rb_raise(keyMissingException(),"%s",db_strerror(return_value));
325
+ | rb_raise(rodException(),"%s",db_strerror(return_value));
256
326
  | }
257
327
  | } else {
258
328
  | rb_raise(rodException(),"DB handle is NULL\\n");
@@ -27,7 +27,11 @@ module Rod
27
27
  @buckets.each do |bucket_number,hash|
28
28
  File.open(path_for(bucket_number),"w") do |out|
29
29
  proxy_index = {}
30
- hash.each{|k,col| proxy_index[k] = [col.offset,col.size]}
30
+ hash.each_key do |key|
31
+ col = self[key]
32
+ col.save
33
+ proxy_index[key] = [col.offset,col.size]
34
+ end
31
35
  out.puts(Marshal.dump(proxy_index))
32
36
  end
33
37
  end
@@ -8,6 +8,7 @@ module Rod
8
8
  class Model < AbstractModel
9
9
  include ActiveModel::Validations
10
10
  include ActiveModel::Dirty
11
+ extend Utils
11
12
  extend Enumerable
12
13
 
13
14
  # A list of updaters that has to be notified when the +rod_id+
@@ -259,11 +260,11 @@ module Rod
259
260
  if properties[property][:index].nil?
260
261
  raise RodException.new("Property '#{property}' doesn't have an index!")
261
262
  end
262
- index_for(property,properties[property]).destroy
263
- instance_variable_set("@#{property}_index",nil)
264
263
  index = index_for(property,properties[property])
265
- self.each do |object|
264
+ index.destroy
265
+ self.each.with_index do |object,position|
266
266
  index[object.send(property)] << object
267
+ report_progress(position,self.count) if $ROD_DEBUG
267
268
  end
268
269
  end
269
270
 
@@ -476,7 +477,19 @@ module Rod
476
477
  old_metadata = self.metadata
477
478
  old_metadata.merge!({:superclass => old_metadata[:superclass].sub(LEGACY_RE,"")})
478
479
  new_class = self.name.sub(LEGACY_RE,"").constantize
479
- return if new_class.compatible?(old_metadata)
480
+ if new_class.compatible?(old_metadata)
481
+ backup_path = self.path_for_data(database.path)
482
+ new_path = new_class.path_for_data(database.path)
483
+ puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
484
+ FileUtils.cp(backup_path,new_path)
485
+ new_class.indexed_properties.each do |name,options|
486
+ backup_path = self.index_for(name,options).path
487
+ new_path = new_class.index_for(name,options).path
488
+ puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
489
+ FileUtils.cp(backup_path,new_path)
490
+ end
491
+ return
492
+ end
480
493
  database.send(:allocate_space,new_class)
481
494
 
482
495
  puts "Migrating #{new_class}" if $ROD_DEBUG
@@ -509,22 +522,26 @@ module Rod
509
522
  old_object.send("_#{name}_length",position+1))
510
523
  new_object.send("_#{name}_offset=",position+1,
511
524
  old_object.send("_#{name}_offset",position+1))
525
+ report_progress(position,self.count) if $ROD_DEBUG
512
526
  end
513
527
  else
514
528
  self.count.times do |position|
515
529
  new_object.send("_#{name}=",position + 1,
516
530
  old_object.send("_#{name}",position + 1))
531
+ report_progress(position,self.count) if $ROD_DEBUG
517
532
  end
518
533
  end
519
534
  elsif self.singular_association?(name)
520
535
  self.count.times do |position|
521
536
  new_object.send("_#{name}=",position + 1,
522
537
  old_object.send("_#{name}",position + 1))
538
+ report_progress(position,self.count) if $ROD_DEBUG
523
539
  end
524
540
  if options[:polymorphic]
525
541
  self.count.times do |position|
526
542
  new_object.send("_#{name}__class=",position + 1,
527
543
  old_object.send("_#{name}__class",position + 1))
544
+ report_progress(position,self.count) if $ROD_DEBUG
528
545
  end
529
546
  end
530
547
  else
@@ -533,9 +550,10 @@ module Rod
533
550
  old_object.send("_#{name}_count",position + 1))
534
551
  new_object.send("_#{name}_offset=",position + 1,
535
552
  old_object.send("_#{name}_offset",position + 1))
553
+ report_progress(position,self.count) if $ROD_DEBUG
536
554
  end
537
555
  end
538
- puts "done" if $ROD_DEBUG
556
+ puts " done" if $ROD_DEBUG
539
557
  end
540
558
  # Migrate the indices.
541
559
  new_class.indexed_properties.each do |name,options|
@@ -544,14 +562,18 @@ module Rod
544
562
  if old_index_type.nil?
545
563
  print "- building index #{options[:index]} for '#{name}'... " if $ROD_DEBUG
546
564
  new_class.rebuild_index(name)
547
- puts "done" if $ROD_DEBUG
565
+ puts " done" if $ROD_DEBUG
566
+ elsif options[:index] == old_index_type
567
+ backup_path = self.index_for(name,options).path
568
+ new_path = new_class.index_for(name,options).path
569
+ puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
570
+ FileUtils.cp(backup_path,new_path)
548
571
  else
549
- # TODO if index is the same, its file should be copied
550
572
  print "- copying index #{options[:index]} for '#{name}'... " if $ROD_DEBUG
551
573
  new_index = new_class.index_for(name,options)
552
574
  old_index = index_for(name,self.properties[name])
553
575
  new_index.copy(old_index)
554
- puts "done" if $ROD_DEBUG
576
+ puts " done" if $ROD_DEBUG
555
577
  end
556
578
  end
557
579
  end
@@ -978,7 +1000,7 @@ module Rod
978
1000
  end
979
1001
  value = database.read_string(length, offset, options)
980
1002
  if options[:type] == :object
981
- value = Marshal.load(value)
1003
+ value = Marshal.load(value) rescue nil
982
1004
  end
983
1005
  # caching Ruby representation
984
1006
  # don't use writer - avoid change tracking
@@ -1085,6 +1107,7 @@ module Rod
1085
1107
  # setter
1086
1108
  define_method("#{name}=") do |value|
1087
1109
  proxy = send(name)
1110
+ proxy.clear
1088
1111
  value.each do |object|
1089
1112
  proxy << object
1090
1113
  end
@@ -25,5 +25,20 @@ module Rod
25
25
  def remove_files_but(name)
26
26
  remove_files(name.sub(INLINE_PATTERN_RE,"*"),name)
27
27
  end
28
+
29
+ # Reports progress of long-running operation.
30
+ # The +index+ is the current operation's step
31
+ # of +count+ steps.
32
+ def report_progress(index,count)
33
+ step = (count.to_f/50).to_i
34
+ return if step == 0
35
+ if index % step == 0
36
+ if index % (5 * step) == 0
37
+ print "#{(index / (5 * step) * 10)}%"
38
+ else
39
+ print "."
40
+ end
41
+ end
42
+ end
28
43
  end
29
44
  end
@@ -49,4 +49,7 @@ count.times do |index|
49
49
  users << user2
50
50
  end
51
51
 
52
+ house = House.new(:name => "cottage house")
53
+ house.store
54
+
52
55
  Database.instance.close_database
@@ -36,4 +36,7 @@ count.times do |index|
36
36
  user.store
37
37
  end
38
38
 
39
+ # Force the creation of index key with empty proxy.
40
+ House.find_by_name("doesn't exist")
41
+
39
42
  Database.instance.close_database
@@ -29,3 +29,7 @@ class UserFile < Model
29
29
  field :data, :string
30
30
  field :path, :string
31
31
  end
32
+
33
+ class House < Model
34
+ field :name, :string, :index => :hash
35
+ end
@@ -70,3 +70,7 @@ class UserFile < Model
70
70
  field :name, :string, :index => :flat
71
71
  end
72
72
 
73
+ # The whole class is the same.
74
+ class House < Model
75
+ field :name, :string, :index => :hash
76
+ end
@@ -8,9 +8,17 @@ Rod::Database.development_mode = true
8
8
  Database.instance.open_database("tmp/migration")
9
9
 
10
10
  count = (ARGV[0] || 10).to_i
11
+ key_count = 0
12
+ User.index_for(:street,:index => :hash).each do |key,proxy|
13
+ key_count += 1
14
+ proxy.to_a.should == User.find_all_by_street(key).to_a
15
+ proxy.count.should > 0
16
+ end
17
+ key_count.should > 0
11
18
  count.times do |index|
12
19
  user1 = User[index*2]
13
20
  user1.should_not == nil
21
+ user1.name.should == "John#{index}"
14
22
  user = User.find_by_name("John#{index}")
15
23
  user.should == user1
16
24
  users = User.find_all_by_city("City#{index}")
@@ -41,7 +49,7 @@ count.times do |index|
41
49
  user = User.find_by_name("Amanda#{index}")
42
50
  user.should == user2
43
51
  user = User.find_by_city("Bigcity#{index}")
44
- #user.should == user2
52
+ user.should == user2
45
53
  user = User.find_by_street("Small street#{index}")
46
54
  user.should == user2
47
55
  user2.name.should == "Amanda#{index}"
@@ -72,4 +80,10 @@ users.size.should == 2
72
80
  users[0].should == User[0]
73
81
  users[1].should == User[1]
74
82
 
83
+ house = House.find_by_name("cottage house")
84
+ house.should_not == nil
85
+ house.name.should == "cottage house"
86
+ house = House.find_by_name("doesn't exist")
87
+ house.should == nil
88
+
75
89
  Database.instance.close_database
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: rod
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.7.0
5
+ version: 0.7.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Aleksander Pohl
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-09-12 00:00:00 Z
13
+ date: 2011-11-01 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: RubyInline