rod 0.6.0 → 0.6.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.
data/README CHANGED
@@ -10,17 +10,25 @@ fast access for data, which rarely changes.
10
10
  == FEATURES/PROBLEMS:
11
11
 
12
12
  * nice Ruby interface which mimicks Active Record
13
- * Ruby-to-C on-the-fly translation based on mmap
14
- * optimized for speed
13
+ * Ruby-to-C on-the-fly translation based on mmap and RubyInline
14
+ * optimized for (reading) speed
15
15
  * weak reference collections for easy memory reclaims
16
- * segmented indices for short start-up time
16
+ * segmented indices for short start-up times
17
+ * compatibility check of library version
18
+ * compatibility check of data model
19
+ * autogeneration of model (based on the database metadata)
20
+ * automatic model migrations (addition/removal of properties so far)
21
+ * append of the database (new objects, new elements in plural associations)
22
+ * databases interlinking (via direct links or inverted indices)
17
23
 
18
24
  * doesn't work on Windows
25
+ * some space is wasted during when database is appended
19
26
 
20
27
  == SYNOPSIS:
21
28
 
22
29
  ROD is designed for storing and accessing data which rarely changes.
23
- It is an opposite of RDBMS as the data is not normalized.
30
+ It is an opposite of RDBMS as the data is not normalized, while
31
+ "joins" are much faster.
24
32
  It is an opposite of in-memory databases, since it is designed to cover
25
33
  out of core data sets (10 GB and more).
26
34
 
@@ -30,17 +38,15 @@ is interconnected in many ways, thus the relational model (joins) introduces
30
38
  unacceptable performance hit. The size of corpora forces them to be kept
31
39
  on disks. The in-memory data bases are unacceptable for larg corpora and
32
40
  would require the data to be kept mostly in the operational memory,
33
- which is not needed, while accessing dictionaries (in most cases only a friction
34
- of the data is needed). That's why a storage facility which minimizes the
35
- number of disk reads was designed. The Ruby interface facilitates it's
36
- usage.
41
+ which is not needed, while accessing dictionaries (in most cases only a fraction
42
+ of the data is used at the same time). That's why a storage facility which minimizes the
43
+ number of disk reads was designed. The Ruby interface facilitates it's usage.
37
44
 
38
45
  == REQUIREMENTS:
39
46
 
40
47
  * RubyInline
41
48
  * english
42
49
  * ActiveModel
43
- * weak_hash
44
50
 
45
51
  == INSTALL
46
52
 
@@ -77,23 +83,21 @@ Grab from rubygems:
77
83
  end
78
84
 
79
85
  MyDatabase.create_database("data")
80
- user = User.new
81
- user.name = 'Fred'
82
- user.surname = 'Smith'
83
- user.age = 22
84
- account = Account.new
85
- account.email = "fred@smith.org"
86
- account.login = "fred"
87
- account.password = "password"
88
- file1 = File.new
89
- file1.title = "Lady Gaga video"
86
+ user = User.new(:name => 'Fred',
87
+ :surname => 'Smith',
88
+ :age => 22)
89
+ account = Account.new(:email => "fred@smith.org",
90
+ :login => "fred",
91
+ :password => "password")
92
+ file1 = File.new(:title => "Lady Gaga video")
90
93
  file2.data = "0012220001..."
91
- file2 = File.new
92
- file2.title = "Pink Floyd video"
94
+ file2 = File.new(:title => "Pink Floyd video")
93
95
  file2.data = "0012220001..."
96
+
94
97
  user.account = account
95
98
  user.files << file1
96
99
  user.files << file2
100
+
97
101
  user.store
98
102
  account.store
99
103
  file1.store
@@ -118,7 +122,7 @@ Grab from rubygems:
118
122
 
119
123
  (The MIT License)
120
124
 
121
- Copyright (c) 2008-2010 Aleksander Pohl
125
+ Copyright (c) 2008-2011 Aleksander Pohl
122
126
 
123
127
  Permission is hereby granted, free of charge, to any person obtaining
