og 0.16.0 → 0.17.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 (69) hide show
  1. data/CHANGELOG +485 -0
  2. data/README +35 -12
  3. data/Rakefile +4 -7
  4. data/benchmark/bench.rb +1 -1
  5. data/doc/AUTHORS +3 -3
  6. data/doc/RELEASES +153 -2
  7. data/doc/config.txt +0 -7
  8. data/doc/tutorial.txt +7 -0
  9. data/examples/README +5 -0
  10. data/examples/mysql_to_psql.rb +25 -50
  11. data/examples/run.rb +62 -77
  12. data/install.rb +1 -1
  13. data/lib/og.rb +45 -106
  14. data/lib/og/collection.rb +156 -0
  15. data/lib/og/entity.rb +131 -0
  16. data/lib/og/errors.rb +10 -15
  17. data/lib/og/manager.rb +115 -0
  18. data/lib/og/{mixins → mixin}/hierarchical.rb +43 -37
  19. data/lib/og/{mixins → mixin}/orderable.rb +35 -35
  20. data/lib/og/{mixins → mixin}/timestamped.rb +0 -6
  21. data/lib/og/{mixins → mixin}/tree.rb +0 -4
  22. data/lib/og/relation.rb +178 -0
  23. data/lib/og/relation/belongs_to.rb +14 -0
  24. data/lib/og/relation/has_many.rb +62 -0
  25. data/lib/og/relation/has_one.rb +17 -0
  26. data/lib/og/relation/joins_many.rb +69 -0
  27. data/lib/og/relation/many_to_many.rb +17 -0
  28. data/lib/og/relation/refers_to.rb +31 -0
  29. data/lib/og/store.rb +223 -0
  30. data/lib/og/store/filesys.rb +113 -0
  31. data/lib/og/store/madeleine.rb +4 -0
  32. data/lib/og/store/memory.rb +291 -0
  33. data/lib/og/store/mysql.rb +283 -0
  34. data/lib/og/store/psql.rb +238 -0
  35. data/lib/og/store/sql.rb +599 -0
  36. data/lib/og/store/sqlite.rb +190 -0
  37. data/lib/og/store/sqlserver.rb +262 -0
  38. data/lib/og/types.rb +19 -0
  39. data/lib/og/validation.rb +0 -4
  40. data/test/og/{mixins → mixin}/tc_hierarchical.rb +21 -23
  41. data/test/og/{mixins → mixin}/tc_orderable.rb +15 -14
  42. data/test/og/mixin/tc_timestamped.rb +38 -0
  43. data/test/og/store/tc_filesys.rb +71 -0
  44. data/test/og/tc_relation.rb +36 -0
  45. data/test/og/tc_store.rb +290 -0
  46. data/test/og/tc_types.rb +21 -0
  47. metadata +54 -40
  48. data/examples/mock_example.rb +0 -50
  49. data/lib/og/adapters/base.rb +0 -706
  50. data/lib/og/adapters/filesys.rb +0 -117
  51. data/lib/og/adapters/mysql.rb +0 -350
  52. data/lib/og/adapters/oracle.rb +0 -368
  53. data/lib/og/adapters/psql.rb +0 -272
  54. data/lib/og/adapters/sqlite.rb +0 -265
  55. data/lib/og/adapters/sqlserver.rb +0 -356
  56. data/lib/og/database.rb +0 -290
  57. data/lib/og/enchant.rb +0 -149
  58. data/lib/og/meta.rb +0 -407
  59. data/lib/og/testing/mock.rb +0 -165
  60. data/lib/og/typemacros.rb +0 -24
  61. data/test/og/adapters/tc_filesys.rb +0 -83
  62. data/test/og/adapters/tc_sqlite.rb +0 -86
  63. data/test/og/adapters/tc_sqlserver.rb +0 -96
  64. data/test/og/tc_automanage.rb +0 -46
  65. data/test/og/tc_lifecycle.rb +0 -105
  66. data/test/og/tc_many_to_many.rb +0 -61
  67. data/test/og/tc_meta.rb +0 -55
  68. data/test/og/tc_validation.rb +0 -89
  69. data/test/tc_og.rb +0 -364
