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
@@ -0,0 +1,119 @@
1
+ require 'og/entity'
2
+
3
+ # Add cloning functionality to entities.
4
+ #--
5
+ # WARNING, gmosx: I haven't tested this extensively,
6
+ # this is a donated patch.
7
+ #++
8
+
9
+ module EntityMixin
10
+
11
+ def og_clone(*args)
12
+ Og::Entity.clone(self,*args)
13
+ end
14
+
15
+ end
16
+
17
+ class Entity
18
+
19
+ class << self
20
+
21
+ # Entity copying support. Eventually this should all
22
+ # be eval'd in at enchanting stage for the minor
23
+ # speed increase.
24
+ # TODO: Convert to enchantments on objects
25
+
26
+ # Accepts source object, destination and ignore.
27
+ # Source and destination are self explanatory; ignore
28
+ # is a list of properties not to copy (i.e.
29
+ # :create_time,:update_time).
30
+ # By default sets the class variables directly on the
31
+ # remote model instance, if you set use_setter_method to
32
+ # true, uses create_time= style copying tactics,
33
+
34
+ def copy_properties(source, destination, ignore = [], use_setter_method = false)
35
+ property_copier(source, destination, ignore, use_setter_method, false)
36
+ end
37
+
38
+ # Copies relations of one record to another. Only copies
39
+ # has_one, refers_to, belongs_to relationships as
40
+ # has_many requires modifying of other objects and
41
+ # cannot be copied (by design). If you think you need to copy
42
+ # these relations, what you need is a joins_many relationship
43
+ # which can be copied.
44
+
45
+ def copy_inferior_relations(source, destination, ignore = [])
46
+ real_ignore = Array.new
47
+
48
+ # Map relation symbols to foreign keys.
49
+
50
+ ignore.each do |symbol|
51
+ source.class.relations.reject{|r| [Og::JoinsMany, Og::ManyToMany, Og::HasMany].include?(r.class)}.each do |relation|
52
+ if relation.name == symbol.to_s
53
+ real_ignore << relation.foreign_key.to_sym
54
+ break
55
+ end
56
+ end
57
+ end
58
+
59
+ # Use instance variable property copier method.
60
+
61
+ property_copier(source, destination, real_ignore, false, true)
62
+ end
63
+
64
+ def copy_equal_relations(source, destination, ignore = [])
65
+ source.class.relations.reject{|r| not [Og::JoinsMany, Og::ManyToMany].include?(r.class)}.each do |relation|
66
+ next if relation.name == nil or ignore.include?(relation.name)
67
+ source.send(relation.name).each do |related|
68
+ destination.send(relation.name).send(:<<, related)
69
+ end
70
+ end
71
+ end
72
+
73
+ # Copies all relations *except* HasMany which is impossible
74
+ # to copy. Use a JoinsMany relation instead if you need a
75
+ # copyable HasMany (which is irrational).
76
+
77
+ def copy_relations(source, destination, ignore = [])
78
+ copy_inferior_relations(source, destination, ignore)
79
+ copy_equal_relations(source, destination, ignore)
80
+ end
81
+
82
+ # Clones an object in every possible way (cannot copy
83
+ # HasMany but can copy all others - BelongsTo, etc).
84
+ # Provide a source object as first arguments, the rest
85
+ # (if any) are passed along to the initialize constructor
86
+ # when calling new to make the copied object.
87
+
88
+ def clone(source,*args)
89
+ destination = source.class.new(*args)
90
+ copy_properties(source, destination, [], false)
91
+ # Must save here to copy join tables.
92
+ destination.save!
93
+ copy_relations(source, destination, [])
94
+ destination.save!
95
+ destination
96
+ end
97
+
98
+ # Does the work of clone_properties and copy_inferior_relations.
99
+ # Syntax is the same with one extra field to tell the
100
+ # routine what it is copying.
101
+
102
+ def property_copier(source,destination,ignore,use_setter_method,relations)
103
+ primary_key_symbol = source.class.primary_key.symbol
104
+ source.class.properties.to_a.each do |symbol, property|
105
+ next if primary_key_symbol == symbol or ignore.include?(symbol) or
106
+ (relations and not property.relation) or (not relations and property.relation)
107
+
108
+ variable = "@#{symbol}"
109
+ if use_setter_method
110
+ destination.send("#{symbol}=".to_sym,source.instance_variable_get(variable))
111
+ else
112
+ destination.instance_variable_set(variable, source.instance_variable_get(variable))
113
+ end
114
+ end
115
+ end
116
+
117
+ end
118
+
119
+ end
@@ -12,5 +12,3 @@ class StoreException < Exception
12
12
  end
13
13
 
14
14
  end
