nitro 0.9.5 → 0.10.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 (104) hide show
  1. data/ChangeLog +260 -0
  2. data/INSTALL +60 -0
  3. data/LICENSE +1 -0
  4. data/README +19 -20
  5. data/RELEASES +48 -0
  6. data/Rakefile +102 -92
  7. data/benchmark/og/bench.rb +74 -0
  8. data/benchmark/og/sqlite-no-prepare.1.txt +13 -0
  9. data/benchmark/og/sqlite-no-prepare.2.txt +13 -0
  10. data/benchmark/og/sqlite-prepare.1.txt +13 -0
  11. data/benchmark/og/sqlite-prepare.2.txt +13 -0
  12. data/bin/cluster +1 -1
  13. data/bin/nitro +3 -0
  14. data/bin/proto/conf/app.conf.rb +2 -10
  15. data/examples/README.windows +9 -0
  16. data/examples/blog/README +16 -4
  17. data/examples/blog/lib/blog.rb +3 -3
  18. data/examples/blog/lib/blog/controller.rb +7 -9
  19. data/examples/blog/root/fcgi.rb +2 -4
  20. data/examples/blog/root/style.xsl +4 -6
  21. data/examples/blog/run.rb +41 -0
  22. data/examples/flash/run.rb +9 -0
  23. data/examples/no_xsl_blog/README +0 -1
  24. data/examples/no_xsl_blog/conf/app.conf.rb +6 -13
  25. data/examples/no_xsl_blog/lib/blog.rb +2 -2
  26. data/examples/no_xsl_blog/lib/blog/controller.rb +6 -6
  27. data/examples/no_xsl_blog/root/fcgi.rb +2 -4
  28. data/examples/no_xsl_blog/run.rb +38 -0
  29. data/examples/og/mock_example.rb +0 -2
  30. data/examples/og/mysql_to_psql.rb +0 -2
  31. data/examples/og/run.rb +23 -22
  32. data/examples/tiny/root/fcgi.rb +2 -4
  33. data/examples/tiny/root/index.xhtml +21 -5
  34. data/examples/tiny/root/upload.xhtml +23 -0
  35. data/examples/tiny/run.rb +9 -0
  36. data/examples/wee_style/{wee.rb → run.rb} +13 -13
  37. data/install.rb +44 -0
  38. data/lib/glue/array.rb +6 -10
  39. data/lib/glue/attribute.rb +0 -3
  40. data/lib/glue/cache.rb +1 -1
  41. data/lib/glue/inflector.rb +5 -5
  42. data/lib/glue/mixins.rb +3 -12
  43. data/lib/glue/number.rb +1 -1
  44. data/lib/glue/object.rb +7 -1
  45. data/lib/glue/property.rb +32 -22
  46. data/lib/glue/string.rb +13 -75
  47. data/lib/glue/time.rb +2 -2
  48. data/lib/glue/validation.rb +7 -11
  49. data/lib/nitro.rb +16 -1
  50. data/lib/nitro/{adaptors → adapters}/cgi.rb +101 -20
  51. data/lib/nitro/{adaptors → adapters}/fastcgi.rb +3 -2
  52. data/lib/nitro/{adaptors → adapters}/webrick.rb +4 -4
  53. data/lib/nitro/builders/rss.rb +1 -1
  54. data/lib/nitro/builders/xml.rb +8 -10
  55. data/lib/nitro/cluster.rb +1 -1
  56. data/lib/nitro/conf.rb +34 -0
  57. data/lib/nitro/controller.rb +8 -9
  58. data/lib/nitro/dispatcher.rb +38 -11
  59. data/lib/nitro/filters.rb +1 -1
  60. data/lib/nitro/markup.rb +14 -1
  61. data/lib/nitro/render.rb +7 -10
  62. data/lib/nitro/runner.rb +232 -0
  63. data/lib/nitro/ui/pager.rb +2 -6
  64. data/lib/nitro/uri.rb +7 -11
  65. data/lib/og.rb +27 -261
  66. data/lib/og/adapter.rb +352 -0
  67. data/lib/og/adapters/mysql.rb +304 -0
  68. data/lib/og/adapters/psql.rb +286 -0
  69. data/lib/og/adapters/sqlite.rb +262 -0
  70. data/lib/og/backend.rb +1 -1
  71. data/lib/og/connection.rb +123 -87
  72. data/lib/og/database.rb +268 -0
  73. data/lib/og/meta.rb +23 -22
  74. data/lib/og/mock.rb +2 -3
  75. data/lib/xsl/base.xsl +1 -55
  76. data/test/glue/tc_property.rb +2 -0
  77. data/test/glue/tc_property_type_checking.rb +32 -0
  78. data/test/glue/tc_strings.rb +2 -2
  79. data/test/glue/tc_validation.rb +2 -0
  80. data/test/nitro/adapters/raw_post1.bin +0 -0
  81. data/test/nitro/{adaptors → adapters}/tc_cgi.rb +11 -2
  82. data/test/nitro/{adaptors → adapters}/tc_webrick.rb +3 -3
  83. data/test/nitro/builders/tc_xml.rb +14 -5
  84. data/test/nitro/tc_dispatcher.rb +3 -3
  85. data/test/nitro/tc_uri.rb +2 -4
  86. data/test/og/tc_lifecycle.rb +22 -25
  87. data/test/og/tc_sqlite.rb +87 -0
  88. data/test/tc_og.rb +61 -42
  89. metadata +67 -33
  90. data/examples/blog/conf/app.conf.rb +0 -52
  91. data/examples/blog/ctl +0 -4
  92. data/examples/flash/conf/app.conf.rb +0 -21
  93. data/examples/flash/ctl +0 -4
  94. data/examples/no_xsl_blog/conf/apache.conf +0 -0
  95. data/examples/no_xsl_blog/ctl +0 -4
  96. data/examples/tiny/conf/app.conf.rb +0 -17
  97. data/examples/tiny/ctl +0 -4
  98. data/lib/glue/macro.rb +0 -56
  99. data/lib/nitro/adaptors/runner.rb +0 -123
  100. data/lib/nitro/version.rb +0 -15
  101. data/lib/og/backends/mysql.rb +0 -370
  102. data/lib/og/backends/psql.rb +0 -386
  103. data/lib/og/backends/sqlite.rb +0 -383
  104. data/lib/og/version.rb +0 -9
