og 0.31.0 → 0.40.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 (97) hide show
  1. data/doc/{AUTHORS → CONTRIBUTORS} +26 -10
  2. data/doc/LICENSE +2 -3
  3. data/doc/RELEASES +56 -7
  4. data/doc/tutorial.txt +15 -15
  5. data/lib/glue/cacheable.rb +2 -5
  6. data/lib/glue/hierarchical.rb +1 -4
  7. data/lib/glue/optimistic_locking.rb +0 -2
  8. data/lib/glue/orderable.rb +79 -75
  9. data/lib/glue/revisable.rb +19 -24
  10. data/lib/glue/searchable.rb +0 -2
  11. data/lib/glue/taggable.rb +31 -29
  12. data/lib/glue/timestamped.rb +4 -2
  13. data/lib/og.rb +50 -29
  14. data/lib/og/adapter.rb +19 -0
  15. data/lib/og/adapter/mysql.rb +212 -0
  16. data/lib/og/adapter/mysql/override.rb +34 -0
  17. data/lib/og/adapter/mysql/script.rb +15 -0
  18. data/lib/og/adapter/mysql/utils.rb +40 -0
  19. data/lib/og/adapter/postgresql.rb +231 -0
  20. data/lib/og/adapter/postgresql/override.rb +117 -0
  21. data/lib/og/adapter/postgresql/script.rb +15 -0
  22. data/lib/og/adapter/postgresql/utils.rb +35 -0
  23. data/lib/og/adapter/sqlite.rb +132 -0
  24. data/lib/og/adapter/sqlite/override.rb +33 -0
  25. data/lib/og/adapter/sqlite/script.rb +15 -0
  26. data/lib/og/collection.rb +35 -7
  27. data/lib/og/{evolution.rb → dump.rb} +4 -5
  28. data/lib/og/entity.rb +102 -173
  29. data/lib/og/entity/clone.rb +119 -0
  30. data/lib/og/errors.rb +0 -2
  31. data/lib/og/manager.rb +85 -37
  32. data/lib/og/relation.rb +52 -34
  33. data/lib/og/relation/belongs_to.rb +0 -2
  34. data/lib/og/relation/has_many.rb +27 -4
  35. data/lib/og/relation/joins_many.rb +41 -14
  36. data/lib/og/relation/many_to_many.rb +10 -0
  37. data/lib/og/relation/refers_to.rb +22 -5
  38. data/lib/og/store.rb +80 -86
  39. data/lib/og/store/sql.rb +710 -713
  40. data/lib/og/store/sql/evolution.rb +119 -0
  41. data/lib/og/store/sql/join.rb +155 -0
  42. data/lib/og/store/sql/utils.rb +149 -0
  43. data/lib/og/test/assertions.rb +1 -3
  44. data/lib/og/test/testcase.rb +0 -2
  45. data/lib/og/types.rb +2 -5
  46. data/lib/og/validation.rb +6 -9
  47. data/test/{og/mixin → glue}/tc_hierarchical.rb +3 -13
  48. data/test/glue/tc_og_paginate.rb +47 -0
  49. data/test/{og/mixin → glue}/tc_optimistic_locking.rb +2 -12
  50. data/test/{og/mixin → glue}/tc_orderable.rb +15 -23
  51. data/test/glue/tc_orderable2.rb +47 -0
  52. data/test/glue/tc_revisable.rb +3 -3
  53. data/test/{og/mixin → glue}/tc_taggable.rb +20 -10
  54. data/test/{og/mixin → glue}/tc_timestamped.rb +2 -12
  55. data/test/glue/tc_webfile.rb +36 -0
  56. data/test/og/CONFIG.rb +8 -11
  57. data/test/og/multi_validations_model.rb +14 -0
  58. data/test/og/store/tc_filesys.rb +3 -1
  59. data/test/og/store/tc_kirby.rb +16 -13
  60. data/test/og/store/tc_sti.rb +11 -11
  61. data/test/og/store/tc_sti2.rb +79 -0
  62. data/test/og/tc_build.rb +41 -0
  63. data/test/og/tc_cacheable.rb +3 -2
  64. data/test/og/tc_has_many.rb +96 -0
  65. data/test/og/tc_inheritance.rb +6 -4
  66. data/test/og/tc_joins_many.rb +93 -0
  67. data/test/og/tc_multi_validations.rb +5 -7
  68. data/test/og/tc_multiple.rb +7 -6
  69. data/test/og/tc_override.rb +13 -7
  70. data/test/og/tc_primary_key.rb +30 -0
  71. data/test/og/tc_relation.rb +8 -14
  72. data/test/og/tc_reldelete.rb +163 -0
  73. data/test/og/tc_reverse.rb +17 -14
  74. data/test/og/tc_scoped.rb +3 -11
  75. data/test/og/tc_setup.rb +13 -11
  76. data/test/og/tc_store.rb +21 -28
  77. data/test/og/tc_validation2.rb +2 -2
  78. data/test/og/tc_validation_loop.rb +17 -15
  79. metadata +109 -103
  80. data/INSTALL +0 -91
  81. data/ProjectInfo +0 -51
  82. data/README +0 -177
  83. data/doc/config.txt +0 -28
  84. data/examples/README +0 -23
  85. data/examples/mysql_to_psql.rb +0 -71
  86. data/examples/run.rb +0 -271
  87. data/lib/glue/tree.rb +0 -218
  88. data/lib/og/store/alpha/filesys.rb +0 -110
  89. data/lib/og/store/alpha/memory.rb +0 -295
  90. data/lib/og/store/alpha/sqlserver.rb +0 -256
  91. data/lib/og/store/kirby.rb +0 -490
  92. data/lib/og/store/mysql.rb +0 -415
  93. data/lib/og/store/psql.rb +0 -875
  94. data/lib/og/store/sqlite.rb +0 -348
  95. data/lib/og/store/sqlite2.rb +0 -241
  96. data/setup.rb +0 -1585
  97. data/test/og/tc_sti_find.rb +0 -35
