og 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
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
data/lib/og/database.rb DELETED
@@ -1,290 +0,0 @@
1
- # * George Moschovitis <gm@navel.gr>
2
- # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: database.rb 19 2005-04-15 09:24:19Z gmosx $
4
-
5
- require 'glue/logger'
6
- require 'glue/attribute'
7
- require 'glue/property'
8
- require 'glue/array'
9
- require 'glue/hash'
10
- require 'glue/time'
11
- require 'glue/pool'
12
- require 'glue/aspects'
13
-
14
- require 'og/enchant'
15
- require 'og/meta'
16
-
17
- module Og
18
-
19
- # Encapsulates an Og Database.
20
-
21
- class Database
22
- include Enchant
23
-
24
- # Managed class metadata
25
-
26
- class ManagedClassMeta
27
- # The managed class.
28
- attr_accessor :klass
29
-
30
- # A mapping of the database fields to the object properties.
31
- attr_accessor :field_index
32
-
33
- def initialize(klass = nil)
34
- @klass = klass
35
- @field_index = {}
36
- end
37
- end
38
-
39
- # hash of configuration options.
40
-
41
- attr_accessor :config
42
-
43
- # The adapter that comunicates with the backend
44
- # datastore.
45
-
46
- attr_accessor :adapter
47
-
48
- # Pool of connections.
49
-
50
- attr_accessor :connection_pool
51
-
52
- # Managed classes.
53
-
54
- attr_accessor :managed_classes
55
-
56
- # Initialize the database interface.
57
-
58
- def initialize(config)
59
- @config = config
60
-
61
- # populate with default options if needed.
62
- @config[:connection_count] ||= 5
63
-
64
- Logger.info "Connecting to database '#{@config[:database]}' using the '#{@config[:adapter]}' adapter."
65
-
66
- @adapter = Adapter.for_name(@config[:adapter])
67
-
68
- self.class.drop!(config) if config[:drop]
69
-
70
- @connection_pool = Pool.new
71
- @managed_classes = SafeHash.new
72
-
73
- @config[:connection_count].times do
74
- @connection_pool << @adapter.new_connection(self)
75
- end
76
-
77
- auto_manage_classes if Og.auto_manage_classes
78
-
79
- # use the newly created database.
80
- Og.use(self)
81
- end
82
-
83
- # Shutdown the database interface.
84
-
85
- def shutdown
86
- for con in @connection_pool
87
- con.close()
88
- end
89
- end
90
- alias_method :close, :shutdown
91
-
92
- # Get a connection from the pool to access the database.
93
- # Stores the connection in a thread-local variable.
94
-
95
- def get_connection
96
- thread = Thread.current
97
-
98
- unless conn = thread[:og_conn]
99
- conn = @connection_pool.pop()
100
- thread[:og_conn] = conn
101
- end
102
-
103
- return conn
104
- end
105
- alias_method :connection, :get_connection
106
-
107
- # Restore an unused connection to the pool.
108
-
109
- def put_connection
110
- thread = Thread.current
111
-
112
- if conn = thread[:og_conn]
113
- thread[:og_conn] = nil
114
- return @connection_pool.push(conn)
115
- end
116
- end
117
-
118
- # Utility method, automatically restores a connection to the pool.
119
-
120
- def connect(&block)
121
- result = nil
122
-
123
- begin
124
- conn = get_connection
125
- result = yield(conn)
126
- ensure
127
- put_connection
128
- end
129
-
130
- return result
131
- end
132
- alias_method :open, :connect
133
-
134
- # Register a standard Ruby class as managed.
135
-
136
- def manage(klass)
137
- return if managed?(klass) or klass.ancestors.include?(Unmanageable)
138
-
139
- @managed_classes[klass] = ManagedClassMeta.new(klass)
140
-
141
- # Add standard og methods to the class.
142
- convert(klass)
143
-
144
- # Add helper methods to the class.
145
- enchant(klass) if Og.enchant_managed_classes
146
- end
147
-
148
- # Helper method to set multiple managed classes.
149
-
150
- def manage_classes(*klasses)
151
- for klass in klasses
152
- manage(klass)
153
- end
154
- end
155
-
156
- # Use Ruby's advanced reflection features to find
157
- # and manage all manageable classes. Manageable are all
158
- # classes that include Properties or Metadata.
159
- #--
160
- # gmosx, FIXME: this automanage code is not elegant and slow
161
- # should probably recode this, along with glue/property.rb
162
- # FIXME: can this be optimized?
163
- # TODO: find a better name.
164
- #++
165
-
166
- def auto_manage_classes
167
- classes_to_manage = []
168
-
169
- ObjectSpace.each_object(Class) do |c|
170
- if c.respond_to?(:__props) and c.__props
171
- classes_to_manage << c
172
- end
173
- end
174
-
175
- Logger.debug "Og auto manages the following classes:"
176
- Logger.debug "#{classes_to_manage.inspect}"
177
-
178
- manage_classes(*classes_to_manage)
179
- end
180
- alias_method :auto_manage, :auto_manage_classes
181
-
182
- # Stop managing a Ruby class
183
-
184
- def unmanage(klass)
185
- @managed_classes.delete(klass)
186
- end
187
-
188
- # Is this class managed?
189
-
190
- def managed?(klass)
191
- return @managed_classes.include?(klass)
192
- end
193
-
194
- # Add standard og functionality to the class
195
-
196
- def convert(klass)
197
- # gmosx: this check is needed to allow the developer to customize
198
- # the sql generated for oid
199
-
200
- @adapter.eval_og_oid(klass) unless klass.instance_methods.include?(:oid)
201
-
202
- klass.class_eval %{
203
- DBTABLE = "#{Adapter.table(klass)}"
204
- DBSEQ = "#{Adapter.table(klass)}_oid_seq"
205
-
206
- cattr_accessor :og_db
207
-
208
- def to_i
209
- @oid
210
- end
211
- }
212
-
213
- # Set the adapter.
214
-
215
- klass.og_db = self
216
-
217
- # Create the schema for this class if not available.
218
-
219
- @adapter.create_table(klass, self) if Og.create_schema
220
- @adapter.eval_lifecycle_methods(klass, self)
221
- end
222
-
223
- # Automatically wrap connection methods.
224
-
225
- def self.wrap_method(method, args)
226
- args = args.split(/,/)
227
- class_eval %{
228
- def #{method}(#{args.join(", ")})
229
- thread = Thread.current
230
-
231
- unless conn = thread[:og_conn]
232
- conn = @connection_pool.pop()
233
- thread[:og_conn] = conn
234
- end
235
-
236
- return conn.#{method}(#{args.collect {|a| a.split(/=/)[0]}.join(", ")})
237
- end
238
- }
239
- end
240
-
241
- wrap_method :db, ''
242
- wrap_method :create_table, 'klass'
243
- wrap_method :drop_table, 'klass'
244
- wrap_method :save, 'obj'; alias_method :<<, :save; alias_method :put, :save
245
- wrap_method :insert, 'obj'
246
- wrap_method :update, 'obj'
247
- wrap_method :update_properties, 'update_sql, obj_or_oid, klass = nil'
248
- wrap_method :pupdate, 'update_sql, obj_or_oid, klass = nil'
249
- wrap_method :load, 'oid, klass'; alias_method :get, :load
250
- wrap_method :load_by_oid, 'oid, klass'
251
- wrap_method :load_by_name, 'name, klass'
252
- wrap_method :load_all, 'klass, extrasql = nil'
253
- wrap_method :select, 'sql, klass'
254
- wrap_method :select_one, 'sql, klass'
255
- wrap_method :count, 'sql, klass = nil'
256
- wrap_method :delete, 'obj_or_oid, klass = nil'
257
- wrap_method :prepare, 'sql'
258
- wrap_method :query, 'sql'
259
- wrap_method :exec, 'sql'
260
- wrap_method :get_row, 'res'
261
- wrap_method :transaction, '&block'
262
-
263
- class << self
264
-
265
- # Create a new database.
266
-
267
- def create_db!(config)
268
- adapter = Adapter.for_name(config[:adapter])
269
- adapter.create_db(config[:database], config[:user], config[:password])
270
- end
271
- alias_method :create!, :create_db!
272
-
273
- # Drop an existing database.
274
-
275
- def drop_db!(config)
276
- adapter = Adapter.for_name(config[:adapter])
277
- adapter.drop_db(config[:database], config[:user], config[:password])
278
- end
279
- alias_method :drop!, :drop_db!
280
-
281
- end
282
- end
283
-
284
- # Helper method
285
-
286
- def self.connect(*args)
287
- db = Database.new(*args)
288
- end
289
-
290
- end
data/lib/og/enchant.rb DELETED
@@ -1,149 +0,0 @@
1
- # * George Moschovitis <gm@navel.gr>
2
- # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: enchant.rb 1 2005-04-11 11:04:30Z gmosx $
4
-
5
- module Og
6
-
7
- module Enchant
8
-
9
- # Enchant a managed class. Add useful DB related methods
10
- # to the class and its instances.
11
-
12
- def enchant(klass)
13
-
14
- # Generate standard methods.
15
-
16
- klass.module_eval %{
17
- def self.create(*params, &block)
18
- obj = #{klass}.new(*params, &block)
19
- obj.save!
20
- end
21
-
22
- def self.save(obj)
23
- @@og_db << obj
24
- end
25
-
26
- def self.load(oid_or_name)
27
- @@og_db.load(oid_or_name, #{klass})
28
- end
29
-
30
- def self.get(oid_or_name)
31
- @@og_db.load(oid_or_name, #{klass})
32
- end
33
-
34
- def self.[](oid_or_name)
35
- @@og_db.load(oid_or_name, #{klass})
36
- end
37
-
38
- def self.load_all(extra_sql = nil)
39
- @@og_db.load_all(#{klass}, extra_sql)
40
- end
41
-
42
- def self.all(extra_sql = nil)
43
- @@og_db.load_all(#{klass}, extra_sql)
44
- end
45
-
46
- def self.update_all(set_sql, extra_sql = nil)
47
- @@og_db.exec("UPDATE #{klass::DBTABLE} SET \#\{set_sql\} \#\{extra_sql\}")
48
- end
49
-
50
- def self.update(set_sql, extra_sql = nil)
51
- @@og_db.exec("UPDATE #{klass::DBTABLE} SET \#\{set_sql\} \#\{extra_sql\}")
52
- end
53
-
54
- def self.count(sql = "SELECT COUNT(*) FROM #{klass::DBTABLE}")
55
- @@og_db.count(sql, #{klass})
56
- end
57
-
58
- def self.select(sql)
59
- @@og_db.select(sql, #{klass})
60
- end
61
-
62
- def self.find(sql)
63
- @@og_db.select(sql, #{klass})
64
- end
65
-
66
- def self.select_one(sql)
67
- @@og_db.select_one(sql, #{klass})
68
- end
69
-
70
- def self.find_one(sql)
71
- @@og_db.select_one(sql, #{klass})
72
- end
73
-
74
- def self.one(sql)
75
- @@og_db.select_one(sql, #{klass})
76
- end
77
-
78
- def self.delete(obj_or_oid)
79
- @@og_db.delete(obj_or_oid, #{klass})
80
- end
81
-
82
- def self.transaction(&block)
83
- @@og_db.transaction(&block)
84
- end
85
-
86
- def self.properties_and_relations
87
- @@__meta[:props_and_relations]
88
- end
89
-
90
- def self.each(&block)
91
- all.each(&block)
92
- end
93
- include Enumerable
94
-
95
- def save
96
- @@og_db << self
97
- return self
98
- end
99
- alias_method :save!, :save
100
-
101
- def reload
102
- raise 'Cannot reload unmanaged object' unless @oid
103
- res = @@og_db.query "SELECT * FROM #{klass::DBTABLE} WHERE oid=\#\{@oid\}"
104
- og_read(@@og_db.get_row(res))
105
- end
106
- alias_method :reload!, :reload
107
-
108
- def update_properties(updatesql)
109
- @@og_db.pupdate(updatesql, self.oid, #{klass})
110
- end
111
- alias_method :pupdate, :update_properties
112
- alias_method :update_property, :update_properties
113
-
114
- def set_property(prop, value)
115
- @@og_db.pupdate("\#\{prop\}=\#\{value\}", self.oid, #{klass})
116
- end
117
-
118
- def delete!
119
- @@og_db.delete(self, #{klass})
120
- end
121
- }
122
-
123
- # Generate finder methods.
124
-
125
- code = ''
126
-
127
- for p in klass.__props
128
- code << %{
129
- def self.find_by_#{p.name}(val, operator = '=', extra_sql = nil)
130
- }
131
-
132
- val = klass.og_db.adapter.typecast[p.klass].gsub(/\:s\:/, 'val')
133
-
134
- if p.meta[:unique]
135
- code << %{@@og_db.select_one("#{p.name}\#\{operator\}#{val} \#\{extra_sql\}", #{klass})}
136
- else
137
- code << %{@@og_db.select("#{p.name}\#\{operator\}#{val} \#\{extra_sql\}", #{klass})}
138
- end
139
- code << %{
140
- end;
141
- }
142
- end
143
-
144
- klass.module_eval(code)
145
- end
146
-
147
- end
148
-
149
- end
data/lib/og/meta.rb DELETED
@@ -1,407 +0,0 @@
1
- # * George Moschovitis <gm@navel.gr>
2
- # (c) 2005 Navel, all rights reserved.
3
- # $Id: meta.rb 1 2005-04-11 11:04:30Z gmosx $
4
- #--
5
- # TODO:
6
- # - precreate the meta sql statements as much as possible to
7
- # avoid string interpolations.
8
- #++
9
-
10
- require 'glue/inflector'
11
- require 'og/adapters/base'
12
- require 'og/typemacros'
13
-
14
- module Og
15
-
16
- class Relation < Property
17
- alias foreign_class klass
18
- end
19
-
20
- class Has < Relation; end
21
- class HasMany < Has; end
22
- class HasOne < Has; end
23
- class ManyToMany < Has; end
24
- class BelongsTo < Relation; end
25
- class RefersTo < Relation; end
26
-
27
- # Some useful meta-language utilities.
28
-
29
- module MetaUtils # :nodoc: all
30
-
31
- # Convert the klass to a string representation
32
- # The leading module if available is removed.
33
- #--
34
- # gmosx, FIXME: unify with the ogutils.encode method?
35
- #++
36
-
37
- def self.expand(klass)
38
- return klass.name.gsub(/^.*::/, '').gsub(/::/, '_').downcase
39
- end
40
-
41
- # Infer the target klass for a relation. When defining
42
- # relations, tha target class is typically given. Some times
43
- # in order to avoid forward declarations a symbol is given instead
44
- # of a class. Other times on class is given at all, and
45
- # the inflection mechanism is used to infer the class name.
46
- #--
47
- # This is not used yet.
48
- #++
49
-
50
- def self.resolve_class(name, klass)
51
- klass ||= Inflector.camelize(name)
52
-
53
- return klass if klass.is_a?(Class)
54
-
55
- unless klass.is_a?(String) or klass.is_a?(Symbol)
56
- raise 'Invalid class definition'
57
- end
58
-
59
- unless Object.const_get(klass.intern)
60
- # Forward declaration.
61
- Object.class_eval("class #{klass}; end")
62
- end
63
-
64
- return Object.const_get(klass)
65
- end
66
-
67
- end
68
-
69
- # Implements a meta-language for manipulating og-managed objects
70
- # and defining their relationships. The original idea comes
71
- # from the excellent ActiveRecord library.
72
- #
73
- # Many more useful relations will be available soon.
74
-
75
- module MetaLanguage
76
-
77
- # Defines an SQL index. Useful for defining indiced
78
- # over multiple columns.
79
-
80
- def sql_index(index, options = {})
81
- meta :sql_index, [index, options]
82
- end
83
-
84
- # Implements a 'belongs_to' relation.
85
- # Automatically enchants the calling class with helper methods.
86
- #
87
- # Example:
88
- #
89
- # class MyObject
90
- # belongs_to AnotherObject, :prop => :parent
91
- # end
92
- #
93
- # creates the code:
94
- #
95
- # prop_accessor Fixnum, :parent_oid
96
- # def parent; ... end
97
- # def parent=(obj_or_oid); ... end
98
-
99
- def belongs_to(name, klass, options = {})
100
- prop_eval = "prop_accessor Fixnum, :#{name}_oid"
101
- prop_eval << ", :sql => '#{options[:sql]}'" if options[:sql]
102
- prop_eval << ", :extra_sql => '#{options[:extra_sql]}'" if options[:extra_sql]
103
-
104
- meta :props_and_relations, BelongsTo.new(name, klass, :property => "#{name}_oid".intern)
105
-
106
- module_eval %{
107
- #{prop_eval}
108
-
109
- def #{name}
110
- Og.db.load_by_oid(@#{name}_oid, #{klass})
111
- end
112
-
113
- def #{name}=(obj_or_oid)
114
- @#{name}_oid = obj_or_oid.to_i
115
- end
116
- }
117
- end
118
-
119
- # Implements a 'has_one' relation.
120
- # Automatically enchants the calling class with helper methods.
121
- #
122
- # Example:
123
- #
124
- # class MyObject
125
- # has_one :child, TheClass
126
- # has_one :article
127
- # end
128
- #
129
- # creates the code:
130
- #
131
- # ...
132
-
133
- def has_one(name, klass = nil, options = {})
134
-
135
- # linkback is the property of the child object that 'links back'
136
- # to this object.
137
-
138
- linkback = (options[:linkback].to_s || "#{MetaUtils.expand(self)}_oid").to_s
139
-
140
- meta :descendants, klass, linkback
141
- meta :props_and_relations, HasOne.new(name, klass, options)
142
-
143
- module_eval %{
144
- def #{name}(extrasql = nil)
145
- Og.db.select_one("SELECT * FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}", #{klass})
146
- end
147
-
148
- def delete_#{name}(extrasql = nil)
149
- Og.db.exec("DELETE FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}")
150
- end
151
- }
152
- end
153
-
154
- # Implements a 'has_many' relation.
155
- # Automatically enchants the calling class with helper methods.
156
- #
157
- # Options:
158
- #
159
- # [+linkback+]
160
- #
161
- # [+order+]
162
- #
163
- # [+extrasql+]
164
- #
165
- # Example:
166
- #
167
- # class MyObject
168
- # has_many :articles, Article
169
- # end
170
- #
171
- # creates the code:
172
- #
173
- # obj.articles
174
- # obj.add_article(article)
175
- # obj.add_article do |a|
176
- # a.title = 'Title'
177
- # a.body = 'Body'
178
- # end
179
-
180
- def has_many(name, klass, options = {})
181
- name_s = Inflector.singularize(name.to_s)
182
-
183
- # linkback is the property of the child object that 'links back'
184
- # to this object.
185
-
186
- linkback = (options[:linkback] || "#{MetaUtils.expand(self)}_oid").to_s
187
-
188
- if order = options[:order]
189
- order = "ORDER BY #{order}"
190
- end
191
- default_extrasql = "#{order}#{options[:sql]}"
192
-
193
- # keep belongs to metadata, useful for
194
- # reflection/scaffolding.
195
-
196
- meta :descendants, klass, linkback
197
- meta :props_and_relations, HasMany.new(name, klass, options)
198
-
199
- code = %{
200
- def #{name}(extrasql = nil)
201
- Og.db.select("SELECT * FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid #{default_extrasql} \#\{extrasql\}", #{klass})
202
- end
203
-
204
- def #{name}_count(extrasql = nil)
205
- Og.db.count("SELECT COUNT(*) FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}")
206
- end
207
-
208
- def add_#{name_s}(obj = nil)
209
- yield(obj = #{klass}.new) unless obj
210
- }
211
- if Og.check_implicit_graph_changes
212
- code << %{
213
- if obj.#{linkback}
214
- Logger.warn "Implicit graph change for object #{obj.inspect}"
215
- end
216
- }
217
- end
218
- code << %{
219
- obj.#{linkback} = @oid
220
- obj.save!
221
- end
222
-
223
- def delete_all_#{name}(extrasql = nil)
224
- Og.db.exec("DELETE FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}")
225
- end
226
- }
227
-
228
- module_eval(code)
229
- end
230
-
231
- # Implements a 'many_to_many' relation.
232
- # Two objects are associated using an intermediate join table.
233
- # Automatically enchants the calling class with helper methods.
234
- #
235
- # Options:
236
- #
237
- # Example:
238
- #
239
- # class Article
240
- # many_to_many :categories, Category, :linkback => articles
241
- # end
242
- #
243
- # article.categories
244
- # article.del_category
245
- # article.add_category
246
- # article.add_category { |c| ... }
247
- # article.clear_categories
248
- #
249
- # category.articles
250
- # ...
251
- #--
252
- # FIXME: make more compatible with other enchant methods.
253
- #++
254
-
255
- def many_to_many(name, klass, options = {})
256
- list_o = name.to_s
257
- prop_o = Inflector.singularize(list_o)
258
- list_m = (options[:linkback] || Inflector.plural_name(self)).to_s
259
- prop_m = Inflector.singularize(list_m)
260
-
261
- # Exit if the class is allready indirectly 'enchanted' from the
262
- # other class of the many_to_many relation.
263
-
264
- return if self.respond_to?(prop_m)
265
-
266
- # Add some metadata to the class to allow for automatic join table
267
- # calculation.
268
-
269
- meta :sql_join, name, klass, options
270
-
271
- # FIXME: should add metadata for cascading delete.
272
-
273
- meta :props_and_relations, ManyToMany.new(prop_o, klass)
274
- klass.meta :props_and_relations, ManyToMany.new(prop_m, self)
275
-
276
- # Enchant this class
277
-
278
- module_eval %{
279
- def #{list_o}(extrasql = nil)
280
- Og.db.select("SELECT d.* FROM #{Og::Adapter.table(klass)} d, #{Og::Adapter.join_table(self, klass, name)} j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass})
281
- end
282
-
283
- def #{list_o}_count(extrasql = nil)
284
- Og.db.select("SELECT COUNT(*) FROM #{Og::Adapter.table(klass)} d, #{Og::Adapter.join_table(self, klass, name)} j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass})
285
- end
286
-
287
- def add_#{prop_o}(obj = nil)
288
- yield(obj = #{klass}.new) unless obj
289
- obj.save! unless obj.oid
290
- Og.db.exec("INSERT INTO #{Og::Adapter.join_table(self, klass, name)} (key1, key2) VALUES (\#\@oid, \#\{obj.oid\})")
291
- end
292
-
293
- def delete_#{prop_o}(obj_or_oid)
294
- Og.db.exec("DELETE FROM #{Og::Adapter.join_table(self, klass, name)} WHERE key2=\#\{obj_or_oid.to_i\}")
295
- end
296
-
297
- def clear_#{list_o}
298
- Og.db.exec("DELETE FROM #{Og::Adapter.join_table(self, klass, name)} WHERE key1=\#\@oid")
299
- end
300
- }
301
-
302
- # indirectly enchant the other class of the relation.
303
-
304
- klass.module_eval %{
305
- def #{list_m}(extrasql = nil)
306
- Og.db.select("SELECT s.* FROM #{Og::Adapter.table(self)} s, #{Og::Adapter.join_table(self, klass, name)} j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self})
307
- end
308
-
309
- def #{list_m}_count(extrasql = nil)
310
- Og.db.select("SELECT COUNT(*) FROM #{Og::Adapter.table(self)} s, #{Og::Adapter.join_table(self, klass, name)} j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self})
311
- end
312
-
313
- def add_#{prop_m}(obj = nil)
314
- yield(obj = #{self}.new) unless obj
315
- obj.save! unless obj.oid
316
- Og.db.exec("INSERT INTO #{Og::Adapter.join_table(self, klass, name)} (key1, key2) VALUES (\#\{obj.oid\}, \#\@oid)")
317
- end
318
-
319
- def delete_#{prop_m}(obj_or_oid)
320
- Og.db.exec("DELETE FROM #{Og::Adapter.join_table(self, klass, name)} WHERE key1=\#\{obj_or_oid.to_i\}")
321
- end
322
-
323
- def clear_#{list_m}
324
- Og.db.exec("DELETE FROM #{Og::Adapter.join_table(self, klass, name)} WHERE key2=\#\@oid")
325
- end
326
- }
327
- end
328
- alias :has_and_belongs_to_many :many_to_many
329
-
330
- # Implements a 'refers_to' relation.
331
- # This is a one-way version of the 'has_one'/'belongs_to'
332
- # relations. The target object cannot link back to the source
333
- # object.
334
- # This is in fact EXACTLY the same as belongs_to with a
335
- # different name (!!!!)
336
- #
337
- # Automatically enchants the calling class with helper methods.
338
- #
339
- #
340
- # Example:
341
- #
342
- # class MyObject
343
- # refers_to article, Article
344
- # end
345
- #
346
- # creates the code:
347
- #
348
- # prop_accessor Fixnum, :article_oid
349
- # def article; ... end
350
- # def article=(obj_or_oid); ... end
351
-
352
- def refers_to(name, klass, options = {})
353
- prop_eval = "prop_accessor Fixnum, :#{name}_oid"
354
- prop_eval << ", :sql => '#{options[:sql]}'" if options[:sql]
355
- prop_eval << ", :extra_sql => '#{options[:extra_sql]}'" if options[:extra_sql]
356
-
357
- meta :props_and_relations, RefersTo.new(name, klass, :property => "#{name}_oid".intern)
358
- klass.meta :descendants, self, "#{name}_oid".intern
359
-
360
- module_eval %{
361
- #{prop_eval}
362
-
363
- def #{name}
364
- Og.db.load_by_oid(@#{name}_oid, #{klass})
365
- end
366
-
367
- def #{name}=(obj_or_oid)
368
- @#{name}_oid = obj_or_oid.to_i
369
- end
370
- }
371
- end
372
-
373
- # Declares that this class can join with another class.
374
- # The join parameters are given so the join-compatible
375
- # methods are generated.
376
-
377
- def joins(klass, options = {})
378
- meta :joins, klass, options
379
- end
380
-
381
- end
382
-
383
- end
384
-
385
- # Include the meta-language extensions into Module. If the flag is
386
- # false the developer is responsible for including the MetaLanguage
387
- # module where needed.
388
- #
389
- # By default this is FALSE, to avoid polution of the Module object.
390
- # However if you include a prop_accessor or a managed Mixin in your
391
- # object MetaLanguage gets automatically extended in the class.
392
-
393
- if Og.include_meta_language
394
- class Module # :nodoc: all
395
- include Og::MetaLanguage
396
-
397
- =begin
398
- A hack to avoid forward references. Does not work
399
- with namespave modules though. Any idea?
400
- -> try TOPLEVEL_BINDING
401
-
402
- def const_missing(sym)
403
- eval "class ::#{sym}; end; return #{sym}"
404
- end
405
- =end
406
- end
407
- end