@@ -1,6 +1,6 @@
1
1
  # * George Moschovitis <gm@navel.gr>
2
2
  # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: pager.rb 223 2005-01-26 17:07:40Z gmosx $
3
+ # $Id: pager.rb 254 2005-02-10 12:44:05Z gmosx $
4
4
 
5
5
  require 'nitro/uri'
6
6
 
@@ -12,13 +12,9 @@ module N; module UI
12
12
  #
13
13
  # The new version is carefully designed for scaleability. It stores
14
14
  # only the items for one page. The name parameter is needed, multiple
15
- # pagers can coexist in a single page. Unlike the v1/v2 pagers this
15
+ # pagers can coexist in a single page. Unlike older pagers this
16
16
  # pager leverages the SQL LIMIT option to optimize database interaction.
17
17
  #
18
- # The pager does not extend Array (it includes an Array instead) to
19
- # avoid a concat() in the initialization step.
20
- #
21
- #
22
18
  # === Example
23
19
  #
24
20
  # @pager = N::UI::Pager.new('entries', @request, 5)
data/lib/nitro/uri.rb CHANGED
@@ -1,8 +1,6 @@
1
- # code:
2
1
  # * George Moschovitis <gm@navel.gr>
3
- #
4
- # (c) 2004 Navel, all rights reserved.
5
- # $Id: uri.rb 202 2005-01-17 10:44:13Z gmosx $
2
+ # (c) 2004-2005 Navel, all rights reserved.
3
+ # $Id: uri.rb 254 2005-02-10 12:44:05Z gmosx $
6
4
 
