rod 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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