og 0.31.0 → 0.40.0

Sign up to get free protection for your applications and to get access to all the features.
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,218 +0,0 @@
1
- # NOT WORKING YET !!!
2
-
3
- raise 'This is not working yet, do not require this file.'
4
-
5
- # A useful encapsulation of the nested intervals pattern for
6
- # hierarchical SQL queries. Slightly adapted from the original
7
- # article (http://www.dbazine.com/tropashko4.shtml)
8
-
9
- module TreeTraversal
10
-
11
- # The default prefix for the tree traversal helpers.
12
-
13
- cattr_accessor :prefix, 'tree'
14
-
15
- def self.child(sum, n)
16
- power = 2 ** n
17
- return sum * power
18
- end
19
-
20
- end
21
-
22
- __END__
23
-
24
- def xcoord(numer, denom)
25
- num = numer + 1
26
- den = denom * 2
27
-
28
- while (num / 2).floor == (num / 2)
29
- num /= 2
30
- den /= 2
31
- end
32
-
33
- return num, den
34
- end
35
-
36
- #--
37
- # TODO: optimize this
38
- #++
39
-
40
- def ycoord(numer, denom)
41
- num, den = xcoord(numer, denom)
42
-
43
- while den < denom
44
- num *= 2
45
- den *= 2
46
- end
47
-
48
- num = numer - num
49
-
50
- while (num / 2).floor == (num / 2)
51
- num /= 2
52
- den /= 2
53
- end
54
-
55
- return num, den
56
- end
57
-
58
- def parent(numer, denom)
59
- return nil if numer == 3
60
-
61
- num = (numer - 1) / 2
62
- den = denom / 2
63
-
64
- while ((num-1)/4).floor == ((num-1)/4)
65
- num = (num + 1) / 2
66
- den = den / 2
67
- end
68
-
69
- return num, den
70
- end
71
-
72
- def sibling(numer, denom)
73
- return nil if numer == 3
74
-
75
- num = (numer - 1) / 2
76
- den = denom / 2
77
- sib = 1
78
-
79
- while ((num-1)/4).floor == ((num-1)/4)
80
- return sib if num == 1 and den == 1
81
- num = (num + 1) / 2
82
- den /= 2
83
- sib += 1
84
- end
85
-
86
- return sib
87
- end
88
-
89
- def child(numer, denom, n)
90
- power = 2 ** n
91
-
92
- num = (numer * power) + 3 - power
93
- den = denom * power
94
-
95
- return num, den
96
- end
97
-
98
- def path(numer, denom)
99
- return '' if numer == nil
100
- n, d = parent(numer, denom)
101
- return "#{path(n, d)}.#{sibling(numer, denom)}"
102
- end
103
-
104
- def encode(path)
105
- num = den = 1
106
- postfix = ".#{path}."
107
-
108
- while postfix.length > 1
109
- sibling, postfix = postfix.split('.', 2)
110
- num, den = child(num, den, sibling.to_i)
111
- end
112
-
113
- return num, den
114
- end
115
-
116
-
117
- require 'og'
118
-
119
- class Comment
120
- property :path, String
121
- property :x, :y, Float
122
-
123
- def initialize(path = nil, x = nil, y = nil)
124
- @path, @x, @y = path, x, y
125
- end
126
- end
127
-
128
- Og::Database.new(
129
- :database => 'test',
130
- :adapter => 'psql',
131
- :user => 'postgres',
132
- :password => 'navelrulez',
133
- :connection_count => 1,
134
- :drop => true
135
- )
136
-
137
- def dp(path)
138
- n, d = encode(path)
139
- n, d = Float(n), Float(d)
140
- # puts "#{path} -> n: #{n} d: #{d} c: #{n/d}"
141
- p = path(n, d)
142
- # puts "=== #{p}"
143
- xn, xd = xcoord(n, d)
144
- yn, yd = ycoord(n, d)
145
- Comment.create(path, xn/xd, yn/yd)
146
- end
147
-
148
- dp '1.1'
149
- dp '1.2'
150
- dp '1.3'
151
- dp '1.1.2.1'
152
- dp '1.5.2.1'
153
- dp '1.2.1.1.1'
154
- dp '1.4.1.1'
155
- dp '1.4.3'
156
- dp '1.4.1'
157
- dp '1.2.1'
158
- dp '1.2.1.2'
159
- dp '1.5'
160
- dp '1.5.1'
161
-
162
- for c in Comment.all('ORDER BY x DESC, y ASC')
163
- puts "#{c.path.ljust(16)}#{c.inspect}"
164
- end
165
-
166
- class Article
167
- property :title, :body, String
168
- has_many: :comments, Comment, :tree => true
169
- end
170
-
171
- class Comment
172
- property :body, String
173
- belongs_to :article, Article
174
- belongs_to :parent, Comment
175
- has_many :children, Comment, :tree => true
176
- has_many :roles, Role, :list => true
177
- end
178
-
179
-
180
- article.comments_tree
181
- comment.add_child(Comment.new('hello'))
182
- comment.children_tree
183
- comment.children
184
-
185
- if options[:tree]
186
- code << %{
187
- property :#{prefix}_x, Fixnum
188
- property :#{prefix}_y, Fixnum
189
- sql_index '#{prefix}_x, #{prefix}_y'
190
- }
191
- end
192
-
193
- if options[:tree]
194
-
195
- code << %{
196
- def #{name}_tree(extrasql = nil)
197
- Og.db.select("SELECT * FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\} ORDER BY #{prefix}_x DESC, #{prefix}_y ASC", #{klass})
198
- end
199
- }
200
-
201
- elsif options[:list]
202
-
203
- else
204
-
205
- end
206
-
207
- }
208
-
209
- if options[:tree]
210
- code << %{
211
- n = Og.db.count("#{linkback}=\#\@oid", #{klass})
212
- ptx = @#{prefix}_x || 0
213
- pty = @#{prefix}_y || 0
214
- obj.#{prefix}_x, obj.#{prefix}_y = TreeTraversal.child(ptx + pty, n)
215
- }
216
- end
217
-
218
- code << %{
@@ -1,110 +0,0 @@
1
- require 'yaml'
2
- require 'fileutils'
3
-
4
- require 'og/store'
5
-
6
- module Og
7
-
8
- # A Store that saves the object on the Filesystem.
9
- # An extension of the Memory store.
10
-
11
- class FilesysStore < Store
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
- # Initialize a connection to this store.
34
-
35
- def initialize(options)
36
- super
37
-
38
- @base_dir = self.class.dir(options[:name])
39
-
40
- unless File.directory?(@base_dir)
41
- Logger.info "Database '#{options[:name]}' not found!"
42
- self.class.create(options)
43
- end
44
- end
45
-
46
- def enchant(klass, manager)
47
- super
48
-
49
- klass.const_set 'OGNAME', klass.to_s.gsub(/::/, "/").downcase
50
- klass.property :oid, Fixnum
51
-
52
- FileUtils.mkdir_p(path(klass))
53
- File.open(spath(klass), 'w') { |f| f << '1' }
54
- rescue => ex
55
- Logger.error "Cannot create directory to store '#{klass}' classes!"
56
- end
57
-
58
- # :section: Lifecycle methods.
59
-
60
- def load(oid, klass)
61
- obj = nil
62
- File.open(path(klass, oid), 'r') { |f| obj = YAML::load(f.read) }
63
- return obj
64
- rescue
65
- return nil
66
- end
67
-
68
- def reload(obj)
69
- end
70
-
71
- def save(obj)
72
- seq = nil
73
- File.open(spath(obj.class), 'r') { |f| seq = f.read.to_i }
74
- obj.oid = seq
75
- File.open(path(obj.class, obj.oid), 'w') { |f| f << obj.to_yaml }
76
- seq += 1
77
- File.open(spath(obj.class), 'w') { |f| f << seq }
78
- end
79
-
80
- def delete(obj_or_pk, klass = nil, cascade = true)
81
- pk = obj_or_pk.is_a?(Fixnum) ? obj_or_pk : obj_or_pk.oid
82
- klass ||= obj_or_pk.class
83
- FileUtils.rm(path(klass, pk))
84
- end
85
-
86
- def find
87
- end
88
-
89
- def count
90
- end
91
-
92
- private
93
-
94
- # Path helper.
95
-
96
- def path(klass, pk = nil)
97
- class_dir = File.join(@base_dir, klass::OGNAME)
98
- pk ? File.join(class_dir, "#{pk}.yml") : class_dir
99
- end
100
-
101
- # Path to sequence helper.
102
-
103
- def spath(klass)
104
- File.join(path(klass), 'seq')
105
- end
106
-
107
- end
108
-
109
- end
110
-
@@ -1,295 +0,0 @@
1
- require 'yaml'
2
- require 'fileutils'
3
-
4
- module Og
5
-
6
- # A collection of utilities. Mainly to stay compatible with the
7
- # SQL based backends.
8
- #
9
- # WARNING: This store does not yet support all Og features.
10
-
11
- module MemoryUtils
12
-
13
- # FIXME: find a neutral name.
14
-
15
- def table(klass)
16
- klass.to_s
17
- end
18
-
19
- # FIXME: find a neutral name.
20
-
21
- def join_table(class1, class2, postfix = nil)
22
- "#{class1}#{class2}"
23
- end
24
-
25
- def quote(val)
26
- val.inspect
27
- end
28
-
29
- end
30
-
31
- # A Store that 'persists' objects into (RAM) memory.
32
- #--
33
- # TODO: fully working query.
34
- # TODO: arbitrary pk.
35
- #++
36
-
37
- class MemoryStore < Store
38
- extend MemoryUtils; include MemoryUtils
39
-
40
- class ObjectHash < Hash
41
- def [](key)
42
- super(key.to_s)
43
- end
44
-
45
- def []=(key, value)
46
- super(key.to_s, value)
47
- end
48
- end
49
-
50
- # This hash implements an in-memory database.
51
-
52
- @objects = ObjectHash.new
53
-
54
- def self.objects
55
- @objects
56
- end
57
-
58
- def self.objects=(val)
59
- @objects = val
60
- end
61
-
62
- # A pseudo-connection to the actual object store.
63
-
64
- attr_accessor :conn
65
-
66
- def self.destroy(options)
67
- FileUtils.rm_rf("#{options[:name]}.db")
68
- rescue
69
- Logger.info "Cannot destroy '#{name}'"
70
- end
71
-
72
- def initialize(options)
73
- super
74
- begin
75
- @conn = self.class.objects = YAML.load_file("#{@options[:name]}.db")
76
- rescue
77
- @conn = self.class.objects = ObjectHash.new
78
- end
79
- end
80
-
81
- def close
82
- File.open("#{@options[:name]}.db", 'w') { |f| f << @conn.to_yaml }
83
- end
84
-
85
- # Enchants a class.
86
-
87
- def enchant(klass, manager)
88
- klass.property :oid, Fixnum
89
-
90
- super
91
-
92
- @conn[klass] ||= {}
93
-
94
- eval_og_insert(klass)
95
- eval_og_update(klass)
96
- eval_og_read(klass)
97
- eval_og_delete(klass)
98
- end
99
-
100
- # :section: Lifecycle methods.
101
-
102
- # Loads an object from the store using the primary key.
103
-
104
- def load(pk, klass)
105
- @conn[klass][pk]
106
- end
107
-
108
- # Update selected properties of an object or class of
109
- # objects.
110
-
111
- def update_properties(target, set, options = nil)
112
- set = set.gsub(/,/, ';')
113
- if target.is_a?(Class)
114
- if options
115
- condition = options[:condition] || options[:where]
116
- else
117
- condition = 'true'
118
- end
119
- for obj in @conn[target].values
120
- obj.instance_eval %{
121
- if #{condition.gsub(/=/, '==')}
122
- #{set}
123
- end
124
- }
125
- end
126
- else
127
- target.instance_eval(set)
128
- end
129
- end
130
- alias_method :pupdate, :update_properties
131
- alias_method :update_property, :update_properties
132
-
133
- # Find a collection of objects.
134
- #
135
- # === Examples
136
- #
137
- # User.find(:condition => 'age > 15', :order => 'score ASC', :offet => 10, :limit =>10)
138
- # Comment.find(:include => :entry)
139
- # store.find(:class => User, :where => 'age > 15')
140
-
141
- def find(options)
142
- query(options)
143
- end
144
-
145
- # Find one object.
146
-
147
- def find_one(options)
148
- query(options).first
149
- end
150
-
151
- # Reloads an object from the store.
152
-
153
- def reload(obj, pk)
154
- # Nop, the memory store is always synchronized.
155
- end
156
-
157
- # Count results.
158
-
159
- def count(options)
160
- objects = 0
161
-
162
- if condition = options[:condition] || options[:where]
163
- condition = "obj." + condition.gsub(/=/, '==')
164
- else
165
- condition = true
166
- end
167
-
168
- eval %{
169
- for obj in @conn[options[:class]].values
170
- objects += 1 if #{condition}
171
- end
172
- }
173
-
174
- return objects
175
- end
176
-
177
- # Relate two objects through an intermediate join table.
178
- # Typically used in joins_many and many_to_many relations.
179
-
180
- def join(obj1, obj2, table)
181
- # nop
182
- end
183
-
184
- # Query.
185
-
186
- def query(options)
187
- objects = []
188
-
189
- if condition = options[:condition] || options[:where]
190
- condition = "obj." + condition.gsub(/=/, '==')
191
- else
192
- condition = true
193
- end
194
-
195
- eval %{
196
- for obj in @conn[options[:class]].values
197
- objects << obj if #{condition}
198
- end
199
- }
200
-
201
- if order = options[:order]
202
- desc = (order =~ /DESC/)
203
- order = order.gsub(/DESC/, '').gsub(/ASC/, '')
204
- eval "objects.sort { |x, y| x.#{order} <=> y.#{order} }"
205
- objects.reverse! if desc
206
- end
207
-
208
- return objects
209
- end
210
-
211
- # :section: Transaction methods.
212
-
213
- # Start a new transaction.
214
-
215
- def start
216
- end
217
-
218
- # Commit a transaction.
219
-
220
- def commit
221
- end
222
-
223
- # Rollback a transaction.
224
-
225
- def rollback
226
- end
227
-
228
- private
229
-
230
- # :section: Lifecycle method compilers.
231
-
232
- # Compile the og_update method for the class.
233
-
234
- def eval_og_insert(klass)
235
- pk = klass.primary_key.first
236
-
237
- klass.class_eval %{
238
- def og_insert(store)
239
- #{::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
240
- @#{pk} = store.conn[#{klass}].size + 1
241
- store.conn[#{klass}][@#{pk}] = self
242
- #{::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
243
- end
244
- }
245
- end
246
-
247
- # Compile the og_update method for the class.
248
-
249
- def eval_og_update(klass)
250
- pk = klass.primary_key.first
251
-
252
- klass.class_eval %{
253
- def og_update(store, options)
254
- #{::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
255
- store.conn[#{klass}][@#{pk}] = self
256
- #{::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
257
- end
258
- }
259
- end
260
-
261
- # Not really useful in this store, kept for compatibility,
262
- # just to call the aspects.
263
-
264
- def eval_og_read(klass)
265
- klass.class_eval %{
266
- def og_read
267
- #{::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
268
- #{::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
269
- end
270
- }
271
- end
272
-
273
- def eval_og_delete(klass)
274
- klass.module_eval %{
275
- def og_delete(store, pk, cascade = true)
276
- #{::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
277
- pk ||= @#{klass.primary_key.first}
278
- transaction do |tx|
279
- tx.conn[#{klass}].delete(pk)
280
- if cascade and #{klass}.__meta[:descendants]
281
- #{klass}.__meta[:descendants].each do |dclass, foreign_key|
282
- eval "tx.conn[dclass].delete_if { |dobj| dobj.\#{foreign_key} == \#{pk} }"
283
- end
284
- end
285
- end
286
- #{::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
287
- end
288
- }
289
- end
290
-
291
- end
292
-
293
- end
294
-
295
- # * George Moschovitis <gm@navel.gr>