124
128
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  $:.unshift "lib"
2
2
  require 'rod/constants'
3
3
 
4
- task :default => [:install]
4
+ task :default => [:all_tests]
5
5
 
6
6
  $gem_name = "rod"
7
7
 
@@ -22,7 +22,7 @@ task :uninstall do
22
22
  sh "sudo gem uninstall #$gem_name"
23
23
  end
24
24
 
25
- task :all_tests => [:test,:spec,:regression_test] do
25
+ task :all_tests => [:test,:regression_test,:spec] do
26
26
  end
27
27
 
28
28
  desc "Run performence tests"
@@ -36,9 +36,18 @@ desc "Run tests and specs"
36
36
  task :test do
37
37
  sh "ruby tests/save_struct.rb"
38
38
  sh "ruby tests/load_struct.rb"
39
+ sh "ruby tests/class_compatibility_create.rb"
40
+ sh "ruby tests/class_compatibility_verify.rb"
41
+ sh "ruby tests/generate_classes_create.rb"
42
+ sh "ruby tests/generate_classes_rewrite.rb"
43
+ sh "ruby tests/generate_classes_rewrite.rb"
44
+ sh "ruby tests/generate_classes_verify.rb"
45
+ sh "ruby tests/migration_create.rb"
46
+ sh "ruby tests/migration_migrate.rb"
47
+ sh "ruby tests/migration_verify.rb"
39
48
  sh "ruby tests/unit/model.rb"
40
49
  sh "ruby tests/unit/model_tests.rb"
41
- sh "ruby tests/unit/abstract_database.rb"
50
+ sh "ruby tests/unit/database.rb"
42
51
  end
43
52
 
44
53
  # Should be removed some time -- specs should cover all these cases
@@ -48,7 +57,12 @@ task :regression_test do
48
57
  end
49
58
 
50
59
  task :spec do
51
- sh "bundle exec cucumber features/*"
60
+ sh "bundle exec cucumber --tags ~@ignore features/*"
61
+ end
62
+
63
+ # Work in progress
64
+ task :wip do
65
+ sh "bundle exec cucumber --tags @wip features/*"
52
66
  end
53
67
 
54
68
  desc "Clean"
data/changelog.txt CHANGED
@@ -1,3 +1,36 @@
1
+ 0.6.1
2
+ - Change order of tests in all_tests
3
+ - Update DB 'created_at' if empty
4
+ - #112 fix: indices are rewritten during migration
5
+ - #110 bulk allocate for objects durign migration
6
+ - #111 back-up of database.yml during migration
7
+ - Fix: uninitilized module name
8
+ - Make incompatible model version message more inteligible
9
+ - Make index rewrite/convert option more meaningful
10
+ - #108 fix: indices are destroyed for read/write mode
11
+ - #81 automatic schema transformation
12
+ - #104 remove ruby inline generated files when model is generated
13
+ - tests/read_on_create.rb remove ruby inline and data files during creation
14
+ - #100 fix: removal of RubyInline generated files
15
+ - #43 generate classes from metadata
16
+ - #43 generate classes without module embedding
17
+ - #96 fix: find_by for assocs
18
+ - #95 remove weak_hash dependency
19
+ - #25 check compatibility of class structure
20
+ - #87 store supreclass of a class in metadata
21
+ - #26 check version of library: fix revision check - Add ~@ignore tag as default for specs
22
+ - #93 fix: storage of has_many appended objects - Turn on full object referential integrity
23
+ - #65 store DB creation and update time in metadata
24
+ - #89 Replace SimpleWeakHash with Cache
25
+ - #90 Remove blocks from collection proxy
26
+ - #80 tests and proper fix for segfault on _allocate_polymorphic
27
+ - Add check for index of already stored object
28
+ - #73 append of has many relationship
29
+ - #82 index (flat -> segmented) transformation tool
30
+ - #86 Cache indexed properties (optimiz.)
31
+ - Remove legacy examples
32
+ - #80 sefault on _allocate_polymorphic_join_elements
33
+ - #79 fix: reading indices during DB creation causes an error
1
34
  0.6.0
