rod 0.6.0 → 0.6.1

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