og 0.23.0 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/ProjectInfo +58 -0
  2. data/README +5 -4
  3. data/Rakefile +2 -2
  4. data/doc/AUTHORS +10 -7
  5. data/doc/RELEASES +108 -0
  6. data/lib/og.rb +1 -3
  7. data/lib/og/collection.rb +4 -4
  8. data/lib/og/entity.rb +96 -27
  9. data/lib/og/evolution.rb +78 -0
  10. data/lib/og/manager.rb +29 -32
  11. data/lib/og/mixin/hierarchical.rb +1 -1
  12. data/lib/og/mixin/optimistic_locking.rb +5 -8
  13. data/lib/og/mixin/orderable.rb +15 -2
  14. data/lib/og/mixin/schema_inheritance_base.rb +12 -0
  15. data/lib/og/mixin/taggable.rb +29 -25
  16. data/lib/og/mixin/timestamped.rb +4 -2
  17. data/lib/og/mixin/tree.rb +0 -1
  18. data/lib/og/relation.rb +161 -116
  19. data/lib/og/relation/all.rb +6 -0
  20. data/lib/og/relation/belongs_to.rb +4 -1
  21. data/lib/og/relation/has_many.rb +6 -5
  22. data/lib/og/relation/joins_many.rb +13 -12
  23. data/lib/og/relation/refers_to.rb +3 -3
  24. data/lib/og/store.rb +9 -9
  25. data/lib/og/store/{filesys.rb → alpha/filesys.rb} +0 -0
  26. data/lib/og/store/alpha/kirby.rb +284 -0
  27. data/lib/og/store/{memory.rb → alpha/memory.rb} +2 -0
  28. data/lib/og/store/{sqlserver.rb → alpha/sqlserver.rb} +6 -6
  29. data/lib/og/store/kirby.rb +145 -162
  30. data/lib/og/store/mysql.rb +58 -27
  31. data/lib/og/store/psql.rb +15 -13
  32. data/lib/og/store/sql.rb +136 -135
  33. data/lib/og/store/sqlite.rb +13 -12
  34. data/lib/og/validation.rb +2 -2
  35. data/lib/vendor/kbserver.rb +20 -0
  36. data/lib/vendor/kirbybase.rb +2790 -1601
  37. data/test/og/CONFIG.rb +79 -0
  38. data/test/og/mixin/tc_hierarchical.rb +1 -1
  39. data/test/og/mixin/tc_optimistic_locking.rb +1 -3
  40. data/test/og/mixin/tc_orderable.rb +42 -1
  41. data/test/og/mixin/tc_taggable.rb +1 -1
  42. data/test/og/mixin/tc_timestamped.rb +1 -1
  43. data/test/og/store/tc_filesys.rb +1 -2
  44. data/test/og/tc_delete_all.rb +45 -0
  45. data/test/og/tc_inheritance.rb +10 -38
  46. data/test/og/tc_join.rb +2 -11
  47. data/test/og/tc_multiple.rb +3 -16
  48. data/test/og/tc_override.rb +3 -3
  49. data/test/og/tc_polymorphic.rb +3 -13
  50. data/test/og/tc_relation.rb +8 -6
  51. data/test/og/tc_reverse.rb +2 -11
  52. data/test/og/tc_select.rb +2 -15
  53. data/test/og/tc_store.rb +4 -63
  54. data/test/og/tc_types.rb +1 -2
  55. metadata +80 -77
@@ -291,3 +291,5 @@ private
291
291
  end
292
292
 
293
293
  end
294
+
295
+ # * George Moschovitis <gm@navel.gr>
@@ -129,10 +129,10 @@ private
129
129
 
130
130
  sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
131
131
 
132
- # Create table constrains.
132
+ # Create table constraints.
133
133
 
134
- if klass.__meta and constrains = klass.__meta[:sql_constrain]
135
- sql << ", #{constrains.join(', ')}"
134
+ if klass.__meta and constraints = klass.__meta[:sql_constraint]
135
+ sql << ", #{constraints.join(', ')}"
136
136
  end
137
137
 
138
138
  sql << ");"
@@ -167,10 +167,10 @@ private
167
167
  # Create join tables if needed. Join tables are used in
168
168
  # 'many_to_many' relations.
169
169
 
170
- if klass.__meta and join_tables = klass.__meta[:join_tables]
171
- for join_table in join_tables
170
+ if klass.__meta and join_tables = klass.__meta[:join_tables]
171
+ for join_table in join_tables
172
172
  begin
173
- conn.query(create_join_table_sql(join_table))
173
+ conn.query(create_join_table_sql(join_table))
174
174
  rescue => ex
175
175
  # gmosx: any idea how to better test this?
176
176
  if ex.errno == 1050 # table already exists.