2
35
  - #64 index for associations
3
36
  - Update legacy test to new API
data/lib/rod.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'inline'
2
2
  require 'english/inflect'
3
- require 'simple_weak_hash'
4
3
  require 'active_model'
5
4
  require 'active_support/dependencies'
6
5
 
@@ -10,10 +9,12 @@ require 'active_support/dependencies'
10
9
  ActiveSupport::Dependencies.mechanism = :require
11
10
 
12
11
  require 'rod/abstract_database'
12
+ require 'rod/abstract_model'
13
13
  require 'rod/constants'
14
14
  require 'rod/database'
15
15
  require 'rod/exception'
16
16
  require 'rod/join_element'
17
+ require 'rod/cache'
17
18
  require 'rod/collection_proxy'
18
19
  require 'rod/model'
19
20
  require 'rod/string_element'
@@ -1,6 +1,7 @@
1
1
  require 'singleton'
2
2
  require 'yaml'
3
3
  require 'rod/segmented_index'
4
+ require 'fileutils'
4
5
 
5
6
  module Rod
6
7
  # This class implements the database abstraction, i.e. it
@@ -12,9 +13,12 @@ module Rod
12
13
  # a given model (set of classes).
13
14
  include Singleton
14
15
 
16
+ # The meta-data of the DataBase.
17
+ attr_reader :metadata
18
+
15
19
  # Initializes the classes linked with this database and the handler.
16
20
  def initialize
17
- @classes ||= self.class.special_classes
21
+ @classes ||= self.special_classes
18
22
  @handler = nil
19
23
  end
20
24
 
@@ -44,54 +48,127 @@ module Rod
44
48
  def create_database(path)
45
49
  raise DatabaseError.new("Database already opened.") unless @handler.nil?
46
50
  @readonly = false
47
- self.classes.each{|s| s.send(:build_structure)}
48
51
  @path = canonicalize_path(path)
49
- # XXX maybe should be more careful?
50
52
  if File.exist?(@path)
51
- Dir.glob("#{@path}**/*").each do |file_name|
52
- File.delete(file_name) unless File.directory?(file_name)
53
- end
53
+ remove_file("#{@path}database.yml")
54
54
  else
55
55
  Dir.mkdir(@path)
56
56
  end
57
+ self.classes.each do |klass|
58
+ klass.send(:build_structure)
59
+ remove_file(klass.path_for_data(@path))
60
+ klass.indexed_properties.each do |property,options|
61
+ path = klass.path_for_index(@path,property,options)
62
+ if test(?d,path)
63
+ remove_files(path + "*")
64
+ elsif test(?f,path)
65
+ remove_file(path)
66
+ end
67
+ end
68
+ next if special_class?(klass)
69
+ remove_files_but(klass.inline_library)
70
+ end
57
71
  generate_c_code(@path, classes)
72
+ remove_files_but(self.inline_library)
73
+ @metadata = {}
74
+ @metadata["Rod"] = {}
75
+ @metadata["Rod"][:created_at] = Time.now
58
76
  @handler = _init_handler(@path)
59
77
  _create(@handler)
60
78
  end
61
79
 
62
- # Opens the database at +path+ for readonly mode. This allows
80
+ # Opens the database at +path+ with +options+. This allows
63
81
  # for Rod::Model.count, Rod::Model.each, and similar calls.
64
- #
65
- # By default the database is opened in +readonly+ mode. You
66
- # can change it by passing +false+ as the second argument.
67
- def open_database(path,readonly=true)
82
+ # Options:
83
+ # * +:readonly+ - no modifiaction (append of models and has many association)
84
+ # is allowed (defaults to +true+)
85
+ # * +:generate+ - value could be true or a module. If present, generates
86
+ # the classes from the database metadata. If module given, the classes
87
+ # are generated withing the module.
88
+ def open_database(path,options={:readonly => true})
68
89
  raise DatabaseError.new("Database already opened.") unless @handler.nil?