7
5
  require "uri"
8
6
  require "cgi"
@@ -11,8 +9,6 @@ require "glue/string"
11
9
 
12
10
  module N
13
11
 
14
- # = UriUtils
15
- #
16
12
  # URI utilities collection
17
13
  #
18
14
  # === Design:
@@ -23,7 +19,7 @@ module N
23
19
  # which isn't possible if you use self.
24
20
  #
25
21
  # The uris passed as parameters are typically strings.
26
- #
22
+
27
23
  module UriUtils
28
24
 
29
25
  # Decode the uri components.
@@ -64,7 +60,7 @@ module UriUtils
64
60
  # hash of parameters, contains arrays for multivalued parameters
65
61
  # (multiselect, checkboxes , etc)
66
62
  # If no query string is provided (nil or "") returns an empty hash.
67
- #
63
+
68
64
  def self.query_string_to_hash(query_string)
69
65
  return {} unless query_string
70
66
 
@@ -156,7 +152,7 @@ module UriUtils
156
152
  #
157
153
  # === TODO:
158
154
  # optimize this a litle bit.
159
- #
155
+
160
156
  def self.update_query_string(uri, parameters)
161
157
  query_string = self.get_query_string(uri)
162
158
  rest = uri.dup.gsub(/\?#{query_string}/, "")
@@ -176,7 +172,7 @@ module UriUtils
176
172
  # Gets the request uri, injects extra parameters in the query string
177
173
  # and returns a new uri. The request object is not modified.
178
174
  # There is always a qs string so an extra test is skipped.
179
- #
175
+
180
176
  def self.update_request_uri(request, parameters)
181
177
  hash = request.parameters.dup()
182
178
  hash.update(parameters)
@@ -191,4 +187,4 @@ module UriUtils
191
187
 
192
188
  end
193
189
 
194
- end # module
190
+ end
data/lib/og.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # * George Moschovitis <gm@navel.gr>
2
2
  # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: og.rb 248 2005-01-31 13:38:34Z gmosx $
3
+ # $Id: og.rb 259 2005-02-15 08:54:54Z gmosx $
4
4
 
5
+ require 'glue'
5
6
  require 'glue/logger'
6
7
  require 'glue/attribute'
7
8
  require 'glue/property'
@@ -9,6 +10,7 @@ require 'glue/array'
9
10
  require 'glue/hash'
10
11
  require 'glue/time'
11
12
  require 'glue/pool'
13
+ require 'glue/validation'
12
14
 
13
15
  # Og (ObjectGraph) is an efficient, yet simple Object-Relational
14
16
  # mapping library.
@@ -19,10 +21,10 @@ require 'glue/pool'
19
21
  #
20
22
  # + Object-Relational mapping.
21
23
  # + Absolutely no configuration files.
22
- # + Multiple backends (PostgreSQL, MySQL).
24
+ # + Multiple backends (PostgreSQL, MySQL, SQLite).
23
25
  # + ActiveRecord-style meta language and db aware methods.
24
- # + Deserialize to Ruby Objects or ResultSets.
25
- # + Deserialize sql join queries to Ruby Objects.
26
+ # + Deserialize to Ruby Objects.
27
+ # + Deserialize sql join queries to Ruby Objects (temporarily dissabled).
26
28
  # + Serialize arbitrary ruby object graphs through YAML.
27
29
  # + Connection pooling.
28
30
  # + Thread safety.
@@ -89,6 +91,18 @@ require 'glue/pool'
89
91
 
90
92
  class Og
91
93
 
94
+ # The name.
95
+
96
+ Name = 'ObjectGraph'
97
+
98
+ # The version.
99
+
100
+ Version = '0.10.0'
101
+
102
+ # Library path.
103
+
104
+ LibPath = File.dirname(__FILE__)
105
+
92
106
  # If true, only allow reading from the database. Usefull
93
107
  # for maintainance.
94
108
 
@@ -131,267 +145,19 @@ class Og
131
145
  @@db.get_connection
132
146
  end
133
147
 
134
- end
135
-
136
- # gmosx: leave this here.
137
- require 'og/enchant'
138
- require 'og/meta'
139
-
140
- class Og
141
-
142
- # Marker module. If included this in a class, the Og automanager
143
- # ignores this class.
148
+ # The adapter of the active database.
144
149
 
145
- module Unmanageable; end
146
-
147
- # Encapsulates an Og Database.
148
-
149
- class Database
150
- include Og::Enchant
151
-
152
- # Managed class metadata
153
-
154
- class ManagedClassMeta
155
- # The managed class.
156
- attr_accessor :klass
157
-
158
- # A mapping of the database fields to the object properties.
159
- attr_accessor :field_index
160
-
161
- def initialize(klass = nil)
162
- @klass = klass
163
- @field_index = {}
164
- end
150
+ def self.adapter
151
+ @@db.adapter
165
152
  end
166
153
 
167
- # hash of configuration options.
154
+ # Marker module. If included this in a class, the Og automanager
155
+ # ignores this class.
168
156
 
169
- attr_accessor :config
157
+ module Unmanageable; end
170
158
 
171
- # Pool of connections to the backend.
172
-
173
- attr_accessor :connection_pool
174
-
175
- # Managed classes.
176
-
177
- attr_accessor :managed_classes
178
-
179
- # Initialize the database interface.
180
-
181
- def initialize(config)
182
- @config = config
183
-
184
- # populate with default options if needed.
185
- @config[:connection_count] ||= 1
186
-
187
- # require the backend.
188
- backend = @config[:backend] || "psql"
189
- require "og/backends/#{backend}"
190
- eval %{ @config[:backend] = #{backend.capitalize}Backend }
191
-
192
- @connection_pool = N::Pool.new
193
- @managed_classes = N::SafeHash.new
194
-
195
- Logger.info "Connecting to database '#{@config[:database]}' using backend '#{backend}'."
196
-
197
- @config[:connection_count].times do
198
- @connection_pool << Og::Connection.new(self)
199
- end
200
-
201
- # gmosx, FIXME: this automanage code is not elegant and slow
202
- # should probably recode this, along with glue/property.rb
203
-
204
- if Og.auto_manage_classes
205
- # automatically manage classes with properties and metadata.
206
- # gmosx: Any idea how to optimize this?
207
- classes_to_manage = []
208
- ObjectSpace.each_object(Class) do |c|
209
- if c.respond_to?(:__props) and c.__props
210
- classes_to_manage << c
211
- end
212
- end
213
- Logger.info "Og auto manages the following classes:"
214
- Logger.info "#{classes_to_manage.inspect}"
215
- manage_classes(*classes_to_manage)
216
- end
217
-
218
- # use the newly created database.
219
- Og.use(self)
220
- end
221
-
222
- # Shutdown the database interface.
223
-
224
- def shutdown
225
- for con in @connection_pool
226
- con.close()
227
- end
228
- end
229
- alias_method :close, :shutdown
230
-
231
- # Get a connection from the pool to access the database.
232
- # Stores the connection in a thread-local variable.
233
-
234
- def get_connection
235
- thread = Thread.current
236
-
237
- unless conn = thread[:og_conn]
238
- conn = @connection_pool.pop()
239
- thread[:og_conn] = conn
240
- end
241
-
242
- return conn
243
- end
244
- alias_method :connection, :get_connection
245
-
246
- # Restore an unused connection to the pool.
247
-
248
- def put_connection
249
- thread = Thread.current
250
-
251
- if conn = thread[:og_conn]
252
- thread[:og_conn] = nil
253
- return @connection_pool.push(conn)
254
- end
255
- end
256
-
257
- # Utility method, automatically restores a connection to the pool.
258
-
259
- def connect(deserialize = nil, &block)
260
- result = nil
261
-
262
- begin
263
- conn = get_connection()
264
- conn.deserialize = deserialize unless deserialize.nil?
265
-
266
- result = yield(conn)
267
-
268
- conn.deserialize = true
269
- ensure
270
- put_connection()
271
- end
272
-
273
- return result
274
- end
275
- alias_method :open, :connect
276
-
277
-
278
- # Register a standard Ruby class as managed.
279
-
280
- def manage(klass)
281
- return if managed?(klass) or klass.ancestors.include?(Og::Unmanageable)
282
-
283
- @managed_classes[klass] = ManagedClassMeta.new(klass)
284
-
285
- # Add standard og methods to the class.
286
- convert(klass)
287
-
288
- # Add helper methods to the class.
289
- enchant(klass) if Og.enchant_managed_classes
290
- end
291
-
292
- # Helper method to set multiple managed classes.
293
-
294
- def manage_classes(*klasses)
295
- for klass in klasses
296
- manage(klass)
297
- end
298
- end
299
-
300
- # Stop managing a Ruby class
301
-
302
- def unmanage(klass)
303
- @managed_classes.delete(klass)
304
- end
305
-
306
- # Is this class managed?
307
- #
308
- def managed?(klass)
309
- return @managed_classes.include?(klass)
310
- end
311
-
312
- # Add standard og functionality to the class
313
-
314
- def convert(klass)
315
- # Grab backend class
316
- backend = @config[:backend]
317
-
318
- # gmosx: this check is needed to allow the developer to customize
319
- # the sql generated for oid
320
- backend.eval_og_oid(klass) unless klass.instance_methods.include?(:oid)
321
-
322
- klass.class_eval %{
323
- DBTABLE = "#{backend.table(klass)}"
324
- DBSEQ = "#{backend.table(klass)}_oids_seq"
325
-
326
- def to_i()
327
- @oid
328
- end
329
- }
330
-
331
- # Create the schema for this class if not available.
332
- create_table(klass)
333
-
334
- # Precompile some code that gets executed all the time.
335
- # Deletion code is not precompiled, because it is not used
336
- # as frequently.
337
- backend.eval_og_insert(klass)
338
- backend.eval_og_update(klass)
339
- backend.eval_og_deserialize(klass, self)
340
- end
341
-
342
- # Automatically wrap connection methods.
343
- #
344
- def self.wrap_method(method, args)
345
- args = args.split(/,/)
346
- class_eval %{
347
- def #{method}(#{args.join(", ")})
348
- thread = Thread.current
349
-
350
- unless conn = thread[:og_conn]
351
- conn = @connection_pool.pop()
352
- thread[:og_conn] = conn
353
- end
354
-
355
- return conn.#{method}(#{args.collect {|a| a.split(/=/)[0]}.join(", ")})
356
- end
357
- }
358
- end
359
-
360
- wrap_method :create_table, "klass"
361
- wrap_method :drop_table, "klass"
362
- wrap_method :save, "obj"; alias_method :<<, :save; alias_method :put, :save
363
- wrap_method :insert, "obj"
364
- wrap_method :update, "obj"
365
- wrap_method :update_properties, "update_sql, obj_or_oid, klass = nil"
366
- wrap_method :pupdate, "update_sql, obj_or_oid, klass = nil"
367
- wrap_method :load, "oid, klass"; alias_method :get, :load
368
- wrap_method :load_by_oid, "oid, klass"
369
- wrap_method :load_by_name, "name, klass"
370
- wrap_method :load_all, "klass, extrasql = nil"
371
- wrap_method :select, "sql, klass"
372
- wrap_method :select_one, "sql, klass"
373
- wrap_method :count, "sql, klass = nil"
374
- wrap_method :delete, "obj_or_oid, klass = nil"
375
- wrap_method :query, "sql"
376
- wrap_method :exec, "sql"
377
-
378
- class << self
379
- def create_db!(config)
380
- get_connection().db.create_db(config[:database], config[:user],
381
- config[:password])
382
- end
383
- alias_method :create!, :create_db!
384
-
385
- def drop_db!(config)
386
- backend = config[:backend] || "psql"
387
- require "og/backends/#{backend}"
388
- eval %{
389
- #{backend.capitalize}Backend.drop_db(config[:database], config[:user],
390
- config[:password])
391
- }
392
- end
393
- alias_method :drop!, :drop_db!
394
- end
395
159
  end
396
160
 
397
- end
161
+ # gmosx: leave this here.
162
+
163
+ require 'og/database'
data/lib/og/adapter.rb ADDED
@@ -0,0 +1,352 @@
1
+ # * George Moschovitis <gm@navel.gr>
2
+ # (c) 2004-2005 Navel, all rights reserved.
3
+ # $Id: adapter.rb 255 2005-02-10 12:45:32Z gmosx $
4
+
5
+ require 'yaml'
6
+ require 'singleton'
7
+
8
+ require 'og/connection'
9
+
10
+ class Og
11
+
12
+ # An adapter communicates with the backend datastore.
13
+ # The adapters for all supported datastores extend this
14
+ # class. Typically, an RDBMS is used to implement a
15
+ # datastore.
16
+
17
+ class Adapter
18
+ include Singleton
19
+
20
+ # A mapping between Ruby and backend Datastore types.
21
+
22
+ attr_accessor :typemap
23
+
24
+ # Lookup the adapter instance from the adapter name.
25
+
26
+ def self.for_name(name)
27
+ require "og/adapters/#{name}"
28
+ eval %{ return #{name.capitalize}Adapter.instance }
29
+ end
30
+
31
+ def initialize
32
+ @typemap = {
33
+ Integer => 'integer',
34
+ Fixnum => 'integer',
35
+ Float => 'float',
36
+ String => 'text',
37
+ Time => 'timestamp',
38
+ Date => 'date',
39
+ TrueClass => 'boolean',
40
+ Object => 'text',
41
+ Array => 'text',
42
+ Hash => 'text'
43
+ }
44
+ end
45
+
46
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
47
+ # :section: Utilities
48
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
49
+
50
+ # Escape an SQL string
51
+
52
+ def self.escape(str)
53
+ return nil unless str
54
+ return str.gsub( /'/, "''" )
55
+ end
56
+
57
+ # Convert a ruby time to an sql timestamp.
58
+ # TODO: Optimize this
59
+
60
+ def self.timestamp(time = Time.now)
61
+ return nil unless time
62
+ return time.strftime("%Y-%m-%d %H:%M:%S")
63
+ end
64
+
65
+ # Output YYY-mm-dd
66
+ # TODO: Optimize this
67
+
68
+ def self.date(date)
69
+ return nil unless date
70
+ return "#{date.year}-#{date.month}-#{date.mday}"
71
+ end
72
+
73
+ # Parse sql datetime
74
+ # TODO: Optimize this
75
+
76
+ def self.parse_timestamp(str)
77
+ return Time.parse(str)
78
+ end
79
+
80
+ # Input YYYY-mm-dd
81
+ # TODO: Optimize this
82
+
83
+ def self.parse_date(str)
84
+ return nil unless str
85
+ return Date.strptime(str)
86
+ end
87
+
88
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
89
+ # :section: Database methods
90
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
91
+
92
+ # Create the database.
93
+
94
+ def create_db(database, user = nil, password = nil)
95
+ Logger.info "Creating database '#{database}'."
96
+ end
97
+
98
+ # Drop the database.
99
+
100
+ def drop_db(database, user = nil, password = nil)
101
+ Logger.info "Dropping database '#{database}'."
102
+ end
103
+
104
+ # Create a new connection to the backend.
105
+
106
+ def new_connection(db)
107
+ return Og::Connection.new(db)
108
+ end
109
+
110
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
111
+ # :section: OR mapping methods and utilities.
112
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
113
+
114
+ # Encode the name of the klass as an sql safe string.
115
+ # The Module separators are replaced with _ and NOT stripped
116
+ # out so that we can convert back to the original notation if
117
+ # needed. The leading module if available is removed.
118
+
119
+ def self.encode(klass)
120
+ "#{klass.name.gsub(/^.*::/, "")}".gsub(/::/, "_").downcase
121
+ end
122
+
123
+ # The name of the SQL table where objects of this class
124
+ # are stored.
125
+
126
+ def self.table(klass)
127
+ "_#{Og.table_prefix}#{encode(klass)}"
128
+ end
129
+
130
+ # The name of the join table for the two given classes.
131
+
132
+ def self.join_table(klass1, klass2)
133
+ "_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}"
134
+ end
135
+
136
+ # Return an sql string evaluator for the property.
137
+ # No need to optimize this, used only to precalculate code.
138
+ # YAML is used to store general Ruby objects to be more
139
+ # portable.
140
+ #
141
+ # FIXME: add extra handling for float.
142
+
143
+ def write_prop(p)
144
+ if p.klass.ancestors.include?(Integer)
145
+ return "#\{@#{p.symbol} || 'NULL'\}"
146
+ elsif p.klass.ancestors.include?(Float)
147
+ return "#\{@#{p.symbol} || 'NULL'\}"
148
+ elsif p.klass.ancestors.include?(String)
149
+ return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
150
+ elsif p.klass.ancestors.include?(Time)
151
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
152
+ elsif p.klass.ancestors.include?(Date)
153
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
154
+ elsif p.klass.ancestors.include?(TrueClass)
155
+ return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
156
+ else
157
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
158
+ end
159
+ end
160
+
161
+ # Return an evaluator for reading the property.
162
+ # No need to optimize this, used only to precalculate code.
163
+
164
+ def read_prop(p, idx)
165
+ if p.klass.ancestors.include?(Integer)
166
+ return "res[#{idx}].to_i"
167
+ elsif p.klass.ancestors.include?(Float)
168
+ return "res[#{idx}].to_f"
169
+ elsif p.klass.ancestors.include?(String)
170
+ return "res[#{idx}]"
171
+ elsif p.klass.ancestors.include?(Time)
172
+ return "#{self.class}.parse_timestamp(res[#{idx}])"
173
+ elsif p.klass.ancestors.include?(Date)
174
+ return "#{self.class}.parse_date(res[#{idx}])"
175
+ elsif p.klass.ancestors.include?(TrueClass)
176
+ return "('0' != res[#{idx}])"
177
+ else
178
+ return "YAML::load(res[#{idx}])"
179
+ end
180
+ end
181
+
182
+ # Create the fields that correpsond to the klass properties.
183
+ # The generated fields array is used in create_table.
184
+ # If the property has an :sql metadata this overrides the
185
+ # default mapping. If the property has an :extra_sql metadata
186
+ # the extra sql is appended after the default mapping.
187
+
188
+ def create_fields(klass)
189
+ fields = []
190
+
191
+ klass.__props.each do |p|
192
+ klass.sql_index(p.symbol) if p.meta[:sql_index]
193
+
194
+ field = "#{p.symbol}"
195
+
196
+ if p.meta and p.meta[:sql]
197
+ field << " #{p.meta[:sql]}"
198
+ else
199
+ field << " #{@typemap[p.klass]}"
200
+ # attach extra sql
201
+ if p.meta and extra_sql = p.meta[:extra_sql]
202
+ field << " #{extra_sql}"
203
+ end
204
+ end
205
+
206
+ fields << field
207
+ end
208
+
209
+ return fields
210
+ end
211
+
212
+ # Create the managed object table. The properties of the
213
+ # object are mapped to the table columns. Additional sql relations
214
+ # and constrains are created (indicices, sequences, etc).
215
+
216
+ def create_table(klass)
217
+ raise 'Not implemented!'
218
+ end
219
+
220
+ # Returns the props that will be included in the insert query.
221
+ # For some backends the oid should be stripped.
222
+
223
+ def props_for_insert(klass)
224
+ klass.__props
225
+ end
226
+
227
+ # Returns the code that actually inserts the object into the
228
+ # database. Returns the code as String.
229
+
230
+ def insert_code(klass, sql, pre_cb, post_cb)
231
+ raise 'Not implemented!'
232
+ end
233
+
234
+ # Generate the mapping of the database fields to the
235
+ # object properties.
236
+
237
+ def calc_field_index(klass, og)
238
+ raise 'Not implemented!'
239
+ end
240
+
241
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
242
+ # :section: Managed object enchant methods
243
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
244
+
245
+ # Generate the property for oid.
246
+
247
+ def eval_og_oid(klass)
248
+ klass.class_eval %{
249
+ prop_accessor :oid, Fixnum, :sql => "integer PRIMARY KEY"
250
+ }
251
+ end
252
+
253
+ # Precompile the insert code for the given class.
254
+ # The generated code sets the oid when inserting!
255
+
256
+ def eval_og_insert(klass, db)
257
+ if klass.instance_methods.include?('og_pre_insert')
258
+ pre_cb = 'og_pre_insert(conn);'
259
+ else
260
+ pre_cb = ''
261
+ end
262
+
263
+ if klass.instance_methods.include?('og_post_insert')
264
+ post_cb = 'og_post_insert(conn);'
265
+ else
266
+ post_cb = ''
267
+ end
268
+
269
+ if klass.instance_methods.include?('og_pre_insert_update')
270
+ pre_cb << 'og_pre_insert_update(conn);'
271
+ end
272
+
273
+ if klass.instance_methods.include?('og_post_insert_update')
274
+ post_cb << 'og_post_insert_update(conn);'
275
+ end
276
+
277
+ klass.class_eval %{
278
+ def og_insert(conn)
279
+ #{insert_code(klass, db, pre_cb, post_cb)}
280
+ end
281
+ }
282
+ end
283
+
284
+ # Precompile the update code for the given class.
285
+ # Ignore the oid when updating!
286
+
287
+ def eval_og_update(klass, db)
288
+ props = klass.__props.reject { |p| :oid == p.symbol }
289
+
290
+ updates = props.collect { |p|
291
+ "#{p.name}=#{write_prop(p)}"
292
+ }
293
+
294
+ sql = "UPDATE #{klass::DBTABLE} SET #{updates.join(', ')} WHERE oid=#\{@oid\}"
295
+
296
+ if klass.instance_methods.include?('og_pre_update')
297
+ pre_cb = 'og_pre_update(conn);'
298
+ else
299
+ pre_cb = ''
300
+ end
301
+
302
+ if klass.instance_methods.include?('og_post_update')
303
+ post_cb = 'og_post_update(conn);'
304
+ else
305
+ post_cb = ''
306
+ end
307
+
308
+ if klass.instance_methods.include?('og_pre_insert_update')
309
+ pre_cb << 'og_pre_insert_update(conn);'
310
+ end
311
+
312
+ if klass.instance_methods.include?('og_post_insert_update')
313
+ post_cb << 'og_post_insert_update(conn);'
314
+ end
315
+
316
+ klass.class_eval %{
317
+ def og_update(conn)
318
+ #{pre_cb}
319
+ conn.exec "#{sql}"
320
+ #{post_cb}
321
+ end
322
+ }
323
+ end
324
+
325
+ # Precompile the code to read (deserialize) objects of the
326
+ # given class from the backend. In order to allow for changing
327
+ # field/attribute orders we have to use a field mapping hash.
328
+
329
+ def eval_og_read(klass, db)
330
+ calc_field_index(klass, db)
331
+
332
+ props = klass.__props
333
+ code = []
334
+
335
+ props.each do |p|
336
+ if idx = db.managed_classes[klass].field_index[p.name]
337
+ # more fault tolerant if a new field is added and it
338
+ # doesnt exist in the database.
339
+ code << "@#{p.name} = #{read_prop(p, idx)}"
340
+ end
341
+ end
342
+
343
+ klass.class_eval %{
344
+ def og_read(res, tuple = nil)
345
+ #{code.join('; ')}
346
+ end
347
+ }
348
+ end
349
+
350
+ end
351
+
352
+ end