@@ -1,348 +0,0 @@
1
- begin
2
- require 'sqlite3'
3
- rescue Object => ex
4
- Logger.error 'Ruby-Sqlite3 bindings are not installed!'
5
- Logger.error ex
6
- end
7
-
8
- require 'fileutils'
9
- require 'set'
10
-
11
- require 'og/store/sql'
12
-
13
- #--
14
- # Customize the standard Sqlite3 resultset to make
15
- # more compatible with Og.
16
- #++
17
-
18
- class SQLite3::ResultSet # :nodoc: all
19
- alias_method :blank?, :eof?
20
-
21
- def each_row
22
- each do |row|
23
- yield(row, 0)
24
- end
25
- end
26
-
27
- def first_value
28
- val = self.next[0]
29
- close
30
- return val
31
- end
32
-
33
- alias_method :fields, :columns
34
- end
35
-
36
- module Og
37
-
38
- # A Store that persists objects into an Sqlite3 database.
39
- # To read documentation about the methods, consult the documentation
40
- # for SqlStore and Store.
41
-
42
- class SqliteStore < SqlStore
43
-
44
- # Override if needed.
45
-
46
- def self.db_filename(options)
47
- options[:name] ||= 'data'
48
- "#{options[:name]}.db"
49
- end
50
-
51
- def self.destroy(options)
52
- begin
53
- FileUtils.rm(db_filename(options))
54
- super
55
- rescue Object
56
- Logger.info "Cannot drop '#{options[:name]}'!"
57
- end
58
- end
59
-
60
- # Initialize the Sqlite store.
61
- # This store provides a default name.
62
-
63
- def initialize(options)
64
- super
65
- @conn = SQLite3::Database.new(self.class.db_filename(options))
66
- end
67
-
68
- def close
69
- @conn.close
70
- super
71
- end
72
-
73
- def enchant(klass, manager)
74
- if klass.ann.self.primary_key.symbol == :oid
75
- unless klass.properties.include? :oid
76
- klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY'
77
- end
78
- end
79
-
80
- super
81
- end
82
-
83
- def query(sql)
84
- Logger.debug sql if $DBG
85
- return @conn.query(sql)
86
- rescue Exception => ex
87
- handle_sql_exception(ex, sql)
88
- end
89
-
90
- def exec(sql)
91
- Logger.debug sql if $DBG
92
- @conn.query(sql).close
93
- rescue => ex
94
- handle_sql_exception(ex, sql)
95
- end
96
-
97
- def start
98
- @conn.transaction if @transaction_nesting < 1
99
- @transaction_nesting += 1
100
- end
101
-
102
- def commit
103
- @transaction_nesting -= 1
104
- @conn.commit if @transaction_nesting < 1
105
- end
106
-
107
- def rollback
108
- @transaction_nesting -= 1
109
- @conn.rollback if @transaction_nesting < 1
110
- end
111
-
112
- def last_insert_rowid
113
- conn.query("SELECT last_insert_rowid()").first_value.to_i
114
- end
115
-
116
- def sql_update(sql)
117
- exec(sql)
118
- @conn.changes
119
- end
120
-
121
- def list_tables
122
- rset = @conn.query("select name from sqlite_master where type = 'table'")
123
- rset.map { |r| r[0] }
124
- end
125
-
126
- def list_fields(table)
127
- rset = @conn.query("pragma table_info(#{table})")
128
- columns = rset.columns
129
- rows = rset.entries
130
- idx = columns.index('name')
131
- rows.map { |r| r[idx] }
132
- end
133
-
134
- # Deserialize one object from the ResultSet.
135
-
136
- def read_one(res, klass, options = nil)
137
- return nil if res.blank?
138
-
139
- if options and join_relations = options[:include]
140
- join_relations = [join_relations].flatten.collect do |n|
141
- klass.relation(n)
142
- end
143
- end
144
-
145
- res_row = res.next
146
-
147
- # causes STI classes to come back as the correct child class
148
- # if accessed from the superclass.
149
-
150
- klass = Og::Entity::entity_from_string(res_row[0]) if klass.schema_inheritance?
151
- obj = klass.og_allocate(res_row, 0)
152
-
153
- if options and options[:select]
154
- read_row(obj, res, res_row, 0)
155
- else
156
- obj.og_read(res_row)
157
- read_join_relations(obj, res_row, 0, join_relations) if join_relations
158
- end
159
-
160
- return obj
161
-
162
- ensure
163
- res.close
164
- end
165
-
166
- private
167
-
168
- def property_to_field(klass, p)
169
- field = []
170
-
171
- klass.index(p.symbol) if p.index
172
-
173
- field = field_for_property(p)
174
-
175
- if p.sql
176
- field << " #{p.sql}"
177
- else
178
- field << " #{type_for_class(p.klass)}"
179
- field << " UNIQUE" if p.unique
180
- field << " DEFAULT #{p.default.inspect} NOT NULL" if p.default
181
- field << " #{p.extra_sql}" if p.extra_sql
182
- end
183
-
184
- field
185
- end
186
-
187
- def create_table_ddl(klass, type = nil, table_name = nil)
188
- fields = fields_for_class(klass)
189
- type ||= ''
190
- table_name ||= klass::OGTABLE
191
-
192
- sql = "CREATE #{type} TABLE #{table_name} (#{fields.join(', ')}"
193
-
194
- # Create table constraints.
195
-
196
- if constraints = klass.ann.self[:sql_constraint]
197
- sql << ", #{constraints.join(', ')}"
198
- end
199
-
200
- sql << ")"
201
-
202
- # Create indices.
203
-
204
- if indices = klass.ann.self[:index]
205
- for data in indices
206
- idx, options = *data
207
- idx = idx.to_s
208
- pre_sql, post_sql = options[:pre], options[:post]
209
- idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
210
- sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"
211
- end
212
- end
213
-
214
- sql
215
- end
216
-
217
- def create_table(klass)
218
- unless list_tables.include?(klass::OGTABLE)
219
- sql = create_table_ddl(klass)
220
-
221
- @conn.query(sql).close
222
- Logger.info "Created table '#{klass::OGTABLE}'."
223
- else
224
- Logger.debug "Table '#{klass::OGTABLE}' already exists" if $DBG
225
-
226
- actual_fields = Set.new(list_fields(klass::OGTABLE))
227
- defined_fields = Set.new(fields_for_class(klass).map { |f| f[0, f.index(' ')] } )
228
-
229
- unless defined_fields == actual_fields
230
- Logger.debug "Field mismatch in '#{klass::OGTABLE}'. Attempting to correct..." if $DBG
231
-
232
- fields_to_add = defined_fields - actual_fields
233
- fields_to_delete = actual_fields - defined_fields
234
-
235
- fields_to_add.each do |field|
236
- if @options[:evolve_schema] == true
237
- Logger.debug "Adding field '#{field}' to '#{klass::OGTABLE}'" if $DBG
238
- sql = "ALTER TABLE #{klass::OGTABLE} ADD COLUMN #{property_to_field(klass, klass.properties[field.to_sym])}"
239
- @conn.query(sql)
240
- @conn.query("VACUUM #{klass::OGTABLE}")
241
- else
242
- Logger.warn "Table '#{klass::OGTABLE} is missing field '#{field}' and :evolve_schema is not set to true or :cautious!"
243
- end
244
- end
245
-
246
- # Sqlite3 does _NOT_ support removing columns via alter table. You have to drop the table.
247
-
248
- unless fields_to_delete.empty?
249
- if (@options[:evolve_schema] == true) and (@options[:evolve_schema_cautious] == false)
250
- Logger.warn "Removing obsolete fields '#{fields_to_delete.to_a.join(', ')}' from '#{klass::OGTABLE}'!"
251
-
252
- field_list = klass.properties.values.map { |f| field_for_property(f) }.join(', ')
253
- backup_table = "#{klass::OGTABLE}_backup"
254
-
255
- sql_transaction = [
256
- "BEGIN TRANSACTION",
257
- "#{create_table_ddl(klass, 'temporary', backup_table)}",
258
- "INSERT INTO #{backup_table} SELECT #{field_list} FROM #{klass::OGTABLE}",
259
- "DROP TABLE #{klass::OGTABLE}",
260
- "#{create_table_ddl(klass)}",
261
- "INSERT INTO #{klass::OGTABLE} SELECT #{field_list} FROM #{backup_table}",
262
- "DROP TABLE #{backup_table}",
263
- "COMMIT"
264
- ]
265
-
266
- sql_transaction.each do |s|
267
- Logger.debug s if $DBG
268
- @conn.query(s)
269
- end
270
- else
271
- Logger.warn "Table '#{klass::OGTABLE}' contains obsolete fields '#{fields_to_delete.to_a.join(', ')}' " +
272
- "and :evolve_schema is not set to true or is in cautious mode!"
273
- end
274
- end
275
- end
276
- end
277
-
278
- # Create join tables if needed. Join tables are used in
279
- # 'many_to_many' relations.
280
-
281
- if join_tables = klass.ann.self[:join_tables]
282
- for info in join_tables
283
- begin
284
- create_join_table_sql(info).each do |sql|
285
- @conn.query(sql).close
286
- end
287
- Logger.debug "Created jointable '#{info[:table]}'." if $DBG
288
- rescue Object => ex
289
- # gmosx: any idea how to better test this?
290
- if ex.to_s =~ /table .* already exists/i
291
- Logger.debug 'Join table already exists' if $DBG
292
- else
293
- raise
294
- end
295
- end
296
- end
297
- end
298
- end
299
-
300
- def create_field_map(klass)
301
- res = @conn.query "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
302
- map = {}
303
-
304
- fields = res.columns
305
-
306
- # Check if the field should be ignored.
307
- ignore = klass.ann[:self][:ignore_field] || klass.ann[:self][:ignore_fields] || klass.ann[:self][:ignore_columns]
308
-
309
- res.fields.size.times do |i|
310
- field_name = fields[i].to_sym
311
- unless (ignore and ignore.include?(field_name))
312
- map[field_name] = i
313
- end
314
- end
315
-
316
- return map
317
- ensure
318
- res.close if res
319
- end
320
-
321
- def eval_og_insert(klass)
322
- pk = klass.primary_key.symbol
323
- props = klass.properties.values.dup
324
- values = props.collect { |p| write_prop(p) }.join(',')
325
-
326
- if klass.schema_inheritance?
327
- props << Property.new(:symbol => :ogtype, :klass => String)
328
- values << ", '#{klass}'"
329
- end
330
-
331
- sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| field_for_property(p)}.join(',')}) VALUES (#{values})"
332
-
333
- klass.class_eval %{
334
- def og_insert(store)
335
- #{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
336
- store.query("#{sql}").close
337
- @#{pk} = store.last_insert_rowid
338
- #{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
339
- end
340
- }
341
- end
342
-
343
- end
344
-
345
- end
346
-
347
- # * George Moschovitis <gm@navel.gr>
348
- # * Ghislain Mary
@@ -1,241 +0,0 @@
1
- #--
2
- # TODO: extend from sqlite.rb to avoid code duplication.
3
- #++
4
-
5
- begin
6
- require 'sqlite'
7
- rescue Object => ex
8
- Logger.error 'Ruby-Sqlite bindings are not installed!'
9
- Logger.error ex
10
- end
11
-
12
- require 'fileutils'
13
-
14
- require 'og/store/sql'
15
-
16
- #--
17
- # Customize the standard Sqlite2 resultset to make
18
- # more compatible with Og.
19
- #++
20
-
21
- class SQLite::ResultSet # :nodoc: all
22
- alias_method :blank?, :eof?
23
-
24
- def each_row
25
- each do |row|
26
- yield(row, 0)
27
- end
28
- end
29
-
30
- def first_value
31
- val = self.next[0]
32
- close
33
- return val
34
- end
35
-
36
- alias_method :fields, :columns
37
- end
38
-
39
- module Og
40
-
41
- # A Store that persists objects into an Sqlite2 database.
42
- # To read documentation about the methods, consult the documentation
43
- # for SqlStore and Store.
44
-
45
- class Sqlite2Store < SqlStore
46
-
47
- # Override if needed.
48
-
49
- def self.db_filename(options)
50
- options[:name] ||= 'data'
51
- "#{options[:name]}.db"
52
- end
53
-
54
- def self.destroy(options)
55
- begin
56
- FileUtils.rm(db_filename(options))
57
- super
58
- rescue Object
59
- Logger.info "Cannot drop '#{options[:name]}'!"
60
- end
61
- end
62
-
63
- # Initialize the Sqlite store.
64
- # This store provides a default name.
65
-
66
- def initialize(options)
67
- super
68
- @conn = SQLite::Database.new(self.class.db_filename(options))
69
- end
70
-
71
- def close
72
- @conn.close
73
- super
74
- end
75
-
76
- def enchant(klass, manager)
77
- if klass.ann.self.primary_key.symbol == :oid
78
- unless klass.properties.include? :oid
79
- klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY'
80
- end
81
- end
82
-
83
- super
84
- end
85
-
86
- def query(sql)
87
- Logger.debug sql if $DBG
88
- return @conn.query(sql)
89
- rescue Exception => ex
90
- handle_sql_exception(ex, sql)
91
- end
92
-
93
- def exec(sql)
94
- Logger.debug sql if $DBG
95
- @conn.query(sql).close
96
- rescue => ex
97
- handle_sql_exception(ex, sql)
98
- end
99
-
100
- def start
101
- @conn.transaction if @transaction_nesting < 1
102
- @transaction_nesting += 1
103
- end
104
-
105
- def commit
106
- @transaction_nesting -= 1
107
- @conn.commit if @transaction_nesting < 1
108
- end
109
-
110
- def rollback
111
- @transaction_nesting -= 1
112
- @conn.rollback if @transaction_nesting < 1
113
- end
114
-
115
- def last_insert_rowid
116
- conn.query("SELECT last_insert_rowid()").first_value.to_i
117
- end
118
-
119
- def sql_update(sql)
120
- exec(sql)
121
- @conn.changes
122
- end
123
-
124
- private
125
-
126
- def create_table(klass)
127
- fields = fields_for_class(klass)
128
-
129
- sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
130
-
131
- # Create table constraints.
132
-
133
- if constraints = klass.ann.self[:sql_constraint]
134
- sql << ", #{constraints.join(', ')}"
135
- end
136
-
137
- sql << ");"
138
-
139
- # Create indices.
140
-
141
- if indices = klass.ann.self[:index]
142
- for data in indices
143
- idx, options = *data
144
- idx = idx.to_s
145
- pre_sql, post_sql = options[:pre], options[:post]
146
- idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
147
- sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"
148
- end
149
- end
150
-
151
- begin
152
- @conn.query(sql).close
153
- Logger.info "Created table '#{klass::OGTABLE}'."
154
- rescue Object => ex
155
- # gmosx: any idea how to better test this?
156
- if ex.to_s =~ /table .* already exists/i
157
- Logger.debug 'Table already exists' if $DBG
158
- return
159
- else
160
- raise
161
- end
162
- end
163
-
164
- # Create join tables if needed. Join tables are used in
165
- # 'many_to_many' relations.
166
-
167
- if join_tables = klass.ann.self[:join_tables]
168
- for info in join_tables
169
- begin
170
- create_join_table_sql(info).each do |sql|
171
- @conn.query(sql).close
172
- end
173
- Logger.debug "Created jointable '#{info[:table]}'." if $DBG
174
- rescue Object => ex
175
- # gmosx: any idea how to better test this?
176
- if ex.to_s =~ /table .* already exists/i
177
- Logger.debug 'Join table already exists' if $DBG
178
- else
179
- raise
180
- end
181
- end
182
- end
183
- end
184
- end
185
-
186
- def create_field_map(klass)
187
- res = @conn.query "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
188
- map = {}
189
-
190
- fields = res.columns
191
-
192
- # Check if the field should be ignored.
193
- ignore = klass.ann[:self][:ignore_field] || klass.ann[:self][:ignore_fields] || klass.ann[:self][:ignore_columns]
194
-
195
- res.fields.size.times do |i|
196
- field_name = fields[i].to_sym
197
- unless (ignore and ignore.include?(field_name))
198
- map[field_name] = i
199
- end
200
- end
201
-
202
- return map
203
- ensure
204
- res.close if res
205
- end
206
-
207
- def eval_og_insert(klass)
208
- pk = klass.primary_key.symbol
209
- props = klass.properties.values.dup
210
- values = props.collect { |p| write_prop(p) }.join(',')
211
-
212
- if klass.schema_inheritance?
213
- props << Property.new(:symbol => :ogtype, :klass => String)
214
- values << ", '#{klass}'"
215
- end
216
-
217
- sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| field_for_property(p)}.join(',')}) VALUES (#{values})"
218
-
219
- klass.class_eval %{
220
- def og_insert(store)
221
- #{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
222
- store.query("#{sql}").close
223
- @#{pk} = store.last_insert_rowid
224
- #{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
225
- end
226
- }
227
- end
228
-
229
- end
230
-
231
- end
232
-
233
- # * George Moschovitis <gm@navel.gr>
234
- # * Ghislain Mary
235
- # * Mitchell Foral
236
-
237
-
238
-
239
-
240
-
241
-