@@ -1,125 +1,194 @@
1
1
  begin
2
- require 'lib/vendor/kirbybase'
2
+ require 'vendor/kirbybase'
3
3
  rescue Object => ex
4
4
  Logger.error 'KirbyBase is not installed!'
5
5
  Logger.error ex
6
6
  end
7
7
 
8
+ require 'fileutils'
9
+
8
10
  require 'og/store/sql'
9
11
 
10
12
  module Og
11
13
 
12
- # A Store that persists objects into an KirbyBase database.
13
- # KirbyBase is a pure-ruby database implementation.
14
- # To read documentation about the methods, consult the
15
- # documentation for SqlStore and Store.
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.
16
17
 
17
18
  class KirbyStore < SqlStore
18
19
 
19
- def self.db_dir(options)
20
- "#{options[:name]}_db"
20
+ # Override if needed.
21
+
22
+ def self.base_dir(options)
23
+ options[:base_dir] || './kirbydb'
21
24
  end
22
25
 
23
26
  def self.destroy(options)
24
27
  begin
25
- FileUtils.rm_rf(db_dir(options))
28
+ FileUtils.rm_rf(base_dir(options))
26
29
  super
27
30
  rescue Object
28
- Logger.info "Cannot drop '#{options[:name]}'!"
31
+ Logger.info 'Cannot drop database!'
29
32
  end
30
33
  end
31
34
 
32
35
  def initialize(options)
33
36
  super
37
+ mode = options[:mode] || :local
34
38
 
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
+ if mode == :client
40
+ # Use a client/server configuration.
41
+ @conn = KirbyBase.new(:client, options[:address], options[:port])
39
42
  else
40
- # TODO
43
+ # Use an in-process configuration.
44
+ base_dir = self.class.base_dir(options)
45
+ FileUtils.mkdir_p(base_dir) unless File.exist?(base_dir)
46
+ @conn = KirbyBase.new(
47
+ :local,
48
+ nil, nil,
49
+ base_dir,
50
+ options[:ext] || '.tbl'
51
+ )
41
52
  end
42
53
  end
43
54
 
44
55
  def close
56
+ # Nothing to do.
45
57
  super
46
58
  end
47
59
 
48
60
  def enchant(klass, manager)