15
-
16
- # * George Moschovitis <gm@navel.gr>
@@ -3,6 +3,7 @@ require 'facet/class/descendents'
3
3
 
4
4
  require 'og/entity'
5
5
  require 'og/store'
6
+ require 'og/adapter'
6
7
 
7
8
  module Og
8
9
 
@@ -58,16 +59,24 @@ class Manager
58
59
 
59
60
  attr_accessor :cache
60
61
 
62
+ # Initialize the manager.
63
+ #
64
+ # === Options
65
+ #
66
+ # :store, :adapter = the adapter/store to use as backend.
67
+
61
68
  def initialize(options)
62
69
  @options = options
63
70
  @entities = {}
64
71
 
65
- @store_class = Store.for_name(options[:store])
66
- @store_class.destroy(options) if options[:destroy]
67
- @store_class.destroy_tables(options) if options[:destroy_tables]
68
- init_store
72
+ @store_class = Adapter.for_name(options[:adapter] || options[:store])
73
+ @store_class.allocate.destroy_db(options) if Og.destroy_schema || options[:destroy]
74
+
75
+ init_store()
69
76
  end
70
77
 
78
+ # Initialize a store.
79
+
71
80
  def initialize_store
72
81
  if @pool
73
82
  close_store
@@ -77,9 +86,11 @@ class Manager
77
86
  @pool = Pool.new
78
87
  (options[:connection_count] || 5).times do
79
88
  @pool << @store_class.new(@options)
89
+ @pool.last.ogmanager = self
80
90
  end
81
91
  else
82
92
  @store = @store_class.new(@options)
93
+ @store.ogmanager = self
83
94
  end
84
95
  end
85
96
  alias :init_store :initialize_store
@@ -87,7 +98,7 @@ class Manager
87
98
  # used when changing thread_safe mode
88
99
 
89
100
  def close_store
90
- if @pool.empty?
101
+ unless @pool
91
102
  @store.close
92
103
  else
93
104
  @pool.each { |s| s.close }
@@ -103,10 +114,22 @@ class Manager
103
114
  thread = Thread.current
104
115
 
105
116
  unless st = thread[:og_store] and st.is_a?(@store_class)
106
- if 0 == @pool.size()
107
- initialize_store()
117
+ if 0 == @pool.size
118
+ initialize_store
108
119
  end
109
- st = @pool.pop()
120
+ st = @pool.pop
121
+ thread[:og_store] = st
122
+ end
123
+
124
+ if st.ogmanager != self
125
+ # This normally shound't happen, there is a leftover store.
126
+ # Just return it to the original store and go on.
127
+ st.ogmanager.put_store
128
+
129
+ if 0 == @pool.size
130
+ initialize_store
131
+ end
132
+ st = @pool.pop
110
133
  thread[:og_store] = st
111
134
  end
112
135
 
@@ -125,6 +148,12 @@ class Manager
125
148
  thread = Thread.current
126
149
 
127
150
  if conn = thread[:og_store]
151
+
152
+ # store released to the wrong manager?
153
+ if conn.ogmanager != self
154
+ return conn.ogmanager.put_store
155
+ end
156
+
128
157
  thread[:og_store] = nil
129
158
  return @pool.push(conn)
130
159
  end
@@ -140,21 +169,39 @@ class Manager
140
169
  # Manage a class. Converts the class to an Entity.
141
170
 
142
171
  def manage(klass)
143
- return if managed?(klass) or Og.unmanageable_classes.include?(klass)
172
+ return if managed?(klass) or (!manageable?(klass))
144
173
 
145
174
  info = EntityInfo.new(klass)
146
175
 
176
+ # Check if the class has a :text key.
177
+
178
+ for a in klass.serializable_attributes
179
+ anno = klass.ann(a)
180
+ if anno[:key]
181
+ klass.ann.self[:text_key] = a
182
+ break
183
+ end
184
+ end
185
+
147
186
  # DON'T DO THIS!!!
187
+ #--
188
+ # gmosx: this is used though, dont remove without recoding
189
+ # some stuff.
190
+ #++
148
191
 
