og 0.16.0 → 0.17.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 (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