69
- @readonly = readonly
70
- self.classes.each{|s| s.send(:build_structure)}
90
+ options = convert_options(options)
91
+ @readonly = options[:readonly]
71
92
  @path = canonicalize_path(path)
72
- generate_c_code(@path, classes)
73
- metadata = {}
93
+ @metadata = {}
74
94
  File.open(@path + DATABASE_FILE) do |input|
75
- metadata = YAML::load(input)
95
+ @metadata = YAML::load(input)
76
96
  end
77
- unless valid_version?(metadata["Rod"][:version])
78
- raise RodException.new("Incompatible versions - library #{VERSION} vs. file #{metatdata["Rod"][:version]}")
97
+ unless valid_version?(@metadata["Rod"][:version])
98
+ raise IncompatibleVersion.new("Incompatible versions - library #{VERSION} vs. " +
99
+ "file #{metatdata["Rod"][:version]}")
79
100
  end
101
+ if options[:generate]
102
+ module_instance = (options[:generate] == true ? Object : options[:generate])
103
+ generate_classes(module_instance)
104
+ elsif options[:migrate]
105
+ create_legacy_classes
106
+ FileUtils.cp(@path + DATABASE_FILE, @path + DATABASE_FILE + LEGACY_DATA_SUFFIX)
107
+ end
108
+ self.classes.each do |klass|
109
+ klass.send(:build_structure)
110
+ next if special_class?(klass)
111
+ if options[:generate] && module_instance != Object
112
+ remove_files_but(klass.inline_library)
113
+ end
114
+ end
115
+ generate_c_code(@path, self.classes)
80
116
  @handler = _init_handler(@path)
81
117
  self.classes.each do |klass|
82
- meta = metadata[klass.name]
118
+ meta = @metadata[klass.name]
83
119
  if meta.nil?
84
120
  # new class
85
121
  next
86
122
  end
123
+ unless klass.compatible?(meta,self) || options[:generate] || options[:migrate]
124
+ raise IncompatibleVersion.
125
+ new("Incompatible definition of '#{klass.name}' class.\n" +
126
+ "Database and runtime versions are different:\n" +
127
+ " #{meta}\n #{klass.metadata(self)}")
128
+ end
87
129
  set_count(klass,meta[:count])
88
130
  file_size = File.new(klass.path_for_data(@path)).size
89
131
  unless file_size % _page_size == 0
90
132
  raise DatabaseError.new("Size of data file of #{klass} is invalid: #{file_size}")
91
133
  end
92
134
  set_page_count(klass,file_size / _page_size)
135
+ if options[:migrate]
136
+ next unless klass.name =~ LEGACY_RE
137
+ new_class = klass.name.sub(LEGACY_RE,"").constantize
138
+ set_count(new_class,meta[:count])
139
+ pages = (meta[:count] * new_class.struct_size / _page_size.to_f).ceil
140
+ set_page_count(new_class,pages)
141
+ end
93
142
  end
94
143
  _open(@handler)
144
+ if options[:migrate]
145
+ empty_data = "\0" * _page_size
146
+ self.classes.each do |klass|
147
+ next unless klass.to_s =~ LEGACY_RE
148
+ new_class = klass.name.sub(LEGACY_RE,"").constantize
149
+ old_metadata = klass.metadata(self)
150
+ old_metadata.merge!({:superclass => old_metadata[:superclass].sub(LEGACY_RE,"")})
151
+ unless new_class.compatible?(old_metadata,self)
152
+ File.open(new_class.path_for_data(@path),"w") do |out|
153
+ send("_#{new_class.struct_name}_page_count",@handler).
154
+ times{|i| out.print(empty_data)}
155
+ end
156
+ klass.migrate
157
+ current_file_name = klass.path_for_data(@path)
158
+ legacy_file_name = current_file_name + LEGACY_DATA_SUFFIX
159
+ new_file_name = new_class.path_for_data(@path)
160
+ FileUtils.mv(current_file_name,legacy_file_name)
161
+ FileUtils.mv(new_file_name,current_file_name)
162
+ end
163
+ @classes.delete(klass)
164
+ new_class.model_path = nil
165
+ end
166
+ close_database(false,true)
167
+ options.delete(:migrate)
168
+ readonly = options.delete(:old_readonly)
169
+ options[:readonly] = readonly
170
+ open_database(path,options)
171
+ end
95
172
  end
