og 0.20.0 → 0.21.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.
Files changed (53) hide show
  1. data/CHANGELOG +796 -664
  2. data/INSTALL +24 -24
  3. data/README +39 -32
  4. data/Rakefile +41 -42
  5. data/benchmark/bench.rb +36 -36
  6. data/doc/AUTHORS +15 -12
  7. data/doc/LICENSE +3 -3
  8. data/doc/RELEASES +311 -243
  9. data/doc/config.txt +1 -1
  10. data/examples/mysql_to_psql.rb +15 -15
  11. data/examples/run.rb +92 -92
  12. data/install.rb +7 -17
  13. data/lib/og.rb +76 -75
  14. data/lib/og/collection.rb +203 -160
  15. data/lib/og/entity.rb +168 -169
  16. data/lib/og/errors.rb +5 -5
  17. data/lib/og/manager.rb +179 -178
  18. data/lib/og/mixin/hierarchical.rb +107 -107
  19. data/lib/og/mixin/optimistic_locking.rb +36 -36
  20. data/lib/og/mixin/orderable.rb +148 -148
  21. data/lib/og/mixin/timestamped.rb +8 -8
  22. data/lib/og/mixin/tree.rb +124 -124
  23. data/lib/og/relation.rb +237 -213
  24. data/lib/og/relation/belongs_to.rb +5 -5
  25. data/lib/og/relation/has_many.rb +60 -58
  26. data/lib/og/relation/joins_many.rb +93 -47
  27. data/lib/og/relation/refers_to.rb +25 -21
  28. data/lib/og/store.rb +210 -207
  29. data/lib/og/store/filesys.rb +79 -79
  30. data/lib/og/store/kirby.rb +263 -258
  31. data/lib/og/store/memory.rb +261 -261
  32. data/lib/og/store/mysql.rb +288 -284
  33. data/lib/og/store/psql.rb +261 -244
  34. data/lib/og/store/sql.rb +873 -720
  35. data/lib/og/store/sqlite.rb +177 -175
  36. data/lib/og/store/sqlserver.rb +204 -214
  37. data/lib/og/types.rb +1 -1
  38. data/lib/og/validation.rb +57 -57
  39. data/lib/vendor/mysql.rb +376 -376
  40. data/lib/vendor/mysql411.rb +10 -10
  41. data/test/og/mixin/tc_hierarchical.rb +59 -59
  42. data/test/og/mixin/tc_optimistic_locking.rb +40 -40
  43. data/test/og/mixin/tc_orderable.rb +67 -67
  44. data/test/og/mixin/tc_timestamped.rb +19 -19
  45. data/test/og/store/tc_filesys.rb +46 -46
  46. data/test/og/tc_inheritance.rb +81 -81
  47. data/test/og/tc_join.rb +67 -0
  48. data/test/og/tc_polymorphic.rb +49 -49
  49. data/test/og/tc_relation.rb +57 -30
  50. data/test/og/tc_select.rb +49 -0
  51. data/test/og/tc_store.rb +345 -337
  52. data/test/og/tc_types.rb +11 -11
  53. metadata +11 -18
@@ -10,102 +10,102 @@ module Og
10
10
 
11
11
  class FilesysStore < Store
12
12
 
13
- # :section: Store methods.
14
-
15
- def self.dir(name)
16
- "#{name}_db"
17
- end
18
-
19
- def self.create(options)
20
- name = dir(options[:name])
21
- FileUtils.mkdir_p(name)
22
- rescue
23
- Logger.error "Cannot create '#{name}'"
24
- end
25
-
26
- def self.destroy(options)
27
- name = dir(options[:name])
28
- FileUtils.rm_rf(name)
29
- rescue
30
- Logger.error "Cannot destroy '#{name}'"
31
- end
32
-
33
- # :section: Misc methods.
34
-
35
- # Initialize a connection to this store.
13
+ # :section: Store methods.
14
+
15
+ def self.dir(name)
16
+ "#{name}_db"
17
+ end
18
+
19
+ def self.create(options)
20
+ name = dir(options[:name])
21
+ FileUtils.mkdir_p(name)
22
+ rescue
23
+ Logger.error "Cannot create '#{name}'"
24
+ end
25
+
26
+ def self.destroy(options)
27
+ name = dir(options[:name])
28
+ FileUtils.rm_rf(name)
29
+ rescue
30
+ Logger.error "Cannot destroy '#{name}'"
31
+ end
32
+
33
+ # :section: Misc methods.
34
+
35
+ # Initialize a connection to this store.
36
36
 
