og 0.20.0 → 0.21.0

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