96
173
 
97
174
  # Closes the database.
@@ -99,58 +176,36 @@ module Rod
99
176
  # If the +purge_classes+ flag is set to true, the information about the classes
100
177
  # linked with this database is removed. This is important for testing, when
101
178
  # classes with same names have different definitions.
102
- def close_database(purge_classes=false)
179
+ #
180
+ # If the +skip_indeces+ flat is set to true, the indices are not written.
181
+ def close_database(purge_classes=false,skip_indices=false)
103
182
  raise DatabaseError.new("Database not opened.") if @handler.nil?
104
183
 
105
184
  unless readonly_data?
106
185
  unless referenced_objects.select{|k, v| not v.empty?}.size == 0
107
186
  raise DatabaseError.new("Not all associations have been stored: #{referenced_objects.size} objects")
108
187
  end
109
- metadata = {}
110
- rod_data = metadata["Rod"] = {}
111
- rod_data[:version] = VERSION
112
- self.classes.each do |klass|
113
- meta = metadata[klass.name] = {}
114
- meta[:count] = count(klass)
115
- next if special_class?(klass)
116
- # fields
117
- fields = meta[:fields] = {} unless klass.fields.empty?
118
- klass.fields.each do |field,options|
119
- fields[field] = {}
120
- fields[field][:options] = options
121
- write_index(klass,field,options) if options[:index]
122
- end
123
- # singular_associations
124
- has_one = meta[:has_one] = {} unless klass.singular_associations.empty?
125
- klass.singular_associations.each do |name,options|
126
- has_one[name] = {}
127
- has_one[name][:options] = options
128
- write_index(klass,name,options) if options[:index]
129
- end
130
- # plural_associations
131
- has_many = meta[:has_many] = {} unless klass.plural_associations.empty?
132
- klass.plural_associations.each do |name,options|
133
- has_many[name] = {}
134
- has_many[name][:options] = options
135
- write_index(klass,name,options) if options[:index]
188
+ unless skip_indices
189
+ self.classes.each do |klass|
190
+ klass.indexed_properties.each do |property,options|
191
+ write_index(klass,property,options)
192
+ end
136
193
  end
137
194
  end
138
- File.open(@path + DATABASE_FILE,"w") do |out|
139
- out.puts(YAML::dump(metadata))
140
- end
195
+ write_metadata
141
196
  end
142
197
  _close(@handler)
143
198
  @handler = nil
144
199
  # clear cached data
145
200
  self.clear_cache
146
201
  if purge_classes
147
- @classes = self.class.special_classes
202
+ @classes = self.special_classes
148
203
  end
149
204
  end
150
205
 
151
206
  # Clears the cache of the database.
152
207
  def clear_cache
153
- classes.each{|c| c.cache.send(:__get_hash__).clear}
208
+ classes.each{|c| c.cache.clear }
154
209
  end
155
210
 
156
211
  #########################################################################
@@ -264,19 +319,20 @@ module Rod
264
319
  def write_index(klass,property,options)
265
320
  raise DatabaseError.new("Readonly database.") if readonly_data?
266
321
  class_index = klass.index_for(property,options)
267
- class_index.each do |key,ids|
268
- unless ids.is_a?(CollectionProxy)
269
- proxy = CollectionProxy.new(ids[1]) do |index|
270
- [join_index(ids[0],index), klass]
322
+ # Only convert the index, without (re)storing the values.
323
+ unless options[:convert]
324
+ class_index.each do |key,ids|
325
+ unless ids.is_a?(CollectionProxy)
326
+ proxy = CollectionProxy.new(ids[1],self,ids[0],klass)
327
+ else
328
+ proxy = ids
271
329
  end
