og 0.29.0 → 0.30.0

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.
@@ -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>