37
37
  def initialize(options)
38
- super
39
-
40
- @base_dir = self.class.dir(options[:name])
41
-
42
- unless File.directory?(@base_dir)
43
- Logger.info "Database '#{options[:name]}' not found!"
44
- self.class.create(options)
45
- end
46
- end
47
-
48
- def enchant(klass, manager)
49
- super
50
-
51
- klass.const_set 'OGNAME', klass.to_s.gsub(/::/, "/").downcase
52
- klass.property :oid, Fixnum
38
+ super
39
+
40
+ @base_dir = self.class.dir(options[:name])
41
+
42
+ unless File.directory?(@base_dir)
43
+ Logger.info "Database '#{options[:name]}' not found!"
44
+ self.class.create(options)
45
+ end
46
+ end
47
+
48
+ def enchant(klass, manager)
49
+ super
50
+
51
+ klass.const_set 'OGNAME', klass.to_s.gsub(/::/, "/").downcase
52
+ klass.property :oid, Fixnum
53
53
 
54
- FileUtils.mkdir_p(path(klass))
55
- File.open(spath(klass), 'w') { |f| f << '1' }
56
- rescue => ex
57
- p ex
58
- Logger.error "Cannot create directory to store '#{klass}' classes!"
59
- end
54
+ FileUtils.mkdir_p(path(klass))
55
+ File.open(spath(klass), 'w') { |f| f << '1' }
56
+ rescue => ex
57
+ p ex
58
+ Logger.error "Cannot create directory to store '#{klass}' classes!"
59
+ end
60
60
 
61
- # :section: Lifecycle methods.
61
+ # :section: Lifecycle methods.
62
62
 
63
- def load(oid, klass)
63
+ def load(oid, klass)
64
64
  obj = nil
65
- File.open(path(klass, oid), 'r') { |f| obj = YAML::load(f.read) }
66
- return obj
67
- rescue
68
- return nil
69
- end
65
+ File.open(path(klass, oid), 'r') { |f| obj = YAML::load(f.read) }
66
+ return obj
67
+ rescue
68
+ return nil
69
+ end
70
70
 
71
- def reload(obj)
72
- end
71
+ def reload(obj)
72
+ end
73
73
 
74
- def save(obj)
74
+ def save(obj)
75
75
  seq = nil
76
- File.open(spath(obj.class), 'r') { |f| seq = f.read.to_i }
77
- obj.oid = seq
78
- File.open(path(obj.class, obj.oid), 'w') { |f| f << obj.to_yaml }
79
- seq += 1
80
- File.open(spath(obj.class), 'w') { |f| f << seq }
81
- end
76
+ File.open(spath(obj.class), 'r') { |f| seq = f.read.to_i }
77
+ obj.oid = seq
78
+ File.open(path(obj.class, obj.oid), 'w') { |f| f << obj.to_yaml }
79
+ seq += 1
80
+ File.open(spath(obj.class), 'w') { |f| f << seq }
81
+ end
82
82
 
83
- def delete(obj_or_pk, klass = nil, cascade = true)
84
- pk = obj_or_pk.is_a?(Fixnum) ? obj_or_pk : obj_or_pk.oid
85
- klass ||= obj_or_pk.class
86
- FileUtils.rm(path(klass, pk))
87
- end
83
+ def delete(obj_or_pk, klass = nil, cascade = true)
84
+ pk = obj_or_pk.is_a?(Fixnum) ? obj_or_pk : obj_or_pk.oid
85
+ klass ||= obj_or_pk.class
86
+ FileUtils.rm(path(klass, pk))
87
+ end
88
88
 
89
- def find
90
- end
89
+ def find
90
+ end
91
91
 
92
- def count
93
- end
92
+ def count
93
+ end
94
94
 
95
95
  private
96
96
 
97
- # Path helper.
97
+ # Path helper.
98
98
 
99
- def path(klass, pk = nil)
100
- class_dir = File.join(@base_dir, klass::OGNAME)
101
- pk ? File.join(class_dir, "#{pk}.yml") : class_dir
102
- end
99
+ def path(klass, pk = nil)
100
+ class_dir = File.join(@base_dir, klass::OGNAME)
101
+ pk ? File.join(class_dir, "#{pk}.yml") : class_dir
102
+ end
103
103
 