272
- else
273
- proxy = ids
274
- end
275
- offset = _allocate_join_elements(proxy.size,@handler)
276
- proxy.each_id.with_index do |rod_id,index|
277
- set_join_element_id(offset, index, rod_id)
330
+ offset = _allocate_join_elements(proxy.size,@handler)
331
+ proxy.each_id.with_index do |rod_id,index|
332
+ set_join_element_id(offset, index, rod_id)
333
+ end
334
+ class_index[key] = [offset,proxy.size]
278
335
  end
279
- class_index[key] = [offset,proxy.size]
280
336
  end
281
337
  case options[:index]
282
338
  when :flat,true
@@ -301,17 +357,21 @@ module Rod
301
357
  # Store the object in the database.
302
358
  def store(klass,object)
303
359
  raise DatabaseError.new("Readonly database.") if readonly_data?
304
- send("_store_" + klass.struct_name,object,@handler)
305
- # set fields' values
306
- object.class.fields.each do |name,options|
307
- # rod_id is set during _store
308
- object.update_field(name) unless name == "rod_id"
309
- end
310
- # set ids of objects referenced via singular associations
311
- object.class.singular_associations.each do |name,options|
312
- object.update_singular_association(name,object.send(name))
360
+ new_object = (object.rod_id == 0)
361
+ if new_object
362
+ send("_store_" + klass.struct_name,object,@handler)
363
+ # set fields' values
364
+ object.class.fields.each do |name,options|
365
+ # rod_id is set during _store
366
+ object.update_field(name) unless name == "rod_id"
367
+ end
368
+ # set ids of objects referenced via singular associations
369
+ object.class.singular_associations.each do |name,options|
370
+ object.update_singular_association(name,object.send(name))
371
+ end
313
372
  end
314
373
  # set ids of objects referenced via plural associations
374
+ # TODO should be disabled, when there are no new elements
315
375
  object.class.plural_associations.each do |name,options|
316
376
  elements = object.send(name) || []
317
377
  if options[:polymorphic]
@@ -345,7 +405,7 @@ module Rod
345
405
  library = VERSION.split(".")
346
406
  return false if file[0] != library[0] || file[1] != library[1]
347
407
  if library[1].to_i.even?
348
- return true
408
+ return file[2].to_i <= library[2].to_i
349
409
  else
350
410
  return file[2] == library[2]
351
411
  end
@@ -362,8 +422,141 @@ module Rod
362
422
  end
363
423
 
364
424
  # Special classes used by the database.
365
- def self.special_classes
425
+ def special_classes
366
426
  [JoinElement, PolymorphicJoinElement, StringElement]
367
427
  end
