og 0.14.0 → 0.15.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.
data/CHANGELOG CHANGED
@@ -1,5 +1,73 @@
1
+ 04-04-2005 George Moschovitis <gm@navel.gr>
2
+
3
+ * examples/run.rb: small fix.
4
+
5
+ * test/og/mixins/tc_tree.rb: removed.
6
+
7
+ * test/og/mixins/tc_list.rb: removed.
8
+
9
+ 03-04-2005 George Moschovitis <gm@navel.gr>
10
+
11
+ * doc/RELEASES: updated.
12
+
13
+ * lib/og/backend.rb: removed.
14
+
15
+ * test/og/mixins/tc_hierarchical: implemented,
16
+ dont use article.
17
+
18
+ * lib/og/mixins/orderable.rb: fixed 1=1 scope.
19
+
20
+ * lib/og/mixins/hierarchical.rb (Hierarchical): implemented,
21
+ (#add_child): implemented.
22
+ after some fixes, it works!!
23
+ fixed 1=1 scope.
24
+ added :root.
25
+
26
+ 02-04-2005 George Moschovitis <gm@navel.gr>
27
+
28
+ * lib/og/typemacros.rb: small update.
29
+
30
+ * test/og/mixins/tc_hierarchical: introduced.
31
+
32
+ * lib/og/mixins/hierarchical.rb: introduced,
33
+ (Hierarchical): introduced,
34
+ (NestedSets): introduced,
35
+ added enchant methods for nested sets.
36
+
37
+ * lib/og/mixins/tree.rb: deprecated.
38
+
39
+ 01-04-2005 George Moschovitis <gm@navel.gr>
40
+
41
+ * lib/og/adapters/sqlserver.rb: small fixes.
42
+
43
+ * doc/README: updated.
44
+
45
+ * doc/RELEASES: updated.
46
+
47
+ * doc/AUTHORS: updated.
48
+
49
+ 01-04-2005 Anastasios Koutoumanos <drak@navel.gr>
50
+
51
+ * lib/og/adapters/sqlserver.rb: implemented.
52
+
53
+ * test/og/tc_sqlserver.rb: implemented.
54
+
55
+ 31-03-2005 George Moschovitis <gm@navel.gr>
56
+
57
+ * lib/og/mixins/orderable.rb: implemented using dynamic_include.
58
+
59
+ * lib/og/meta.rb: removed inclusion of Og::List.
60
+
61
+ * lib/og/mixins/list.rb: deprecated in favour of the superior implementation.
62
+
63
+ 31-03-2005 Anastasios Koutoumanos <drak@navel.gr>
64
+
65
+ * lib/og/adapters/oracle.rb: removed duplicate eval_og_oid.
66
+
1
67
  28-03-2005 George Moschovitis <gm@navel.gr>
2
68
 
69
+ * --- VERSION 0.14.0 ---
70
+
3
71
  * test/og/mixins/tc_list.rb: implemented tests.
4
72
 
5
73
  * lib/og/connection.rb (#delete): passs obj_or_oid to the callback.
data/README CHANGED
@@ -1,4 +1,4 @@
1
- = Og 0.14.0
1
+ = Og 0.15.0
2
2
 
3
3
  Og (ObjectGraph) is a powerfull object-relational mapping library. Og provides
4
4
  transparent serialization of object graphs to a RDBMS
@@ -27,10 +27,10 @@ The library provides the following features:
27
27
 
28
28
  + Object-Relational mapping.
29
29
  + Absolutely no configuration files.
30
- + Multiple backend adapters (PostgreSQL, MySQL, SQLite).
30
+ + Multiple backend adapters (PostgreSQL, MySQL, SQLite3, Oracle, SqlServer).
31
31
  + ActiveRecord-style meta language and db aware methods.
32
32
  + Automatially generates join-tables for many_to_many relations.
33
- + Deserialize sql join queries to Ruby Objects.
33
+ + Deserialize sql join queries to Ruby Objects (coming soon).
34
34
  + Serialize arbitrary ruby object graphs through YAML.
35
35
  + Connection pooling.
36
36
  + Thread safety.
@@ -38,18 +38,21 @@ The library provides the following features:
38
38
  + Lifecycle callbacks.
39
39
  + Lifecycle observers.
40
40
  + Transparent support for cascading deletes for all backends.
41
- + Hierarchical structures (preorder traversal, materialized paths)
41
+ + Dynamic db related mixins (Orderable, etc).
42
+ + Hierarchical structures (nested sets, more coming soon).
42
43
  + Works safely as part of a distributed application.
43
44
  + Automatic Validation/Constraints.
44
45
  + Simple and clean implementation.
45
46
 
47
+
46
48
  == Download
47
49
 
48
50
  The latest version of Og can be found at
49
51
 
50
- * http://www.rubyforge.com/projects/nitro
52
+ * http://nitro.rubyforge.org
51
53
 
52
- Documentation for Og can be found in the distribution.
54
+ Documentation for Og can be found in the distribution. You can find
55
+ a nice tutorial at www.rubygarden.com
53
56
 
54
57
 
55
58
  == Requirements
@@ -88,7 +91,7 @@ installed RubyGems on your system. Then run the following command:
88
91
 
89
92
  gem install og
90
93
 
91
- Then try to run the examples/og Example application.
94
+ Then try to run the examples in the examples directory.
92
95
 
93
96
  A tar.gz distribution is also available on http://www.rubyforge.com/projects/nitro.
94
97
 
@@ -99,6 +102,12 @@ For any questions regarding Og, feel free to ask on the ruby-talk
99
102
  mailing list (which is mirrored to comp.lang.ruby) or contact
100
103
  mailto:gm@navel.gr.
101
104
 
105
+ An Og specific mailing list is also available. Please subscribe
106
+ to nitro-general@rubyforge.com. The homepage for this list
107
+ is available here:
108
+
109
+ http://rubyforge.org/mailman/listinfo/nitro-general
110
+
102
111
 
103
112
  == Licence
104
113
 
@@ -6,17 +6,14 @@ MAIN DEVELOPER:
6
6
 
7
7
  IDEAS, ADDITIONAL CODING, SUPPORT:
8
8
 
9
+ * Anastasios Koutoumanos <ak@navel.gr>
10
+ Design, additional coding.
11
+
9
12
  * Michael Neumann <mneumann@ntecs.de>
10
13
  Design, additional coding and bug reports.
11
14
 
12
15
  * Matt Bowen <matt.bowen@farweststeel.com>
13
16
  Additional code and documentation.
14
17
 
15
- * Anastasios Koutoumanos <ak@navel.gr>
16
- Design, additional coding.
17
-
18
- * Elias Athanasopoulos <elathan@navel.gr>
19
- Additional coding.
20
-
21
18
  * Thomas Quas <tquas@yahoo.com>
22
19
  Ideas, bug reports, unit tests.
@@ -1,10 +1,53 @@
1
+ == Version 0.15.0 was released on 4/03/2005.
2
+
3
+ A great release. Many cool new features and tons of subtle
4
+ improvements. We also welcome a new core developer, Anastastios
5
+ Koutoumanos, who started contributing with a new SqlServer adapter.
6
+
7
+ Most notable additions:
8
+
9
+ * NestedSets mixin:
10
+
11
+ class Comment
12
+ include NestedSets
13
+ end
14
+
15
+ or
16
+
17
+ class Comment
18
+ include Hierarchical, :method => :nested_sets
19
+ end
20
+
21
+ c.add_comment(child_comment)
22
+ c.full_children
23
+ c.direct_children
24
+ c.children
25
+
26
+ this is a reimplementation of the SqlTraversable mixin
27
+ available in older versions.
28
+
29
+ * New implementation of Orderable mixin:
30
+
31
+ class Comment
32
+ property :body, String
33
+ belongs_to :article, Article
34
+ include Orderable, :scope => article
35
+ end
36
+
37
+ c.move_higher
38
+
39
+ The Orderable mixin uses the :scope parameter to dynamically alter
40
+ the methods appended to the Comment class.
41
+
42
+ * New SqlServer adapter.
43
+
1
44
  == Version 0.14.0 was released on 18/03/2005.
2
45
 
3
46
  Many many important fixes, and many small additions
4
47
  and improvements. Og mixins are introduced with
5
48
  an experimental List mixin implementation.
6
49
 
7
- Most notable addition:
50
+ Most notable additions:
8
51
 
9
52
  * Support for objects that participate in list
10
53
  (ordering/removal etc)
@@ -221,7 +221,7 @@ Article.all.each { |a| puts a }
221
221
  # The previous command updates the whole object. It is used
222
222
  # when there are many updates or you dont care about speed.
223
223
  # To update only specific fields use pupdate or properties_update
224
- a2.pupdate! "title='A specific title'"
224
+ a2.pupdate "title='A specific title'"
225
225
 
226
226
  puts "\n\n"
227
227
  Article.all.each { |a| puts a }
Binary file
data/lib/og.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # * George Moschovitis <gm@navel.gr>
2
2
  # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: og.rb 326 2005-03-28 11:07:17Z gmosx $
3
+ # $Id: og.rb 340 2005-04-04 08:26:58Z gmosx $
4
4
 
5
5
  require 'glue'
6
6
  require 'glue/logger'
@@ -107,7 +107,7 @@ module Og
107
107
 
108
108
  # The version.
109
109
 
110
- Version = '0.14.0'
110
+ Version = '0.15.0'
111
111
 
112
112
  # Library path.
113
113
 
@@ -1,7 +1,7 @@
1
1
  # * Matt Bowen <matt.bowen@farweststeel.com>
2
2
  # * George Moschovitis <gm@navel.gr>
3
3
  # (c) 2004-2005 Navel, all rights reserved.
4
- # $Id: oracle.rb 326 2005-03-28 11:07:17Z gmosx $
4
+ # $Id: oracle.rb 337 2005-03-31 16:20:40Z gmosx $
5
5
 
6
6
  begin
7
7
  require 'oracle'
@@ -123,12 +123,6 @@ class OracleAdapter < Adapter
123
123
  res.close if res
124
124
  end
125
125
 
126
- def eval_og_oid(klass)
127
- klass.class_eval %{
128
- prop_accessor :oid, Fixnum, :sql => "number PRIMARY KEY"
129
- }
130
- end
131
-
132
126
  def create_table(klass, db)
133
127
  conn = db.get_connection
134
128
 
@@ -0,0 +1,360 @@
1
+ # * Anastasios Koutoumanos <ak@navel.gr>
2
+ # (c) 2004-2005 Navel, all rights reserved.
3
+ # $Id: sqlserver.rb 341 2005-04-04 08:28:54Z gmosx $
4
+
5
+ begin
6
+ require 'dbi'
7
+ rescue Object => ex
8
+ Logger.error 'Ruby-DBI bindings not present or ADO support not available.'
9
+ Logger.error ex
10
+ end
11
+
12
+ require 'glue/attribute'
13
+ require 'og/adapter'
14
+ require 'og/connection'
15
+
16
+ module Og
17
+
18
+ # The Microsoft SQL Server Sql Server adapter. This adapter
19
+ # communicates with a Microsoft SQL Server sql server rdbms.
20
+ # This adapter will ONLY work on Windows systems, since it
21
+ # relies on Win32OLE is apparently only available on Windows.
22
+ # It relies on the ADO support in the DBI module. If you are using the
23
+ # one-click installer of Ruby, then you already have DBI installed, but
24
+ # the ADO module is *NOT* installed. You will need to get the latest
25
+ # source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
26
+ # unzip it, and copy the file <tt>src/lib/dbd_ado/ADO.rb</tt> to
27
+ # <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt>
28
+ # (you will need to create the ADO directory). Once you've installed
29
+ # that file, you are ready to go.
30
+ # Originally based on the sqlserver_adapter.rb -- the ActiveRecord
31
+ # adapter for Microsoft SQL Server by Joey Gibson and DeLynn Berry.
32
+ # Information related to the SQL Server datatypes:
33
+ # http://msdn.microsoft.com/library/default.asp?url=/library/en-us/architec/8_ar_da_4ucz.asp
34
+ # For extra documentation see lib/og/adapter.rb
35
+
36
+ class SqlserverAdapter < Adapter
37
+
38
+ def initialize
39
+ super
40
+ @typemap.update(
41
+ Integer => 'int', # drak: maybe smallint?
42
+ Fixnum => 'int',
43
+ Float => 'float',
44
+ String => 'varchar(1024)', # drak: maybe nvarchar for unicode
45
+ Time => 'datetime', # drak: maybe smalldatime?
46
+ Date => 'smalldatetime',
47
+ TrueClass => 'bit'
48
+ )
49
+
50
+ @typecast.update(TrueClass => "#\{:s: ? \'1\' : '0' \}")
51
+ end
52
+
53
+
54
+ def self.timestamp(time = Time.now)
55
+ return nil unless time
56
+ return time.strftime("%Y-%m-%d %H:%M:%S")
57
+ end
58
+
59
+ def self.date(date)
60
+ return nil unless date
61
+ return "#{date.year}-#{date.month}-#{date.mday}"
62
+ end
63
+
64
+ def read_prop(p, idx)
65
+ if p.klass.ancestors.include?(Integer)
66
+ return "res[tuple][#{idx}].to_i"
67
+ elsif p.klass.ancestors.include?(Float)
68
+ return "res[tuple][#{idx}].to_f"
69
+ elsif p.klass.ancestors.include?(String)
70
+ return "res[tuple][#{idx}]"
71
+ elsif p.klass.ancestors.include?(Time)
72
+ return "#{self.class}.parse_timestamp(res[tuple][#{idx}])"
73
+ elsif p.klass.ancestors.include?(Date)
74
+ return "#{self.class}.parse_date(res[tuple][#{idx}])"
75
+ elsif p.klass.ancestors.include?(TrueClass)
76
+ return "('0' != res[tuple][#{idx}])"
77
+ else
78
+ return "YAML::load(res[tuple][#{idx}])"
79
+ end
80
+ end
81
+
82
+ def create_db(database, user = nil, password = nil)
83
+ raise 'Not implemented!'
84
+
85
+ ### how can we get a valid connection when the database does not already exist?
86
+ begin
87
+ conn = DBI.connect("DBI:ADO:Provider=SQLOLEDB;Data Source=localhost;Initial Catalog=master;User Id=sa;Password=sa;")
88
+ conn["AutoCommit"] = true
89
+ conn.execute("CREATE DATABASE #{database}")
90
+ conn.commit
91
+ Logger.warn "Ignoring credentials for database creation (not implemented)" if user
92
+ Logger.info "Created database '#{database}'."
93
+ super
94
+ rescue Exception => ex
95
+ # gmosx: any idea how to better test this?
96
+ if ex.to_s =~ /0x80020009/i
97
+ Logger.warn "Cannot drop database '#{database}', it does not exist!"
98
+ super
99
+ else
100
+ raise
101
+ end
102
+ end
103
+ end
104
+
105
+ def drop_db(database, user = nil, password = nil)
106
+ raise 'Not implemented!'
107
+
108
+ begin
109
+ conn = DBI.connect("DBI:ADO:Provider=SQLOLEDB;Data Source=localhost;Initial Catalog=master;User Id=sa;Password=sa;")
110
+ conn["AutoCommit"] = true
111
+ # FIXME: why no drop?
112
+ # conn.execute("DROP DATABASE #{database}")
113
+ Logger.warn "Ignoring credentials for database drop (not implemented)" if user
114
+ Logger.info "Dropped database '#{database}'."
115
+ super
116
+ rescue Exception => ex
117
+ # gmosx: any idea how to better test this?
118
+ if ex.to_s =~ /0x80020009/i
119
+ Logger.warn "Cannot drop database '#{database}', it does not exist!"
120
+ super
121
+ else
122
+ raise
123
+ end
124
+ end
125
+ end
126
+
127
+ def props_for_insert(klass)
128
+ klass.__props.reject { |p| :oid == p.symbol }
129
+ end
130
+
131
+ def insert_code(klass, db, pre_cb, post_cb="123")
132
+ props = props_for_insert(klass)
133
+ values = props.collect { |p| write_prop(p) }.join(',')
134
+
135
+ sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values});"
136
+
137
+ %{
138
+ #{pre_cb}
139
+ conn.exec "#{sql}"
140
+ conn.commit
141
+ res = conn.query("SELECT IDENT_CURRENT('#{klass::DBTABLE}')")
142
+ @oid = res[0][0].to_i
143
+ #res.finish
144
+ #{post_cb}
145
+ }
146
+ end
147
+
148
+ def new_connection(db)
149
+ return SqlserverConnection.new(db)
150
+ end
151
+
152
+ def calc_field_index(klass, db)
153
+ res = db.get_connection.store.execute("SELECT TOP 1 * FROM #{klass::DBTABLE}")
154
+ meta = db.managed_classes[klass]
155
+ columns = res.column_names
156
+
157
+ for idx in (0...columns.size)
158
+ meta.field_index[columns[idx]] = idx
159
+ end
160
+
161
+ ensure
162
+ res.finish
163
+ end
164
+
165
+ def create_table(klass, db)
166
+ conn = db.get_connection
167
+
168
+ fields = create_fields(klass)
169
+
170
+ sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
171
+
172
+ # Create table constrains.
173
+ if klass.__meta and constrains = klass.__meta[:sql_constrain]
174
+ sql << ", #{constrains.join(', ')}"
175
+ end
176
+ sql << ");"
177
+
178
+ begin
179
+ conn.store.execute(sql)
180
+ conn.commit
181
+ Logger.info "Created table '#{klass::DBTABLE}'."
182
+ rescue => ex
183
+ if ex.to_s =~ /80040E14/i
184
+ Logger.debug 'Table already exists' if $DBG
185
+ else
186
+ raise
187
+ end
188
+ end
189
+
190
+ # Create indices.
191
+
192
+ if klass.__meta and indices = klass.__meta[:sql_index]
193
+ for data in indices
194
+ idx, options = *data
195
+ idx = idx.to_s
196
+ pre_sql, post_sql = options[:pre], options[:post]
197
+ idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
198
+ sql << " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx});"
199
+ begin
200
+ conn.store.execute(sql)
201
+ conn.commit
202
+ rescue => ex
203
+ if ex.to_s =~ /80040E14/i
204
+ Logger.debug 'Table already exists' if $DBG
205
+ else
206
+ raise
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ # Create join tables if needed. Join tables are used in
213
+ # 'many_to_many' relations.
214
+
215
+ if klass.__meta and joins = klass.__meta[:sql_join]
216
+ for data in joins
217
+ # the class to join to and some options.
218
+ join_name, join_class, options = *data
219
+
220
+ # gmosx: dont use DBTABLE here, perhaps the join class
221
+ # is not managed yet.
222
+ join_table = "#{self.class.join_table(klass, join_class, join_name)}"
223
+ join_src = "#{self.class.encode(klass)}_oid"
224
+ join_dst = "#{self.class.encode(join_class)}_oid"
225
+ begin
226
+ conn.store.execute("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )").finish
227
+ conn.store.execute("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").finish
228
+ conn.store.execute("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").finish
229
+ conn.commit
230
+ rescue => ex
231
+ # gmosx: any idea how to better test this?
232
+ if ex.to_s =~ /80040E14/i
233
+ Logger.debug "Join table already exists" if $DBG
234
+ else
235
+ raise
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ ensure
242
+ db.put_connection
243
+ end
244
+
245
+ def drop_table(klass)
246
+ super
247
+ exec "DROP TABLE #{klass::DBTABLE}"
248
+ end
249
+
250
+ # Generate the property for oid.
251
+
252
+ def eval_og_oid(klass)
253
+ klass.class_eval %{
254
+ prop_accessor :oid, Fixnum, :sql => 'int NOT NULL IDENTITY(1, 1) PRIMARY KEY'
255
+ }
256
+ end
257
+
258
+ end
259
+
260
+ # The SqlserverConnection connection.
261
+
262
+ class SqlserverConnection < Connection
263
+
264
+ def initialize(db)
265
+ super
266
+ config = db.config
267
+
268
+ begin
269
+ @store = DBI.connect("DBI:ADO:Provider=SQLOLEDB;Data Source=#{config[:address]};Initial Catalog=#{config[:database]};User Id=#{config[:user].to_s};Password=#{config[:password].to_s};")
270
+ rescue => ex
271
+ # gmosx, FIXME: drak, fix this!
272
+ if ex.to_s =~ /database .* does not exist/i
273
+ Logger.info "Database '#{config[:database]}' not found!"
274
+ @db.adapter.create_db(config[:database], config[:user])
275
+ retry
276
+ end
277
+ raise
278
+ end
279
+ end
280
+
281
+ def close
282
+ @store.disconnect
283
+ super
284
+ end
285
+
286
+ def prepare(sql)
287
+ @store.prepare(sql)
288
+ end
289
+
290
+ def query(sql)
291
+ Logger.debug sql if $DBG
292
+ begin
293
+ res = @store.select_all(sql)
294
+ return res
295
+ rescue => ex
296
+ handle_db_exception(ex, sql)
297
+ end
298
+ end
299
+
300
+ def exec(sql)
301
+ Logger.debug sql if $DBG
302
+ begin
303
+ @store.execute(sql).finish
304
+ rescue => ex
305
+ handle_db_exception(ex, sql)
306
+ end
307
+ end
308
+
309
+ def start
310
+ # no need to do something.
311
+ end
312
+
313
+ def commit
314
+ @store.commit
315
+ end
316
+
317
+ def rollback
318
+ @store.rollback
319
+ end
320
+
321
+ def valid_res?(res)
322
+ return !(res.nil?)
323
+ end
324
+
325
+ def read_one(res, klass)
326
+ return nil unless valid_res?(res)
327
+ return nil unless res
328
+
329
+ obj = klass.new
330
+ obj.og_read(res)
331
+
332
+ return obj
333
+ end
334
+
335
+ def read_all(res, klass)
336
+ return [] unless valid_res?(res)
337
+ objects = []
338
+
339
+ for tuple in (0...res.size)
340
+ obj = klass.new
341
+ obj.og_read(res, tuple)
342
+ objects << obj
343
+ end
344
+
345
+ return objects
346
+ end
347
+
348
+ def read_int(res, idx = 0)
349
+ val = res.next[idx].to_i
350
+ res.finish
351
+ return val
352
+ end
353
+
354
+ def get_row(res)
355
+ res.next
356
+ end
357
+
358
+ end
359
+
360
+ end