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
@@ -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>