104
- # Path to sequence helper.
105
-
106
- def spath(klass)
107
- File.join(path(klass), 'seq')
108
- end
104
+ # Path to sequence helper.
105
+
106
+ def spath(klass)
107
+ File.join(path(klass), 'seq')
108
+ end
109
109
 
110
110
  end
111
111
 
@@ -1,8 +1,8 @@
1
1
  begin
2
- require 'lib/og/store/kirby/kirbybase'
2
+ require 'lib/og/store/kirby/kirbybase'
3
3
  rescue Object => ex
4
- Logger.error 'KirbyBase is not installed!'
5
- Logger.error ex
4
+ Logger.error 'KirbyBase is not installed!'
5
+ Logger.error ex
6
6
  end
7
7
 
8
8
  require 'og/store/sql'
@@ -16,265 +16,270 @@ module Og
16
16
 
17
17
  class KirbyStore < SqlStore
18
18
 
19
- def self.db_dir(options)
20
- "#{options[:name]}_db"
21
- end
22
-
23
- def self.destroy(options)
24
- begin
25
- FileUtils.rm_rf(db_dir(options))
26
- super
27
- rescue Object
28
- Logger.info "Cannot drop '#{options[:name]}'!"
29
- end
30
- end
31
-
32
- def initialize(options)
33
- super
34
-
35
- if options[:embedded]
36
- name = self.class.db_dir(options)
37
- FileUtils.mkdir_p(name)
38
- @conn = KirbyBase.new(:local, nil, nil, name)
39
- else
40
- # TODO
41
- end
42
- end
43
-
44
- def close
45
- super
46
- end
47
-
48
- def enchant(klass, manager)
49
- klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY'
50
- super
51
- end
52
-
53
- def query(sql)
54
- Logger.debug sql if $DBG
55
- return @conn.query(sql)
56
- rescue => ex
57
- handle_sql_exception(ex, sql)
58
- end
59
-
60
- def exec(sql)
61
- Logger.debug sql if $DBG
62
- @conn.query(sql).close
63
- rescue => ex
64
- handle_sql_exception(ex, sql)
65
- end
66
-
67
- def start
68
- # nop
69
- end
70
-
71
- def commit
72
- # nop
73
- end
74
-
75
- def rollback
76
- # nop
77
- end
19
+ def self.db_dir(options)
20
+ "#{options[:name]}_db"
21
+ end
22
+
23
+ def self.destroy(options)
24
+ begin
25
+ FileUtils.rm_rf(db_dir(options))
26
+ super
27
+ rescue Object
28
+ Logger.info "Cannot drop '#{options[:name]}'!"
29
+ end
30
+ end
31
+
32
+ def initialize(options)
33
+ super
34
+
35
+ if options[:embedded]
36
+ name = self.class.db_dir(options)
37
+ FileUtils.mkdir_p(name)
38
+ @conn = KirbyBase.new(:local, nil, nil, name)
39
+ else
40
+ # TODO
41
+ end
42
+ end
43
+
44
+ def close
45
+ super
46
+ end
47
+
48
+ def enchant(klass, manager)
49
+ klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY'
50
+ super
51
+ end
52
+
53
+ def query(sql)
54
+ Logger.debug sql if $DBG
55
+ return @conn.query(sql)
56
+ rescue => ex
57
+ handle_sql_exception(ex, sql)
58
+ end
59
+
60
+ def exec(sql)
61
+ Logger.debug sql if $DBG
62
+ @conn.query(sql).close
63
+ rescue => ex
64
+ handle_sql_exception(ex, sql)
65
+ end
66
+
67
+ def start
68
+ # nop
69
+ end
70
+
71
+ def commit
72
+ # nop
73
+ end
74
+
75
+ def rollback
76
+ # nop
77
+ end
78
78
 
79
79
  private
80
80
 