428
+
429
+ def convert_options(options)
430
+ result = {}
431
+ case options
432
+ when true,false
433
+ result[:readonly] = options
434
+ when Hash
435
+ result = options
436
+ if options[:migrate]
437
+ result[:old_readonly] = options[:readonly]
438
+ result[:readonly] = false
439
+ end
440
+ else
441
+ raise RodException.new("Invalid options for open_database: #{options}!")
442
+ end
443
+ result[:readonly] = true if result[:readonly].nil?
444
+ result
445
+ end
446
+
447
+ # Generates the classes for the data using the metadata from database.yml
448
+ # +module_instance+ is the module in which the classes are generated.
449
+ # This allows for embedding them in a separate namespace and use the same model
450
+ # with different databases in the same time.
451
+ def generate_classes(module_instance)
452
+ special_names = special_classes.map{|k| k.name}
453
+ special_names << "Rod"
454
+ superclasses = {}
455
+ @metadata.reject{|k,o| special_names.include?(k)}.each do |k,o|
456
+ superclasses[k] = o[:superclass]
457
+ end
458
+ superclass_tree = {}
459
+ superclasses.each do |klass,superclass|
460
+ superclass_tree[klass] = []
461
+ current_superclass = superclass
462
+ loop do
463
+ break if current_superclass.nil?
464
+ superclass_tree[klass] << current_superclass
465
+ break if current_superclass == "Rod::Model"
466
+ current_superclass = superclasses[current_superclass]
467
+ end
468
+ end
469
+ superclasses.keys.sort do |klass1,klass2|
470
+ if superclass_tree[klass1].include?(klass2)
471
+ 1
472
+ elsif superclass_tree[klass2].include?(klass1)
473
+ -1
474
+ else
475
+ klass1 <=> klass2
476
+ end
477
+ end.each do |klass_name|
478
+ metadata = @metadata[klass_name]
479
+ original_name = klass_name
480
+ if module_instance != Object
481
+ prefix = module_instance.name + "::"
482
+ if superclasses.keys.include?(metadata[:superclass])
483
+ metadata[:superclass] = prefix + metadata[:superclass]
484
+ end
485
+ [:fields,:has_one,:has_many].each do |property_type|
486
+ next if metadata[property_type].nil?
487
+ metadata[property_type].each do |property,options|
488
+ if superclasses.keys.include?(options[:options][:class_name])
489
+ metadata[property_type][property][:options][:class_name] =
490
+ prefix + options[:options][:class_name]
491
+ end
492
+ end
493
+ end
494
+ # klass name
495
+ klass_name = prefix + klass_name
496
+ @metadata.delete(original_name)
497
+ @metadata[klass_name] = metadata
498
+ end
499
+ klass = Model.generate_class(klass_name,metadata)
500
+ klass.model_path = Model.struct_name_for(original_name)
501
+ @classes << klass
502
+ klass.database_class(self.class)
503
+ end
504
+ end
505
+
506
+ # During migration it creats the classes which are used to read
507
+ # the legacy data. It also changes the path for the
508
+ # actual classes not to conflict with paths of legacy data.
509
+ def create_legacy_classes
510
+ legacy_module = nil
511
+ begin
512
+ legacy_module = Object.const_get(LEGACY_MODULE)
513
+ rescue NameError
514
+ legacy_module = Module.new
515
+ Object.const_set(LEGACY_MODULE,legacy_module)
516
+ end
517
+ self.classes.each do |klass|
518
+ next if special_class?(klass)
519
+ klass.model_path = Model.struct_name_for(klass.name) + NEW_DATA_SUFFIX
520
+ end
521
+ generate_classes(legacy_module)
522
+ end
523
+
524
+ # Removes single file.
525
+ def remove_file(file_name)
526
+ if test(?f,file_name)
527
+ File.delete(file_name)
528
+ puts "Removing #{file_name}" if $ROD_DEBUG
529
+ end
530
+ end
531
+
532
+ # Remove all files matching the +pattern+.
533
+ # If +skip+ given, the file with the given name is not deleted.
534
+ def remove_files(pattern,skip=nil)
535
+ Dir.glob(pattern).each do |file_name|
536
+ remove_file(file_name) unless file_name == skip
537
+ end
538
+ end
539
+
540
+ # Removes all files which are similar (i.e. are generated
541
+ # by RubyInline for the same class) to +name+
542
+ # excluding the file with exactly the name given.
543
+ def remove_files_but(name)
544
+ remove_files(name.sub(INLINE_PATTERN_RE,"*"),name)
545
+ end
546
+
547
+ # Writes the metadata to the database.yml file.
548
+ def write_metadata
549
+ metadata = {}
550
+ rod_data = metadata["Rod"] = {}
551
+ rod_data[:version] = VERSION
552
+ rod_data[:created_at] = self.metadata["Rod"][:created_at] || Time.now
553
+ rod_data[:updated_at] = Time.now
554
+ self.classes.each do |klass|
555
+ metadata[klass.name] = klass.metadata(self)
556
+ end
557
+ File.open(@path + DATABASE_FILE,"w") do |out|
558
+ out.puts(YAML::dump(metadata))
559
+ end
560
+ end
368
561
  end
369
562
  end