149
192
  klass.module_eval %{
150
193
  def ==(other)
151
- other.instance_of?(#{klass}) ? @#{klass.primary_key.symbol} == other.#{klass.primary_key.symbol} : false
194
+ other.instance_of?(#{klass}) ? @#{klass.primary_key} == other.#{klass.primary_key} : false
152
195
  end
153
196
  }
154
197
 
155
198
  klass.class.send(:attr_accessor, :ogmanager)
156
199
  klass.instance_variable_set '@ogmanager', self
157
200
 
201
+ # FIXME: move somewhere else.
202
+
203
+ klass.define_force_methods
204
+
158
205
  Relation.enchant(klass)
159
206
 
160
207
  # ensure that the superclass is managed before the
@@ -162,7 +209,8 @@ class Manager
162
209
 
163
210
  manage(klass.superclass) if manageable?(klass.superclass)
164
211
 
165
- # FIXME: uggly!
212
+ # Perform store related enchanting.
213
+
166
214
  store.enchant(klass, self); put_store
167
215
 
168
216
  # Call special class enchanting code.
@@ -175,12 +223,15 @@ class Manager
175
223
  # Is this class manageable by Og?
176
224
  #
177
225
  # Unmanageable classes include classes:
178
- # * without properties
226
+ # * without serializable attributes
179
227
  # * explicitly marked as Unmanageable (is Og::Unamanageable)
180
228
  # * are polymorphic_parents (ie thay are used to spawn polymorphic relations)
181
229
 
182
230
  def manageable?(klass)
183
- klass.respond_to?(:properties) and (!klass.properties.empty?) and (!Og.unmanageable_classes.include?(klass)) and (!klass.polymorphic_parent?)
231
+ (klass.respond_to?(:serializable_attributes)) and
232
+ (!klass.serializable_attributes.empty?) and
233
+ (!Og.unmanageable_classes.include?(klass)) and
234
+ (!klass.polymorphic_parent?)
184
235
  end
185
236
 
186
237
  # Is the class managed by Og?
@@ -203,7 +254,6 @@ class Manager
203
254
  def manageable_classes
204
255
  classes = []
205
256
 
206
- # for c in Property.classes
207
257
  ObjectSpace.each_object(Class) do |c|
208
258
  if manageable?(c)
209
259
  classes << c
@@ -216,17 +266,27 @@ class Manager
216
266
  # Manage a collection of classes.
217
267
 
218
268
  def manage_classes(*classes)
219
- classes = manageable_classes.flatten # if classes.empty? FIXME!
220
- classes = classes.reject { |c| self.class.managed?(c) }
269
+ classes.flatten!
270
+ classes.compact!
271
+
272
+ mc = self.class.managed_classes
273
+
274
+ classes = manageable_classes.flatten if classes.empty?
275
+ classes = classes.reject { |c| mc.member?(c) || !manageable?(c) }
276
+
277
+ sc = @store_class.allocate
221
278
 
279
+ classes.each { |c| sc.force_primary_key(c) }
222
280
  classes.each { |c| Relation.resolve_targets(c) }
223
281
  classes.each { |c| Relation.resolve_polymorphic_markers(c) }
224
- classes.each { |c| Relation.resolve_polymorphic_relations(c) }
225
-
282
+
226
283
  # The polymorpic resolution step creates more manageable classes.
227
-
228
- classes = manageable_classes.flatten # if classes.empty? FIXME!
229
- classes = classes.reject { |c| self.class.managed?(c) }
284
+
285
+ classes += classes.map {|c| Relation.resolve_polymorphic_relations(c) }
286
+
287
+ classes.flatten!
288
+
289
+ classes = classes.reject { |c| !c or self.class.managed?(c) }
230
290
 
231
291
  Logger.debug "Og manageable classes: #{classes.inspect}" if $DBG
232
292
 
@@ -234,8 +294,10 @@ class Manager
234
294
  classes.each { |c| Relation.resolve_names(c) }
235
295
  classes.each { |c| manage(c) }
236
296
  end
237
- alias_method :manage_class, :manage_classes
297
+ alias manage_class manage_classes
238
298
 
299
+ # Do not manage the given classes.
300
+
239
301
  def unmanage_classes(*classes)
240
302
  classes = manageable_classes.flatten if classes.empty?
241
303
 
@@ -243,7 +305,7 @@ class Manager
243
305
  @entities.delete_if { |k, v| v.klass == c }
244
306
  end
245
307
  end
246
- alias_method :unmanage_class, :unmanage_classes
308
+ alias unmanage_class unmanage_classes
247
309
 
248
310
  # Allows functionality that requires a store is finalized
249
311
  # to be implemented. A vastly superior method of constructing
@@ -258,20 +320,6 @@ class Manager
258
320
  store.post_setup if store.respond_to?(:post_setup)
259
321
  end
260
322
 
261
- # Dump a nice name for this store.
262
- =begin
263
- def to_s
264
- if store = get_store
265
- store.to_s
266
- else
267
- 'Uninitialized'
268
- end
269
- end
270
- =end
271
323
  end
272
324
 
273
325
  end
274
-
275
- # * George Moschovitis <gm@navel.gr>
276
- # * Guillaume Pierronnet <guillaume.pierronnet@laposte.net>
277
- # * Rob Pitt
@@ -2,7 +2,8 @@ require 'facet/kernel/constant'
2
2
  require 'facet/string/capitalized'
3
3
  require 'facet/ormsupport'
4
4
  require 'facet/inheritor'
5
- require 'facet/annotation'
5
+
6
+ require 'facets/core/module/class_extension'
6
7
 
7
8
  module Og
8
9
 
@@ -53,9 +54,6 @@ class Relation
53
54
  # Inflect target_class if not provided.
54
55
 
55
56
  @options[:target_class] ||= @options[target_name].to_s.singular.camelize.intern
56
-
57
- # FIXME: this is a hack!
58
- # setup() rescue nil
59
57
  end
60
58
 
61
59
  # Get an option.
@@ -79,6 +77,8 @@ class Relation
79
77
  # Is this a polymorphic relation ?
80
78
 
81
79
  def polymorphic?
80
+ # hack fix!
81
+ return false unless target_class.is_a?(Class)
82
82
  target_class.ann.self[:polymorphic]
83
83
  end
84
84
 
@@ -116,23 +116,31 @@ class Relation
116
116
  # (at the time of the creation of the relation) classes are
117
117
  # stored as symbols. These symbols are resolved by this
118
118
  # method.
119
+ #
120
+ # First attempts to find a class in the form:
121
+ #
122
+ # owner_class::class
123
+ # (ie, Article::Category)
124
+ #
125
+ # then a class of the form:
126
+ #
127
+ # class
128
+ # (ie, ::Category)
119
129
  #--
120
- # FIXME: do something more elegant here.
130
+ # The lookup is handled automatically by the #constant Facets
131
+ # method.
121
132
  #++
122
-
133
+
123
134
  def symbol_to_class(sym, owner_class)
124
- c = owner_class.name.dup
125
- c = "::" + c unless c =~ /::/
126
- c.gsub!(/::[^:]*$/, '::')
127
- c << sym.to_s
135
+ owner_class = owner_class.name
136
+
128
137
  begin
129
- return constant(c)
130
- rescue
131
- unless c == sym
132
- c = sym
133
- retry
134
- end
135
- end
138
+ c = "#{owner_class}::#{sym}"
139
+ const = constant(c)
140
+ owner_class = owner_class.to_s.scan(/^(.*)::(?:[^:])*$/)
141
+ end while const.class != Class && !owner_class.empty?
142
+
143
+ return const.class == Class ? const : nil
136
144
  end
137
145
  alias_method :resolve_symbol, :symbol_to_class
138
146
 
@@ -175,32 +183,45 @@ class Relation
175
183
  # For example:
176
184
  #
177
185
  # class Article
178
- # has_many Comment
186
+ # has_many :comments
179
187
  # ...
180
188
  # end
189
+ #
190
+ # generates:
191
+ #
192
+ # class Article::Comment < Comment
193
+ # end
181
194
 
182
195
  def resolve_polymorphic_relations(klass)
196
+ generated = []
197
+
183
198
  for r in klass.relations
184
199
  if r.polymorphic?
185
-
186
200
  target_dm = r.target_class.to_s.demodulize
187
-
188
- r.owner_class.module_eval %{
189
- class #{r.owner_class}::#{target_dm} < #{r.target_class}
190
- end
191
- }
192
-
193
- # Replace the target class.
194
-
195
- r[:target_class] = eval("#{r.owner_class}::#{target_dm}")
201
+
202
+ # Replace the target class by either creating or getting the
203
+ # polymorphic child if it already exists.
204
+
205
+ r[:target_class] = if r.owner_class.constants.include?(target_dm)
206
+ r.owner_class.const_get(target_dm)
207
+ else
208
+ r.owner_class.const_set(target_dm, Class.new(r.target_class))
209
+ end
210
+
211
+ r.resolve_polymorphic
212
+
213
+ generated << r[:target_class]
196
214
  end
197
-
198
- r.resolve_polymorphic
199
215
  end
216
+
217
+ return generated
200
218
  end
201
219
 
202
220
  # Resolve the names of the relations.
203
-
221
+ #--
222
+ # For the target name it uses the demodulized class name.
223
+ #++
224
+
204
225
  def resolve_names(klass)
205
226
  for r in klass.relations
206
227
  target_name = if r.collection
@@ -243,7 +264,6 @@ class Relation
243
264
  # enchant.
244
265
 
245
266
  for r in klass.relations
246
- # p "=== #{klass} : #{r.class} : #{r.name}"
247
267
  r.enchant() unless r.polymorphic_marker?
248
268
  end
249
269
 
@@ -328,5 +348,3 @@ module RelationDSL
328
348
  end
329
349
 
330
350
  end
331
-
332
- # * George Moschovitis <gm@navel.gr>