81
- def create_table(klass)
82
- fields = fields_for_class(klass)
83
-
84
- table = @conn.create_table(klass::OGTABLE, *fields) { |obj| obj.encrypt = false }
85
- =begin
86
- # Create join tables if needed. Join tables are used in
87
- # 'many_to_many' relations.
88
-
89
- if klass.__meta and join_tables = klass.__meta[:join_tables]
90
- for join_table in join_tables
91
- begin
92
- @conn.query("CREATE TABLE #{join_table} (key1 integer NOT NULL, key2 integer NOT NULL)").close
93
- @conn.query("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").close
94
- @conn.query("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").close
95
- rescue Object => ex
96
- # gmosx: any idea how to better test this?
97
- if ex.to_s =~ /table .* already exists/i
98
- Logger.debug 'Join table already exists' if $DBG
99
- else
100
- raise
101
- end
102
- end
103
- end
104
- end
105
- =end
106
- end
107
-
108
- def drop_table(klass)
109
- @conn.drop_table(klass.table) if @conn.table_exists?(klass.table)
110
- end
111
-
112
- def fields_for_class(klass)
113
- fields = []
114
-
115
- klass.properties.each do |p|
116
- klass.index(p.symbol) if p.meta[:index]
117
-
118
- fields << p.symbol
119
-
120
- type = p.klass.name.intern
121
- type = :Integer if type == :Fixnum
122
-
123
- fields << type
124
- end
125
-
126
- return fields
127
- end
128
-
129
- def create_field_map(klass)
130
- map = {}
131
- fields = @conn.get_table(klass.table).field_names
132
-
133
- fields.size.times do |i|
134
- map[fields[i]] = i
135
- end
136
-
137
- return map
138
- end
139
-
140
- # Return an sql string evaluator for the property.
141
- # No need to optimize this, used only to precalculate code.
142
- # YAML is used to store general Ruby objects to be more
143
- # portable.
144
- #--
145
- # FIXME: add extra handling for float.
146
- #++
147
-
148
- def write_prop(p)
149
- if p.klass.ancestors.include?(Integer)
150
- return "@#{p.symbol} || nil"
151
- elsif p.klass.ancestors.include?(Float)
152
- return "@#{p.symbol} || nil"
153
- elsif p.klass.ancestors.include?(String)
154
- return %|@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : nil|
155
- elsif p.klass.ancestors.include?(Time)
156
- return %|@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : nil|
157
- elsif p.klass.ancestors.include?(Date)
158
- return %|@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : nil|
159
- elsif p.klass.ancestors.include?(TrueClass)
160
- return "@#{p.symbol} ? \"'t'\" : nil"
161
- else
162
- # gmosx: keep the '' for nil symbols.
163
- return %|@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"|
164
- end
165
- end
166
-
167
- # Return an evaluator for reading the property.
168
- # No need to optimize this, used only to precalculate code.
169
-
170
- def read_prop(p, col)
171
- if p.klass.ancestors.include?(Integer)
172
- return "#{self.class}.parse_int(res[#{col} + offset])"
173
- elsif p.klass.ancestors.include?(Float)
174
- return "#{self.class}.parse_float(res[#{col} + offset])"
175
- elsif p.klass.ancestors.include?(String)
176
- return "res[#{col} + offset]"
177
- elsif p.klass.ancestors.include?(Time)
178
- return "#{self.class}.parse_timestamp(res[#{col} + offset])"
179
- elsif p.klass.ancestors.include?(Date)
180
- return "#{self.class}.parse_date(res[#{col} + offset])"
181
- elsif p.klass.ancestors.include?(TrueClass)
182
- return "('0' != res[#{col} + offset])"
183
- else
184
- return "YAML::load(res[#{col} + offset])"
185
- end
186
- end
187
-
188
- # :section: Lifecycle method compilers.
189
-
190
- # Compile the og_update method for the class.
191
-
192
- def eval_og_insert(klass)
193
- pk = klass.pk_symbol
194
- props = klass.properties
195
-
196
- data = props.collect {|p| ":#{p.symbol} => #{write_prop(p)}"}.join(', ')
197
- # data.gsub!(/#|\{|\}/, '')
198
-
199
- klass.module_eval %{
200
- def og_insert(store)
201
- #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
202
- store.conn.get_table('#{klass.table}').insert(#{data})
203
- #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
204
- end
205
- }
206
- end
207
-
208
- # Compile the og_update method for the class.
209
-
210
- def eval_og_update(klass)
211
- pk = klass.pk_symbol
212
- props = klass.properties.reject { |p| pk == p.symbol }
213
-
214
- updates = props.collect { |p|
215
- "#{p.symbol}=#{write_prop(p)}"
216
- }
217
-
218
- sql = "UPDATE #{klass::OGTABLE} SET #{updates.join(', ')} WHERE #{pk}=#\{@#{pk}\}"
219
-
220
- klass.module_eval %{
221
- def og_update(store)
222
- #{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
223
- store.exec "#{sql}"
224
- #{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
225
- end
226
- }
227
- end
228
-
229
- # Compile the og_read method for the class. This method is
230
- # used to read (deserialize) the given class from the store.
231
- # In order to allow for changing field/attribute orders a
232
- # field mapping hash is used.
233
-
234
- def eval_og_read(klass)
235
- code = []
236
- props = klass.properties
237
- field_map = create_field_map(klass)
238
-
239
- props.each do |p|
240
- if col = field_map[p.symbol]
241
- code << "@#{p.symbol} = #{read_prop(p, col)}"
242
- end
243
- end
244
-
245
- code = code.join('; ')
246
-
247
- klass.module_eval %{
248
- def og_read(res, row = 0, offset = 0)
249
- #{Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
250
- #{code}
251
- #{Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
252
- end
253
- }
254
- end
255
-
256
- #--
257
- # FIXME: is pk needed as parameter?
258
- #++
259
-
260
- def eval_og_delete(klass)
261
- klass.module_eval %{
262
- def og_delete(store, pk, cascade = true)
263
- #{Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
264
- pk ||= @#{klass.pk_symbol}
265
- transaction do |tx|
266
- tx.exec "DELETE FROM #{klass.table} WHERE #{klass.pk_symbol}=\#{pk}"
267
- if cascade and #{klass}.__meta[:descendants]
268
- #{klass}.__meta[:descendants].each do |dclass, foreign_key|
269
- tx.exec "DELETE FROM \#{dclass::OGTABLE} WHERE \#{foreign_key}=\#{pk}"
270
- end
271
- end
272
- end
273
- #{Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
274
- end
275
- }
276
- end
81
+ def create_table(klass)
82
+ fields = fields_for_class(klass)
83
+
84
+ table = @conn.create_table(klass::OGTABLE, *fields) { |obj| obj.encrypt = false }
85
+
86
+ # Create join tables if needed. Join tables are used in
87
+ # 'many_to_many' relations.
88
+
89
+ if klass.__meta and join_tables = klass.__meta[:join_tables]
90
+ for join_table in join_tables
91
+ begin
92
+ @conn.create_table(join_table[:table],
93
+ join_table[:first_key], :Integer,
94
+ join_table[:second_key], :Integer)
95
+ # KirbyBase doesn't support indices.
96
+ rescue RuntimeError => error
97
+ # Unfortunately, KirbyBase just throws RuntimeErrors
98
+ # with no extra information, so we just have to look
99
+ # for the error message it uses.
100
+ if error.message =~ /table #{join_table[:table]} already exists/i
101
+ Logger.debug 'Join table already exists' if $DBG
102
+ else
103
+ raise
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ def drop_table(klass)
111
+ @conn.drop_table(klass.table) if @conn.table_exists?(klass.table)
112
+ end
113
+
114
+ def fields_for_class(klass)
115
+ fields = []
116
+
117
+ klass.properties.each do |p|
118
+ klass.index(p.symbol) if p.meta[:index]
119
+
120
+ fields << p.symbol
121
+
122
+ type = p.klass.name.intern
123
+ type = :Integer if type == :Fixnum
124
+
125
+ fields << type
126
+ end
127
+
128
+ return fields
129
+ end
130
+
131
+ def create_field_map(klass)
132
+ map = {}
133
+ fields = @conn.get_table(klass.table).field_names
134
+
135
+ fields.size.times do |i|
136
+ map[fields[i]] = i
137
+ end
138
+
139
+ return map
140
+ end
141
+
142
+ # Return an sql string evaluator for the property.
143
+ # No need to optimize this, used only to precalculate code.
144
+ # YAML is used to store general Ruby objects to be more
145
+ # portable.
146
+ #--
147
+ # FIXME: add extra handling for float.
148
+ #++
149
+
150
+ def write_prop(p)
151
+ if p.klass.ancestors.include?(Integer)
152
+ return "@#{p.symbol} || nil"
153
+ elsif p.klass.ancestors.include?(Float)
154
+ return "@#{p.symbol} || nil"
155
+ elsif p.klass.ancestors.include?(String)
156
+ return %|@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : nil|
157
+ elsif p.klass.ancestors.include?(Time)
158
+ return %|@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : nil|
159
+ elsif p.klass.ancestors.include?(Date)
160
+ return %|@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : nil|
161
+ elsif p.klass.ancestors.include?(TrueClass)
162
+ return "@#{p.symbol} ? \"'t'\" : nil"
163
+ else
164
+ # gmosx: keep the '' for nil symbols.
165
+ return %|@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"|
166
+ end
167
+ end
168
+
169
+ # Return an evaluator for reading the property.
170
+ # No need to optimize this, used only to precalculate code.
171
+
172
+ def read_prop(p, col)
173
+ if p.klass.ancestors.include?(Integer)
174
+ return "#{self.class}.parse_int(res[#{col} + offset])"
175
+ elsif p.klass.ancestors.include?(Float)
176
+ return "#{self.class}.parse_float(res[#{col} + offset])"
177
+ elsif p.klass.ancestors.include?(String)
178
+ return "res[#{col} + offset]"
179
+ elsif p.klass.ancestors.include?(Time)
180
+ return "#{self.class}.parse_timestamp(res[#{col} + offset])"
181
+ elsif p.klass.ancestors.include?(Date)
182
+ return "#{self.class}.parse_date(res[#{col} + offset])"
183
+ elsif p.klass.ancestors.include?(TrueClass)
184
+ return "('0' != res[#{col} + offset])"
185
+ else
186
+ return "YAML::load(res[#{col} + offset])"
187
+ end
188
+ end
189
+
190
+ # :section: Lifecycle method compilers.
191
+
192
+ # Compile the og_update method for the class.
193
+
194
+ def eval_og_insert(klass)
195
+ pk = klass.pk_symbol
196
+ props = klass.properties
197
+
198
+ data = props.collect {|p| ":#{p.symbol} => #{write_prop(p)}"}.join(', ')
199
+ # data.gsub!(/#|\{|\}/, '')
200
+
201
+ klass.module_eval %{
202
+ def og_insert(store)
203
+ #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
204
+ store.conn.get_table('#{klass.table}').insert(#{data})
205
+ #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
206
+ end
207
+ }
208
+ end
209
+
210
+ # Compile the og_update method for the class.
211
+
212
+ def eval_og_update(klass)
213
+ pk = klass.pk_symbol
214
+ props = klass.properties.reject { |p| pk == p.symbol }
215
+
216
+ updates = props.collect { |p|
217
+ "#{p.symbol}=#{write_prop(p)}"
218
+ }
219
+
220
+ sql = "UPDATE #{klass::OGTABLE} SET #{updates.join(', ')} WHERE #{pk}=#\{@#{pk}\}"
221
+
222
+ klass.module_eval %{
223
+ def og_update(store)
224
+ #{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
225
+ store.exec "#{sql}"
226
+ #{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
227
+ end
228
+ }
229
+ end
230
+
231
+ # Compile the og_read method for the class. This method is
232
+ # used to read (deserialize) the given class from the store.
233
+ # In order to allow for changing field/attribute orders a
234
+ # field mapping hash is used.
235
+
236
+ def eval_og_read(klass)
237
+ code = []
238
+ props = klass.properties
239
+ field_map = create_field_map(klass)
240
+
241
+ props.each do |p|
242
+ if col = field_map[p.symbol]
243
+ code << "@#{p.symbol} = #{read_prop(p, col)}"
244
+ end
245
+ end
246
+
247
+ code = code.join('; ')
248
+
249
+ klass.module_eval %{
250
+ def og_read(res, row = 0, offset = 0)
251
+ #{Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
252
+ #{code}
253
+ #{Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
254
+ end
255
+ }
256
+ end
257
+
258
+ #--
259
+ # FIXME: is pk needed as parameter?
260
+ #++
261
+
262
+ def eval_og_delete(klass)
263
+ klass.module_eval %{
264
+ def og_delete(store, pk, cascade = true)
265
+ #{Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
266
+ pk ||= @#{klass.pk_symbol}
267
+ transaction do |tx|
268
+ tx.exec "DELETE FROM #{klass.table} WHERE #{klass.pk_symbol}=\#{pk}"
269
+ if cascade and #{klass}.__meta[:descendants]
270
+ #{klass}.__meta[:descendants].each do |dclass, foreign_key|
271
+ tx.exec "DELETE FROM \#{dclass::OGTABLE} WHERE \#{foreign_key}=\#{pk}"
272
+ end
273
+ end
274
+ end
275
+ #{Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
276
+ end
277
+ }
278
+ end
277
279
 
278
280
  end
279
281
 
280
282
  end
283
+
284
+ # * George Moschovitis <gm@navel.gr>
285
+ # * Ysabel <deb@isabel.org>