og 0.29.0 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -140,7 +140,7 @@ class Manager
140
140
  # Manage a class. Converts the class to an Entity.
141
141
 
142
142
  def manage(klass)
143
- return if managed?(klass) or klass.ancestors.include?(Unmanageable)
143
+ return if managed?(klass) or Og.unmanageable_classes.include?(klass)
144
144
 
145
145
  info = EntityInfo.new(klass)
146
146
 
@@ -173,12 +173,14 @@ class Manager
173
173
  end
174
174
 
175
175
  # Is this class manageable by Og?
176
- #--
177
- # FIXME: investigate this (polymorphic/unmanageable).
178
- #++
179
-
176
+ #
177
+ # Unmanageable classes include classes:
178
+ # * without properties
179
+ # * explicitly marked as Unmanageable (is Og::Unamanageable)
180
+ # * are polymorphic_parents (ie thay are used to spawn polymorphic relations)
181
+
180
182
  def manageable?(klass)
181
- klass.respond_to?(:properties) and (!klass.properties.empty?) # and klass.ann.self.polymorphic.nil?
183
+ klass.respond_to?(:properties) and (!klass.properties.empty?) and (!Og.unmanageable_classes.include?(klass)) and (!klass.polymorphic_parent?)
182
184
  end
183
185
 
184
186
  # Is the class managed by Og?
@@ -201,6 +203,7 @@ class Manager
201
203
  def manageable_classes
202
204
  classes = []
203
205
 
206
+ # for c in Property.classes
204
207
  ObjectSpace.each_object(Class) do |c|
205
208
  if manageable?(c)
206
209
  classes << c
@@ -1,9 +1,17 @@
1
1
  module Og
2
2
 
3
+ # This file contains a collection of 'marker' modules. You can
4
+ # include these modules to your classes to pass hints to the
5
+ # object manager (typically Og).
6
+
3
7
  # Marker module. If included in a class, the Og automanager
4
8
  # ignores this class.
5
9
 
6
- module Unmanageable; end
10
+ module Unmanageable;
11
+ def self.included(base)
12
+ Og.unmanageable_classes << base
13
+ end
14
+ end
7
15
 
8
16
  # This is a marker module that denotes that the
9
17
  # base class follows the SingleTableInheritance
@@ -94,7 +94,8 @@ class Relation
94
94
  end
95
95
 
96
96
  def to_s
97
- @options[:target_name]
97
+ self.class.to_s.underscore
98
+ # @options[:target_name]
98
99
  end
99
100
 
100
101
  # Access the hash values as methods.
@@ -138,8 +139,11 @@ class Relation
138
139
  def resolve_targets(klass)
139
140
  for r in klass.relations
140
141
  if r.target_class.is_a? Symbol
141
- klass = symbol_to_class(r.target_class, r.owner_class)
142
- r.options[:target_class] = klass
142
+ if klass = symbol_to_class(r.target_class, r.owner_class)
143
+ r.options[:target_class] = klass
144
+ else
145
+ Logger.error "Cannot resolve target class '#{r.target_class}' for relation '#{r}' of class '#{r.owner_class}'!"
146
+ end
143
147
  end
144
148
  end
145
149
  end
@@ -208,9 +212,9 @@ class Relation
208
212
 
209
213
  unless r[target_name]
210
214
  r[target_name] = if r.collection
211
- r.target_class.to_s.demodulize.underscore.downcase.plural.intern
215
+ r.target_class.to_s.demodulize.underscore.downcase.plural.to_sym
212
216
  else
213
- r.target_class.to_s.demodulize.underscore.downcase.intern
217
+ r.target_class.to_s.demodulize.underscore.downcase.to_sym
214
218
  end
215
219
  end
216
220
 
@@ -31,13 +31,13 @@ class JoinsMany < Relation
31
31
  if sclass = owner_class.ann.self[:superclass]
32
32
  join_class = sclass
33
33
  end
34
- join_table_info = store.join_table_info(join_class, target_class)
34
+ join_table_info = store.join_table_info(self)
35
35
 
36
36
  if through = self[:through]
37
37
  # A custom class is used for the join. Use the class