@@ -1,117 +0,0 @@
1
- # * George Moschovitis <gm@navel.gr>
2
- # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: filesys.rb 17 2005-04-14 16:03:40Z gmosx $
4
-
5
- require 'fileutils'
6
-
7
- require 'og/adapters/base'
8
-
9
- module Og
10
-
11
- # The Filesys adapter. This adapter stores Og objectes in
12
- # the filesystem. This adapter is a proof of concept and
13
- # at the moment severely limited. For extra documentation
14
- # see lib/og/adapter.rb
15
-
16
- class FilesysAdapter < Adapter
17
-
18
- # Creates the name of the database root dir.
19
-
20
- def self.dir(database)
21
- "#{database}_db"
22
- end
23
-
24
- def create_db(database, user = nil, password = nil)
25
- begin
26
- FileUtils.mkdir_p(self.class.dir(database))
27
- rescue
28
- Logger.error "Cannot create '#{database}'!"
29
- end
30
- end
31
-
32
- def drop_db(database, user = nil, password = nil)
33
- begin
34
- FileUtils.rmdir(self.class.dir(database))
35
- super
36
- rescue
37
- Logger.error "Cannot drop '#{database}'!"
38
- end
39
- end
40
-
41
- def insert_code(klass, db)
42
- props = props_for_insert(klass)
43
- values = props.collect { |p| write_prop(p) }.join(',')
44
-
45
- sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
46
-
47
- %{
48
- conn.store.query("#{sql}").close
49
- @oid = conn.store.last_insert_row_id
50
- }
51
- end
52
-
53
- def new_connection(db)
54
- return FilesysConnection.new(db)
55
- end
56
-
57
- # A 'table' is emulated by using a directory to store
58
- # one file per table row. Each file contains an object
59
- # marshaled in YAML format. A special file called
60
- # '_sequence' stores the sequence for this 'table'.
61
-
62
- def create_table(klass, db)
63
- class_dir = File.join(self.class.dir(db.config[:database]), klass::DBTABLE)
64
- FileUtils.mkdir_p(class_dir)
65
-
66
- seq_file = File.join(class_dir, 'seq')
67
- File.open(seq_file, 'w') { |f| f << '1' }
68
- rescue => ex
69
- Logger.error "Cannot create directory to store '#{klass}' classes!"
70
- end
71
-
72
- end
73
-
74
- # The Filesys adapter connection.
75
-
76
- class FilesysConnection < Connection
77
-
78
- def initialize(db)
79
- super
80
- config = db.config
81
-
82
- begin
83
- raise unless File.directory?(FilesysAdapter.dir(config[:database]))
84
- rescue => ex
85
- if true # ex.to_s =~ /database .* does not exist/i
86
- Logger.info "Database '#{config[:database]}' not found!"
87
- @db.adapter.create_db(config[:database], config[:user])
88
- retry
89
- end
90
- raise
91
- end
92
- end
93
-
94
- def save(obj)
95
- seq = nil
96
- class_dir = File.join(FilesysAdapter.dir(@db.config[:database]), obj.class::DBTABLE)
97
- File.open(File.join(class_dir, 'seq'), 'r') { |f| seq = f.read.to_i }
98
- obj.oid = seq
99
- File.open(File.join(class_dir, "#{seq}.yml"), 'w') { |f| f << obj.to_yaml }
100
- seq += 1
101
- File.open(File.join(class_dir, 'seq'), 'w') { |f| f << seq }
102
- end
103
-
104
- def load(oid, klass)
105
- obj = nil
106
- class_dir = File.join(FilesysAdapter.dir(@db.config[:database]), klass::DBTABLE)
107
- File.open(File.join(class_dir, "#{oid}.yml"), 'r') { |f| obj = YAML::load(f.read) }
108
- return obj
109
- end
110
-
111
- def close
112
- Logger.debug "Closed DB connection." if $DBG
113
- end
114
-
115
- end
116
-
117
- end
@@ -1,350 +0,0 @@
1
- # * George Moschovitis <gm@navel.gr>
2
- # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: mysql.rb 17 2005-04-14 16:03:40Z gmosx $
4
-
5
- begin
6
- require 'mysql'
7
- rescue Object => ex
8
- Logger.error 'Ruby-Mysql bindings are not installed!'
9
- Logger.error ex
10
- end
11
-
12
- require 'og/adapters/base'
13
-
14
- module Og
15
-
16
- # The MySQL adapter. This adapter communicates with
17
- # an MySQL rdbms. For extra documentation see
18
- # lib/og/adapter.rb
19
-
20
- class MysqlAdapter < Adapter
21
-
22
- def initialize
23
- super
24
- @typemap.update(TrueClass => 'tinyint')
25
- @typecast.update(TrueClass => "#\{:s: ? \"1\" : 'NULL' \}")
26
- end
27
-
28
- def self.escape(str)
29
- return nil unless str
30
- return Mysql.quote(str)
31
- end
32
-
33
- def self.timestamp(time = Time.now)
34
- return nil unless time
35
- return time.strftime("%Y%m%d%H%M%S")
36
- end
37
-
38
- def self.date(date)
39
- return nil unless date
40
- return "#{date.year}-#{date.month}-#{date.mday}"
41
- end
42
-
43
- def write_prop(p)
44
- if p.klass.ancestors.include?(Integer)
45
- return "#\{@#{p.symbol} || 'NULL'\}"
46
- elsif p.klass.ancestors.include?(Float)
47
- return "#\{@#{p.symbol} || 'NULL'\}"
48
- elsif p.klass.ancestors.include?(String)
49
- return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}|
50
- elsif p.klass.ancestors.include?(Time)
51
- return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
52
- elsif p.klass.ancestors.include?(Date)
53
- return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
54
- elsif p.klass.ancestors.include?(TrueClass)
55
- return "#\{@#{p.symbol} ? 1 : 0 \}"
56
- else
57
- # gmosx: keep the '' for nil symbols.
58
- return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
59
- end
60
- end
61
-
62
- def read_prop(p, idx)
63
- if p.klass.ancestors.include?(Integer)
64
- return "res[#{idx}].to_i"
65
- elsif p.klass.ancestors.include?(Float)
66
- return "res[#{idx}].to_f"
67
- elsif p.klass.ancestors.include?(String)
68
- return "res[#{idx}]"
69
- elsif p.klass.ancestors.include?(Time)
70
- return "#{self.class}.parse_timestamp(res[#{idx}])"
71
- elsif p.klass.ancestors.include?(Date)
72
- return "#{self.class}.parse_date(res[#{idx}])"
73
- elsif p.klass.ancestors.include?(TrueClass)
74
- return "('0' != res[#{idx}])"
75
- else
76
- return "YAML::load(res[#{idx}])"
77
- end
78
- end
79
-
80
- def create_db(database, user = nil, password = nil)
81
- # gmosx: system is used to avoid shell expansion.
82
- system 'mysqladmin', '-f', "--user=#{user}", "--password=#{password}", 'create', database
83
- super
84
- end
85
-
86
- def drop_db(database, user = nil, password = nil)
87
- system 'mysqladmin', '-f', "--user=#{user}", "--password=#{password}", 'drop', database
88
- super
89
- end
90
-
91
- def props_for_insert(klass)
92
- klass.__props.reject { |p| :oid == p.symbol }
93
- end
94
-
95
- def insert_code(klass, db)
96
- props = props_for_insert(klass)
97
- values = props.collect { |p| write_prop(p) }.join(',')
98
-
99
- sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
100
-
101
- %{
102
- conn.store.query_with_result = false
103
- conn.store.query "#{sql}"
104
- @oid = conn.store.insert_id()
105
- }
106
- end
107
-
108
- def new_connection(db)
109
- return MysqlConnection.new(db)
110
- end
111
-
112
- def calc_field_index(klass, db)
113
- res = db.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
114
- meta = db.managed_classes[klass]
115
-
116
- for idx in (0...res.num_fields)
117
- meta.field_index[res.fetch_field.name] = idx
118
- end
119
-
120
- ensure
121
- res.free if res
122
- end
123
-
124
- def create_fields(klass)
125
- fields = []
126
-
127
- klass.__props.each do |p|
128
- klass.sql_index(p.symbol) if p.meta[:sql_index]
129
-
130
- field = "#{p.symbol}"
131
-
132
- if p.meta and p.meta[:sql]
133
- field << " #{p.meta[:sql]}"
134
- else
135
- field << " #{@typemap[p.klass]}"
136
-
137
- if p.meta
138
- # set default value (gmosx: not that useful in the
139
- # current implementation).
140
- if default = p.meta[:default]
141
- field << " DEFAULT #{default.inspect} NOT NULL"
142
- end
143
-
144
- # set unique
145
- # FIXME: correctly handle UNIQUE constrain.
146
- # field << " UNIQUE" if p.meta[:unique]
147
-
148
- # attach extra sql
149
- if extra_sql = p.meta[:extra_sql]
150
- field << " #{extra_sql}"
151
- end
152
- end
153
- end
154
-
155
- fields << field
156
- end
157
-
158
- return fields
159
- end
160
-
161
- def create_table(klass, db)
162
- conn = db.get_connection
163
-
164
- fields = create_fields(klass)
165
-
166
- sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
167
-
168
- conn.store.query_with_result = true
169
-
170
- # Create table constrains
171
-
172
- if klass.__meta and constrains = klass.__meta[:sql_constrain]
173
- sql << ", #{constrains.join(', ')}"
174
- end
175
-
176
- sql << ');'
177
-
178
- begin
179
- conn.store.query(sql)
180
- Logger.info "Created table '#{klass::DBTABLE}'."
181
- rescue => ex
182
- if ex.errno == 1050 # table already exists.
183
- Logger.debug "Table already exists" if $DBG
184
- return
185
- else
186
- raise
187
- end
188
- end
189
-
190
- # Create indices
191
-
192
- if klass.__meta and indices = klass.__meta[:sql_index]
193
- for data in indices
194
- idx, options = *data
195
- idx = idx.to_s
196
- pre_sql, post_sql = options[:pre], options[:post]
197
- idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
198
- conn.store.query("CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx})")
199
- end
200
- end
201
-
202
- # Create join tables if needed. Join tables are used in
203
- # 'many_to_many' relations.
204
-
205
- if klass.__meta and joins = klass.__meta[:sql_join]
206
- for data in joins
207
- # the class to join to and some options.
208
- join_name, join_class, options = *data
209
-
210
- # gmosx: dont use DBTABLE here, perhaps the join class
211
- # is not managed yet.
212
- join_table = "#{self.class.join_table(klass, join_class, join_name)}"
213
- join_src = "#{self.class.encode(klass)}_oid"
214
- join_dst = "#{self.class.encode(join_class)}_oid"
215
- begin
216
- conn.store.query("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )")
217
- conn.store.query("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)")
218
- conn.store.query("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)")
219
- rescue => ex
220
- if ex.errno == 1050 # table already exists.
221
- Logger.debug "Join table already exists" if $DBG
222
- else
223
- raise
224
- end
225
- end
226
- end
227
- end
228
-
229
- ensure
230
- db.put_connection
231
- end
232
-
233
- def eval_og_oid(klass)
234
- klass.class_eval %{
235
- prop_accessor :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY'
236
- }
237
- end
238
- end
239
-
240
- # The MySQL connection.
241
-
242
- class MysqlConnection < Connection
243
-
244
- def initialize(db)
245
- super
246
-
247
- config = db.config
248
-
249
- @store = Mysql.connect(
250
- config[:address] || 'localhost',
251
- config[:user],
252
- config[:password],
253
- config[:database]
254
- )
255
- rescue => ex
256
- if ex.errno == 1049 # database does not exist.
257
- Logger.info "Database '#{config[:database]}' not found!"
258
- @db.adapter.create_db(config[:database], config[:user], config[:password])
259
- retry
260
- end
261
- raise
262
- end
263
-
264
- def close
265
- @store.close
266
- super
267
- end
268
-
269
- def prepare(sql)
270
- raise 'Not implemented!'
271
- end
272
-
273
- def query(sql)
274
- Logger.debug sql if $DBG
275
- begin
276
- @store.query_with_result = true
277
- return @store.query(sql)
278
- rescue => ex
279
- handle_db_exception(ex, sql)
280
- end
281
- end
282
-
283
- def exec(sql)
284
- Logger.debug sql if $DBG
285
- begin
286
- @store.query_with_result = false
287
- @store.query(sql)
288
- rescue => ex
289
- handle_db_exception(ex, sql)
290
- end
291
- end
292
-
293
- def start
294
- # @store.transaction
295
- end
296
-
297
- def commit
298
- # @store.commit
299
- end
300
-
301
- def rollback
302
- # @store.rollback
303
- end
304
-
305
- def valid_res?(res)
306
- return !(res.nil? or 0 == res.num_rows)
307
- end
308
-
309
- def read_one(res, klass)
310
- return nil unless valid_res?(res)
311
-
312
- row = res.fetch_row
313
- obj = klass.allocate
314
- obj.og_read(row)
315
-
316
- res.free
317
- return obj
318
- end
319
-
320
- def read_all(res, klass)
321
- return [] unless valid_res?(res)
322
-
323
- objects = []
324
-
325
- for tuple in (0...res.num_rows)
326
- row = res.fetch_row
327
-
328
- obj = klass.allocate
329
- obj.og_read(row)
330
-
331
- objects << obj
332
- end
333
-
334
- res.free
335
- return objects
336
- end
337
-
338
- def read_int(res, idx = 0)
339
- val = res.fetch_row[idx].to_i
340
- res.free
341
- return val
342
- end
343
-
344
- def get_row(res)
345
- res.fetch_row
346
- end
347
-
348
- end
349
-
350
- end