49
- klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY'
61
+ klass.send :attr_accessor, :recno
62
+ klass.send :alias_method, :oid, :recno
63
+ klass.send :alias_method, :oid=, :recno=
64
+
65
+ symbols = klass.properties.keys
66
+
67
+ klass.module_eval %{
68
+ def self.kb_create(recno, #{symbols.join(', ')})
69
+ obj = self.allocate
70
+ obj.recno = recno
71
+ #{ symbols.map { |s| "obj.#{s} = #{s}; "} }
72
+ return obj
73
+ end
74
+ }
75
+
50
76
  super
51
77
  end
52
78
 
53
- def query(sql)
54
- Logger.debug sql if $DBG
55
- return @conn.query(sql)
56
- rescue => ex
57
- handle_sql_exception(ex, sql)
79
+ def get_table(klass)
80
+ @conn.get_table(klass.table.to_sym)
81
+ end
82
+
83
+ # :section: Lifecycle methods.
84
+
85
+ def load(pk, klass)
86
+ get_table(klass)[pk.to_i]
58
87
  end
88
+ alias_method :exist?, :load
59
89
 
60
- def exec(sql)
61
- Logger.debug sql if $DBG
62
- @conn.query(sql).close
63
- rescue => ex
64
- handle_sql_exception(ex, sql)
90
+ def reload(obj, pk)
91
+ raise 'Cannot reload unmanaged object' unless obj.saved?
92
+ new_obj = load(pk, obj.class)
93
+ obj.clone(new_obj)
94
+ end
95
+
96
+ def find(options)
97
+ query(options)
98
+ end
99
+
100
+ def find_one(options)
101
+ query(options).first
102
+ end
103
+
104
+ #--
105
+ # FIXME: optimize me!
106
+ #++
107
+
108
+ def count(options)
109
+ find(options).size
65
110
  end
66
111
 
67
- def start
112
+ def query(options)
113
+ Logger.debug "Querying with #{options.inspect}." if $DBG
114
+
115
+ klass = options[:class]
116
+ table = get_table(klass)
117
+
118
+ objects = []
119
+
120
+ if condition = options[:condition] || options[:where]
121
+ condition.gsub!(/=/, '==')
122
+ condition.gsub!(/LIKE '(.*?)'/, '=~ /\1/')
123
+ condition.gsub!(/\%/, '')
124
+ condition.gsub!(/(\w*?)\s?=(.)/, 'o.\1 =\2')
125
+ objects = eval("table.select { |o| #{condition} }")
126
+ else
127
+ objects = table.select
128
+ end
129
+
130
+ if order = options[:order]
131
+ desc = (order =~ /DESC/)
132
+ order = order.gsub(/DESC/, '').gsub(/ASC/, '')
133
+ eval "objects.sort { |x, y| x.#{order} <=> y.#{order} }"
134
+ objects.reverse! if desc
135
+ end
136
+
137
+ return objects
138
+ end
139
+
140
+ def start
68
141
  # nop
69
142
  end
70
143
 
144
+ # Commit a transaction.
145
+
71
146
  def commit
72
- # nop
147
+ # nop, not supported?
73
148
  end
74
149
 
150
+ # Rollback a transaction.
151
+
75
152
  def rollback
76
- # nop
153
+ # nop, not supported?
154
+ end
155
+
156
+ def sql_update(sql)
157
+ # nop, not supported.
77
158
  end
78
159
 
79
160
  private
80
161
 
81
162
  def create_table(klass)
82
163
  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
164
+ begin
165
+ table = @conn.create_table(klass.table.to_sym, *fields) do |t|
166
+ t.record_class = klass
167
+ end
168
+ rescue Object => ex
169
+ # gmosx: any idea how to better test this?
170
+ if ex.to_s =~ /already exists/i
171
+ Logger.debug "Table for '#{klass}' already exists!"
172
+ return
173
+ else
174
+ raise
106
175
  end
107
176
  end
108
177
  end
109
178
 
110
179
  def drop_table(klass)
111
- @conn.drop_table(klass.table) if @conn.table_exists?(klass.table)
180
+ @conn.drop_table(klass.table) if @conn.table_exists?(klass.table)
112
181
  end
113
182
 
114
183
  def fields_for_class(klass)
115
184
  fields = []
116
185
 
117
- klass.properties.each do |p|
118
- klass.index(p.symbol) if p.meta[:index]
186
+ klass.properties.values.each do |p|
187
+ klass.index(p.symbol) if p.index
119
188
 
120
189
  fields << p.symbol
121
190
 
122
- type = p.klass.name.intern
191
+ type = p.klass.name.to_sym
123
192
  type = :Integer if type == :Fixnum
124
193
 
125
194
  fields << type
@@ -128,153 +197,67 @@ private
128
197
  return fields
129
198
  end
130
199
 
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
200
+ def eval_og_insert(klass)
201
+ pk = klass.primary_key.symbol
141
202
 
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)\}'" : "''"|
203
+ if klass.schema_inheritance?
204
+ props << Property.new(:symbol => :ogtype, :klass => String)
205
+ values << ", '#{klass}'"
166
206
  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
207
 
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 %{
208
+ klass.class_eval %{
202
209
  def og_insert(store)
203
210
  #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
204
- store.conn.get_table('#{klass.table}').insert(#{data})
211
+ Logger.debug "Inserting \#{self}." if $DBG
212
+ @#{pk} = store.get_table(#{klass}).insert(self)
205
213
  #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
206
214
  end
207
215
  }
208
216
  end
209
-
217
+
210
218
  # Compile the og_update method for the class.
211
219
 
212
220
  def eval_og_update(klass)
213
221
  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}\}"
222
+ updates = klass.properties.keys.collect { |p| ":#{p} => @#{p}" }
221
223
 
222
224
  klass.module_eval %{
223
- def og_update(store)
225
+ def og_update(store, options = nil)
224
226
  #{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
225
- store.exec "#{sql}"
227
+ store.get_table(#{klass}).update { |r| r.recno == #{pk} }.set(#{updates.join(', ')})
226
228
  #{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
227
229
  end
228
230
  }
229
231
  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
232
 
236
233
  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
234
  klass.module_eval %{
250
235
  def og_read(res, row = 0, offset = 0)
251
236
  #{Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
252
- #{code}
253
237
  #{Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
254
238
  end
255
239
  }
256
240
  end
257
241
 
258
- #--
259
- # FIXME: is pk needed as parameter?
260
- #++
261
-
262
242
  def eval_og_delete(klass)
263
243
  klass.module_eval %{
264
244
  def og_delete(store, pk, cascade = true)
245
+ pk ||= self.pk
246
+ pk = pk.to_i
265
247
  #{Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
266
- pk ||= @#{klass.pk_symbol}
248
+ table = store.get_table(self.class)
267
249
  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}"
250
+ table.delete { |r| r.recno == pk }
251
+ if cascade and #{klass}.ann.this[:descendants]
252
+ #{klass}.ann.this.descendants.each do |dclass, foreign_key|
253
+ dtable = store.get_table(dclass)
254
+ dtable.delete { |r| foreign_key == pk }
272
255
  end
273
256
  end
274
257
  end
275
258
  #{Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
276
259
  end
277
- }
260
+ }
278
261
  end
279
262
 
280
263
  end