38
38
  # table and don't create a new table.
39
39
  through = Relation.symbol_to_class(through, owner_class) if through.is_a?(Symbol)
40
- join_table = self[:join_table] = through.table
40
+ join_table = self[:join_table] = store.table(through)
41
41
  else
42
42
  # Calculate the name of the join table
43
43
  join_table = self[:join_table] = join_table_info[:table]
@@ -236,10 +236,10 @@ private
236
236
 
237
237
  klass.class_eval %{
238
238
  def og_insert(store)
239
- #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
239
+ #{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
240
240
  @#{pk} = store.conn[#{klass}].size + 1
241
241
  store.conn[#{klass}][@#{pk}] = self
242
- #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
242
+ #{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
243
243
  end
244
244
  }
245
245
  end
@@ -251,9 +251,9 @@ private
251
251
 
252
252
  klass.class_eval %{
253
253
  def og_update(store, options)
254
- #{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
254
+ #{::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
255
255
  store.conn[#{klass}][@#{pk}] = self
256
- #{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
256
+ #{::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
257
257
  end
258
258
  }
259
259
  end
@@ -264,8 +264,8 @@ private
264
264
  def eval_og_read(klass)
265
265
  klass.class_eval %{
266
266
  def og_read
267
- #{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
268
- #{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
267
+ #{::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
268
+ #{::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
269
269
  end
270
270
  }
271
271
  end
@@ -273,7 +273,7 @@ private
273
273
  def eval_og_delete(klass)
274
274
  klass.module_eval %{
275
275
  def og_delete(store, pk, cascade = true)
276
- #{Glue::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
276
+ #{::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
277
277
  pk ||= @#{klass.primary_key.first}
278
278
  transaction do |tx|
279
279
  tx.conn[#{klass}].delete(pk)
@@ -283,7 +283,7 @@ private
283
283
  end
284
284
  end
285
285
  end
286
- #{Glue::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
286
+ #{::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
287
287
  end
288
288
  }
289
289
  end
@@ -174,7 +174,7 @@ private
174
174
  rescue => ex
175
175
  # gmosx: any idea how to better test this?
176
176
  if ex.errno == 1050 # table already exists.
177
- Logger.debug 'Join table already exists'
177
+ Logger.debug 'Join table already exists' if $DBG
178
178
  else
179
179
  raise
180
180
  end
@@ -242,11 +242,11 @@ private
242
242
 
243
243
  klass.class_eval %{
244
244
  def og_insert(store)
245
- #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
245
+ #{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
246
246
  store.conn.query_with_result = false
247
247
  store.conn.query "#{sql}"
248
248
  @#{klass.pk_symbol} = store.conn.insert_id
249
- #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
249
+ #{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
250
250
  end
251
251
  }
252
252
  end
@@ -10,226 +10,374 @@ require 'fileutils'
10
10
  require 'og/store/sql'
11
11
 
12
12
  module Og
13
-
14
- # A Store that persists objects into a KirbyBase database.
15
- # To read documentation about the methods, consult the documentation
16
- # for SqlStore and Store.
17
-
18
- class KirbyStore < SqlStore
19
-
20
- # Override if needed.
21
-
22
- def self.base_dir(options)
23
- options[:base_dir] || './kirbydb'
13
+
14
+ #Fix for schema inheritance. Instead of inferring the property on each
15
+ #access of the database we add it explicitly when the marker module
16
+ #is imported. Currently this fix is applied for this store only. The
17
+ #other stores should be similarly altered.
18
+ module SchemaInheritanceBase
19
+ property :ogtype, String
24
20
  end
21
+
22
+ # A Store that persists objects into a KirbyBase database.
23
+ # To read documentation about the methods, consult the documentation
24
+ # for SqlStore and Store.
25
+
26
+ class KirbyStore < SqlStore
27
+
28
+ Text = :Text
25
29
 
26
- def self.destroy(options)
27
- begin
28
- FileUtils.rm_rf(base_dir(options))
30
+ # Override if needed.
31
+
32
+ def self.base_dir(options)
33
+ options[:base_dir] || './kirbydb'
34
+ end
35
+
36
+ def self.destroy(options)
37
+ begin
38
+ FileUtils.rm_rf(base_dir(options))
39
+ super
40
+ rescue Object
41
+ Logger.info 'Cannot drop database!'
42
+ end
43
+ end
44
+
45
+ def initialize(options)
29
46
  super
30
- rescue Object
31
- Logger.info 'Cannot drop database!'
47
+
48
+ @typemap = {
49
+ Integer => :Integer,
50
+ Fixnum => :Integer,
51
+ Float => :Float,
52
+ String => :String,
53
+ Time => :Time,
54
+ Date => :Date,
55
+ TrueClass => :Boolean,
56
+ FalseClass => :Boolean,
57
+ Object => :Object,
58
+ Array => :String,
59
+ Hash => :String
60
+ }
61
+
62
+ mode = options[:mode] || :local
63
+
64
+ if mode == :client
65
+ # Use a client/server configuration.
66
+ @conn = KirbyBase.new(:client, options[:address], options[:port])
67
+ else
68
+ # Use an in-process configuration.
69
+ base_dir = self.class.base_dir(options)
70
+ FileUtils.mkdir_p(base_dir) unless File.exist?(base_dir)
71
+ @conn = KirbyBase.new(
72
+ :local,
73
+ nil, nil,
74
+ base_dir,
75
+ options[:ext] || '.tbl'
76
+ )
77
+ end
32
78
  end
33
- end
34
-
35
- def initialize(options)
36
- super
37
79
 
38
- @typemap = {
39
- :Fixnum => :Integer,
40
- :TrueClass => :Boolean
41
- }
42
-
43
- mode = options[:mode] || :local
44
-
45
- if mode == :client
46
- # Use a client/server configuration.
47
- @conn = KirbyBase.new(:client, options[:address], options[:port])
48
- else
49
- # Use an in-process configuration.
50
- base_dir = self.class.base_dir(options)
51
- FileUtils.mkdir_p(base_dir) unless File.exist?(base_dir)
52
- @conn = KirbyBase.new(
53
- :local,
54
- nil, nil,
55
- base_dir,
56
- options[:ext] || '.tbl'
57
- )
80
+ def close
81
+ # Nothing to do.
82
+ super
58
83
  end
59
- end
60
-
61
- def close
62
- # Nothing to do.
63
- super
64
- end
65
-
66
- def enchant(klass, manager)
67
- klass.send :attr_accessor, :oid
68
- klass.send :alias_method, :recno, :oid
69
- klass.send :alias_method, :recno=, :oid=
70
84
 
71
- symbols = klass.properties.keys
85
+ def enchant(klass, manager)
86
+ klass.send(:attr_accessor, :oid)
87
+ klass.send(:alias_method, :recno, :oid)
88
+ klass.send(:alias_method, :recno=, :oid=)
89
+
90
+ super
91
+ end
72
92
 
73
- klass.module_eval %{
74
- def self.kb_create(recno, #{symbols.join(', ')})
75
- obj = self.allocate
76
- obj.recno = recno
77
- #{ symbols.map { |s| "obj.#{s} = #{s}; "} }
78
- return obj
93
+ def get_table(klass)
94
+ @conn.get_table(klass.table.to_sym)
95
+ end
96
+
97
+ # :section: Lifecycle methods.
98
+
99
+ def load(pk, klass)
100
+ convert_to_instance(klass, get_table(klass)[pk.to_i])
101
+ end
102
+
103
+ #convert the given record into an instance of the given class.
104
+ #Supports schema_inheritance.
105
+ def convert_to_instance(klass, record)
106
+ return nil unless record
107
+
108
+ if(klass.schema_inheritance?)
109
+ obj = eval("#{record.ogtype}.allocate")
110
+ elsif
111
+ obj = klass.allocate
79
112
  end
80
- }
81
-
82
- super
83
- end
84
-
85
- def get_table(klass)
86
- @conn.get_table(klass.table.to_sym)
87
- end
88
-
89
- # :section: Lifecycle methods.
90
-
91
- def load(pk, klass)
92
- get_table(klass)[pk.to_i]
93
- end
94
- alias_method :exist?, :load
95
-
96
- def reload(obj, pk)
97
- raise 'Cannot reload unmanaged object' unless obj.saved?
98
- new_obj = load(pk, obj.class)
99
- obj.clone(new_obj)
100
- end
101
-
102
- def find(options)
103
- query(options)
104
- end
105
-
106
- def find_one(options)
107
- query(options).first
108
- end
109
-
110
- #--
111
- # FIXME: optimize me!
112
- #++
113
-
114
- def count(options)
115
- find(options).size
116
- end
117
-
118
- def query(options)
119
- Logger.debug "Querying with #{options.inspect}." if $DBG
120
-
121
- klass = options[:class]
122
- table = get_table(klass)
123
113
 
124
- objects = []
125
-
126
- if condition = options[:condition] || options[:where]
127
- condition.gsub!(/=/, '==')
128
- condition.gsub!(/LIKE '(.*?)'/, '=~ /\1/')
129
- condition.gsub!(/\%/, '')
130
- condition.gsub!(/(\w*?)\s?=(.)/, 'o.\1 =\2')
131
- objects = eval("table.select { |o| #{condition} }")
132
- else
133
- objects = table.select
114
+ record.each_pair do |k, v|
115
+ assign_sym = (k.to_s + '=').to_sym
116
+ if (v && obj.respond_to?(assign_sym))
117
+ obj.method(assign_sym).call(v)
118
+ end
119
+ end
120
+ obj
134
121
  end
135
-
136
- if order = options[:order]
137
- order = order.to_s
138
- desc = (order =~ /DESC/)
139
- order = order.gsub(/DESC/, '').gsub(/ASC/, '')
140
- eval "objects.sort { |x, y| x.#{order} <=> y.#{order} }"
141
- objects.reverse! if desc
122
+
123
+ alias_method :exist?, :load
124
+
125
+ def reload(obj, pk)
126
+ raise 'Cannot reload unmanaged object' unless obj.saved?
127
+ new_obj = load(pk, obj.class)
128
+ #TODO: replace obj with new_obj
142
129
  end
143
-
144
- return objects
145
- end
146
-
147
- def start
148
- # nop
149
- end
150
-
151
- # Commit a transaction.
152
-
153
- def commit
154
- # nop, not supported?
155
- end
156
-
157
- # Rollback a transaction.
158
-
159
- def rollback
160
- # nop, not supported?
161
- end
162
-
163
- def sql_update(sql)
164
- # nop, not supported.
165
- end
166
-
167
- def join(obj1, obj2, table, options = nil)
168
- first, second = join_object_ordering(obj1, obj2)
169
- @conn.get_table(table.to_sym).insert(first.pk, second.pk)
170
- end
171
-
172
- def unjoin(obj1, obj2, table)
173
- first, second = join_object_ordering(obj1, obj2)
174
-
175
- @conn.get_table(table.to_sym).delete do |r|
176
- require 'dev-utils/debug'
177
- breakpoint
178
- r.send(:first_key) == first.pk and
179
- r.send(:second_key) == second.pk
130
+
131
+ def find(options)
132
+ #FIXME: this currently overrides options with scope; this should
133
+ #work the other way around
134
+ if scope = options[:class].get_scope
135
+ scope = scope.dup
136
+ options.update(scope)
137
+ end
138
+ query(options)
180
139
  end
181
- end
182
140
 
183
- private
141
+ def find_one(options)
142
+ query(options).first
143
+ end
144
+
145
+ #--
146
+ # FIXME: optimize me!
147
+ #++
148
+
149
+ def count(options)
150
+ find(options).size
151
+ end
152
+
153
+ #--
154
+ # FIXME: optimize me!
155
+ #
156
+ #++
157
+
158
+ def aggregate(term = 'COUNT(*)', options = {})
159
+ case term.upcase
160
+ when /COUNT/
161
+ count(options)
162
+ when /MIN/
163
+ term =~ /MIN\((\w*)\)/
164
+ find_column_values($1, options){|result| result.min}
165
+ when /MAX/
166
+ term =~ /MAX\((\w*)\)/
167
+ find_column_values($1, options) {|result| result.max}
168
+ when /AVG/
169
+ term =~ /AVG\((\w*)\)/
170
+ values = find_column_values($1, options) do |results|
171
+ results.inject(0.0){|sum, result| sum + result} / results.size
172
+ end
173
+ when /SUM/
174
+ term =~ /SUM\((\w*)\)/
175
+ values = find_column_values($1, options) do |results|
176
+ results.inject(0.0){|sum, result| sum + result}
177
+ end
178
+ end
179
+ end
180
+ alias_method :calculate, :aggregate
181
+
182
+ #find an array of values for the given column; or a hash of arrays
183
+ #if there is a group by expression. Takes an optional block
184
+ #that operates on each value in the array.
185
+
186
+ def find_column_values(column_name, options)
187
+ options[:column] = column_name
188
+ results = find(options)
189
+ if (block_given?)
190
+ if (options[:group]) #hash of arrays
191
+ results.values.collect { |group| yield group}
192
+ else
193
+ yield results
194
+ end
195
+ end
196
+ end
184
197
 
185
- def typemap(key)
186
- @typemap[key] || key
187
- end
198
+ #needs refactoring, badly
199
+ def query(options)
200
+ Logger.debug "Querying with #{options.inspect}." if $DBG
201
+
202
+ klass = options[:class]
203
+
204
+ table = get_table(klass)
205
+ result_set = []
206
+ if (klass.schema_inheritance_child?)
207
+ type_condition = 'ogtype = \'' + klass.name + '\''
208
+ condition = options[:condition]
209
+
210
+ if (condition)
211
+ #if there is already an ogtype condition do nothing
212
+ if (condition !~ /ogtype/)
213
+ options[:condition] = condition + " and #{type_condition}"
214
+ end
215
+ else
216
+ options[:condition] = type_condition
217
+ end
218
+ end
219
+
220
+ #FIXME: add support for select statements with mathematics (tc_select)
221
+ #as well as 'from table' statements
222
+ if condition = options[:condition] || options[:where]
223
+ condition = condition.is_a?(Array) ? prepare_statement(condition) : condition
224
+ condition.gsub!(/ogtc_resolveoptions\w*\./, '')
225
+ condition.gsub!(/([^><])=/, '\1==')
226
+ condition.gsub!(/ OR /, ' or ')
227
+ condition.gsub!(/ AND /, ' and ')
228
+ condition.gsub!(/LIKE '(.*?)'/, '=~ /\1/')
229
+ condition.gsub!(/\%/, '')
230
+ condition.gsub!(/(\w*?)\s?([=><])(.)/, 'o.\1 \2\3')
231
+ condition.gsub!(/o.oid/, 'o.recno')
232
+ if(condition =~ /LIMIT (\d+)/)
233
+ condition.gsub!(/LIMIT (\d+)/, '')
234
+ options[:limit] = $1.to_i
235
+ end
236
+ result_set = eval("table.select do |o| #{condition} end")
237
+ else
238
+ result_set = table.select
239
+ end
240
+
241
+ if (order = options[:order])
242
+ order = order.to_s
243
+ desc = (order =~ /DESC/)
244
+ order = order.gsub(/DESC/, '').gsub(/ASC/, '')
245
+ eval("result_set.sort { |x, y| x.#{order} <=> y.#{order} }")
246
+ result_set.reverse! if desc
247
+ end
248
+
249
+ if (options[:limit])
250
+ limit = options[:limit]
251
+ if (result_set.size > limit)
252
+ result_set = result_set[0, limit]
253
+ end
254
+ end
255
+
256
+ if (column = options[:column])
257
+ if (!result_set.methods.include?(column))
258
+ return []
259
+ end
260
+ begin
261
+ #if there is a group by expression on a valid column
262
+ #gather arrays of values
263
+ if ((group = options[:group]) && result_set.method(options[:group]))
264
+ #store an empty array under the key if there is no value
265
+ groups = Hash.new {|groups, key| groups[key] = []}
266
+ result_set.each do |object|
267
+ groups[object.method(group).call] << object.method(column).call
268
+ end
269
+ return groups
270
+ end
271
+ rescue NameError => error
272
+ Logger.warn 'Attempt to group by missing column:'
273
+ Logger.warn 'Column \'' + options[:group].to_s + '\' does not exist on table \'' + klass.name + '\''
274
+ raise error
275
+ end
276
+
277
+ #when called on a resultset a column method returns an array of that
278
+ #column's values
279
+ return result_set.method(column).call()
280
+ end
188
281
 
189
- def create_table(klass)
190
- if @conn.table_exists?(klass.table.to_sym)
191
- get_table(klass).pack # Kirby specific method of database cleanup.
282
+ result_set.map { |object| convert_to_instance(klass, object) }
283
+ end
192
284
 
193
- field_names = field_names_for_class(klass)
194
- actual_fields = get_table(klass).field_names
285
+ def start
286
+ # nop
287
+ end
288
+
289
+ # Commit a transaction.
290
+
291
+ def commit
292
+ # nop, not supported?
293
+ end
294
+
295
+ # Rollback a transaction.
296
+
297
+ def rollback
298
+ # nop, not supported?
299
+ end
300
+
301
+ def sql_update(sql)
302
+ # nop, not supported.
303
+ end
304
+
305
+ #FIXME: this method relies on naming conventions and does not account
306
+ #for more than one set of '::' marks.
307
+
308
+ def join(obj1, obj2, table, options = nil)
309
+ first, second = join_object_ordering(obj1, obj2)
310
+
311
+ options[get_key(first)] = first.pk
312
+ options[get_key(second)] = second.pk
313
+
314
+ @conn.get_table(table.to_sym).insert(options)
315
+ end
316
+
317
+ #retrieve the join table key name for the object
318
+
319
+ def get_key(obj)
320
+ obj.class.name =~ /::(\w*)/
321
+ ($1.downcase + '_oid').to_sym
322
+ end
323
+
324
+ def unjoin(obj1, obj2, table)
325
+ first, second = join_object_ordering(obj1, obj2)
326
+
327
+ @conn.get_table(table.to_sym).delete do |r|
328
+ r.send(get_key(first)) == first.pk and
329
+ r.send(get_key(second)) == second.pk
330
+ end
331
+ end
332
+
333
+ private
195
334
 
196
- field_names.each do |needed_field|
197
- next if actual_fields.include?(needed_field)
335
+ def typemap(key)
336
+ @typemap[key]
337
+ end
198
338
 
199
- if @options[:evolve_schema] == true
200
- Logger.debug "Adding field '#{needed_field}' to '#{klass.table}'"
201
- field_type = typemap(klass.properties[needed_field].klass.name.to_sym)
202
- if get_table(klass).respond_to?(:add_column)
203
- get_table(klass).add_column(needed_field, field_type)
339
+ def create_table(klass)
340
+ if @conn.table_exists?(klass.table.to_sym)
341
+ get_table(klass).pack # Kirby specific method of database cleanup.
342
+
343
+ field_names = field_names_for_class(klass)
344
+
345
+ actual_fields = get_table(klass).field_names
346
+
347
+ field_names.each do |needed_field|
348
+ next if actual_fields.include?(needed_field)
349
+
350
+ if @options[:evolve_schema] == true
351
+ Logger.debug "Adding field '#{needed_field}' to '#{klass.table}'" if $DBG
352
+ field_type = typemap(klass.properties[needed_field].klass)
353
+ if get_table(klass).respond_to?(:add_column)
354
+ get_table(klass).add_column(needed_field, field_type)
355
+ else
356
+ @conn.add_table_column(klass.table, needed_field, field_type)
357
+ end
204
358
  else
205
- @conn.add_table_column(klass.table, needed_field, field_type)
359
+ Logger.warn "Table '#{klass.table}' is missing field '#{needed_field}' and :evolve_schema is not set to true!"
206
360
  end
207
- else
208
- Logger.warn "Table '#{klass.table}' is missing field '#{needed_field}' and :evolve_schema is not set to true!"
209
361
  end
210
- end
211
-
212
- actual_fields.each do |obsolete_field|
213
- next if field_names.include?(obsolete_field)
214
362
 
215
- if @options[:evolve_schema] == true and @options[:evolve_schema_cautious] == false
216
- Logger.debug "Removing obsolete field '#{obsolete_field}' from '#{klass.table}'"
217
- if get_table(klass).respond_to?(:drop_column)
218
- get_table(klass).drop_column(obsolete_field)
363
+ actual_fields.each do |obsolete_field|
364
+ next if field_names.include?(obsolete_field) || obsolete_field == :recno
365
+ if @options[:evolve_schema] == true and @options[:evolve_schema_cautious] == false
366
+ Logger.debug "Removing obsolete field '#{obsolete_field}' from '#{klass.table}'" if $DBG
367
+ if get_table(klass).respond_to?(:drop_column)
368
+ get_table(klass).drop_column(obsolete_field)
369
+ else
370
+ @conn.drop_table_column(klass.table, obsolete_field)
371
+ end
219
372
  else
220
- @conn.drop_table_column(klass.table, obsolete_field)
373
+ Logger.warn "You have an obsolete field '#{obsolete_field}' on table '#{klass.table}' and :evolve_schema is not set or is in cautious mode!"
221
374
  end
222
- else
223
- Logger.warn "You have an obsolete field '#{obsolete_field}' on table '#{klass.table}' and :evolve_schema is not set or is in cautious mode!"
224
375
  end
376
+ else
377
+ Logger.debug "Creating table '#{klass.table}'" if $DBG
378
+ fields = fields_for_class(klass)
379
+ table = @conn.create_table(klass.table.to_sym, *fields)
225
380
  end
226
- else
227
- Logger.debug "Creating table '#{klass.table}'"
228
- fields = fields_for_class(klass)
229
- table = @conn.create_table(klass.table.to_sym, *fields) do |t|
230
- t.record_class = klass
231
- end
232
- end
233
381
 
234
382
  =begin
235
383
  # Create join tables if needed. Join tables are used in
@@ -239,109 +387,104 @@ private
239
387
  for info in join_tables
240
388
  unless @conn.table_exists?(info[:table].to_sym)
241
389
  @conn.create_table(info[:table].to_sym, *create_join_table_sql(info))
242
- Logger.debug "Created jointable '#{info[:table]}'."
390
+ Logger.debug "Created jointable '#{info[:table]}'." if $DBG
243
391
  end
244
392
  end
245
393
  end
246
394
  =end
247
- end
248
-
249
-
250
- def create_join_table_sql(join_info)
251
- [join_info[:first_key].to_sym, :Integer, join_info[:second_key].to_sym, :Integer]
252
- end
253
-
254
- def drop_table(klass)
255
- @conn.drop_table(klass.table) if @conn.table_exists?(klass.table)
256
- end
257
-
258
- def field_names_for_class(klass)
259
- klass.properties.values.map {|p| p.symbol }
260
- end
261
-
262
- def fields_for_class(klass)
263
- fields = []
264
-
265
- klass.properties.values.each do |p|
266
- klass.index(p.symbol) if p.index
267
-
268
- fields << p.symbol
269
-
270
- type = typemap(p.klass.name.to_sym)
271
-
272
- fields << type
395
+ end
396
+
397
+ def create_join_table_sql(join_info)
398
+ [join_info[:first_key].to_sym, :Integer, join_info[:second_key].to_sym, :Integer]
273
399
  end
274
400
 
275
- return fields
276
- end
277
-
278
- def eval_og_insert(klass)
279
- pk = klass.primary_key.symbol
280
- props = klass.properties.values.dup
281
- values = props.collect { |p| write_prop(p) }.join(',')
401
+ def drop_table(klass)
402
+ @conn.drop_table(klass.table) if @conn.table_exists?(klass.table)
403
+ end
282
404
 
283
- if klass.schema_inheritance?
284
- props << Property.new(:symbol => :ogtype, :klass => String)
285
- values << ", '#{klass}'"
405
+ def field_names_for_class(klass)
406
+ properties = get_properties_for_class(klass)
407
+ properties.values.map {|p| p.symbol }
286
408
  end
287
409
 
288
- klass.class_eval %{
289
- def og_insert(store)
290
- #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
291
- Logger.debug "Inserting \#{self}." if $DBG
292
- @#{pk} = store.get_table(#{klass}).insert(self)
293
- #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
410
+ def fields_for_class(klass)
411
+ fields = []
412
+
413
+ get_properties_for_class(klass).values.each do |p|
414
+ klass.index(p.symbol) if p.index
415
+
416
+ fields << p.symbol
417
+
418
+ type = typemap(p.klass)
419
+
420
+ fields << type
294
421
  end
295
- }
296
- end
297
-
298
- # Compile the og_update method for the class.
299
-
300
- def eval_og_update(klass)
301
- pk = klass.pk_symbol
302
- updates = klass.properties.keys.collect { |p| ":#{p} => @#{p}" }
422
+
423
+ fields
424
+ end
425
+
426
+ def eval_og_insert(klass)
427
+ pk = klass.primary_key.symbol
428
+
429
+ fields = get_properties_for_class(klass).keys
430
+ fields = fields.map { |f| ":#{f} => (self.respond_to?(:#{f}) ? self.#{f} : nil)"}
431
+ field_string = fields.join(', ')
432
+ klass.class_eval %{
433
+ def og_insert(store)
434
+ @ogtype = #{klass}
435
+ #{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
436
+ Logger.debug "Inserting \#{self}." if $DBG
437
+ @#{pk} = store.get_table(#{klass}).insert({#{field_string}})
438
+ #{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
439
+ end
440
+ }
441
+ end
303
442
 
304
- klass.module_eval %{
305
- def og_update(store, options = nil)
306
- #{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
307
- store.get_table(#{klass}).update { |r| r.recno == #{pk} }.set(#{updates.join(', ')})
308
- #{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
309
- end
310
- }
311
- end
443
+ # Compile the og_update method for the class.
312
444
 
313
- def eval_og_read(klass)
314
- klass.module_eval %{
315
- def og_read(res, row = 0, offset = 0)
316
- #{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
317
- #{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
318
- end
319
- }
320
- end
445
+ def eval_og_update(klass)
446
+ pk = klass.pk_symbol
447
+ updates = klass.properties.keys.collect { |p| ":#{p} => @#{p}" }
321
448
 
322
- def eval_og_delete(klass)
323
- klass.module_eval %{
324
- def og_delete(store, pk, cascade = true)
325
- pk ||= self.pk
326
- pk = pk.to_i
327
- #{Glue::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
328
- table = store.get_table(self.class)
329
- transaction do |tx|
330
- table.delete { |r| r.recno == pk }
331
- if cascade and #{klass}.ann.self[:descendants]
332
- #{klass}.ann.self.descendants.each do |dclass, foreign_key|
333
- dtable = store.get_table(dclass)
334
- dtable.delete { |r| foreign_key == pk }
449
+ klass.module_eval %{
450
+ def og_update(store, options = nil)
451
+ #{::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
452
+ store.get_table(#{klass}).update { |r| r.recno == #{pk} }.set(#{updates.join(', ')})
453
+ #{::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
454
+ end
455
+ }
456
+ end
457
+
458
+ def eval_og_read(klass)
459
+ klass.module_eval %{
460
+ def og_read(res, row = 0, offset = 0)
461
+ #{::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
462
+ #{::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
463
+ end
464
+ }
465
+ end
466
+
467
+ def eval_og_delete(klass)
468
+ klass.module_eval %{
469
+ def og_delete(store, pk, cascade = true)
470
+ pk ||= self.pk
471
+ pk = pk.to_i
472
+ #{::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
473
+ table = store.get_table(self.class)
474
+ transaction do |tx|
475
+ table.delete { |r| r.recno == pk }
476
+ if cascade and #{klass}.ann.self[:descendants]
477
+ #{klass}.ann.self.descendants.each do |dclass, foreign_key|
478
+ dtable = store.get_table(dclass)
479
+ dtable.delete { |r| foreign_key == pk }
480
+ end
335
481
  end
336
482
  end
483
+ #{::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
337
484
  end
338
- #{Glue::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
339
- end
340
- }
485
+ }
486
+ end
341
487
  end
342
-
343
- end
344
-
345
488
  end
346
489
 
347
490
  # * George Moschovitis <gm@navel.gr>