og 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
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