nitro 0.11.0 → 0.12.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 (56) hide show
  1. data/ChangeLog +150 -0
  2. data/README +1 -1
  3. data/RELEASES +89 -0
  4. data/Rakefile +3 -3
  5. data/{AUTHORS → doc/AUTHORS} +0 -0
  6. data/{LICENSE → doc/LICENSE} +0 -0
  7. data/doc/bugs.txt +2 -1
  8. data/examples/README.windows +2 -2
  9. data/examples/blog/lib/blog/controller.rb +9 -8
  10. data/examples/blog/log/apache.error_log +71 -0
  11. data/{lib/xsl → examples/blog/root}/base.xsl +0 -0
  12. data/examples/blog/root/error.xhtml +56 -0
  13. data/examples/blog/root/index.xhtml +2 -2
  14. data/examples/blog/root/recent_posts.xhtml +1 -1
  15. data/examples/blog/root/style.xsl +4 -4
  16. data/examples/blog/run.rb +1 -2
  17. data/examples/no_xsl_blog/root/index.xhtml +2 -2
  18. data/examples/no_xsl_blog/root/recent_posts.xhtml +1 -1
  19. data/examples/why_wiki/run.rb +19 -19
  20. data/lib/nitro.rb +2 -21
  21. data/lib/nitro/adapters/webrick.rb +19 -3
  22. data/lib/nitro/context.rb +15 -1
  23. data/lib/nitro/controller.rb +84 -49
  24. data/lib/nitro/dispatcher.rb +30 -6
  25. data/lib/nitro/markup.rb +4 -2
  26. data/lib/nitro/render.rb +15 -11
  27. data/lib/nitro/routing.rb +33 -0
  28. data/lib/nitro/runner.rb +38 -3
  29. data/lib/nitro/scaffold.rb +7 -4
  30. data/lib/nitro/shaders.rb +11 -4
  31. data/lib/nitro/template.rb +140 -0
  32. data/lib/og.rb +25 -11
  33. data/lib/og/adapter.rb +141 -7
  34. data/lib/og/adapters/mysql.rb +41 -3
  35. data/lib/og/adapters/oracle.rb +4 -3
  36. data/lib/og/adapters/psql.rb +3 -3
  37. data/lib/og/adapters/sqlite.rb +3 -3
  38. data/lib/og/connection.rb +5 -1
  39. data/lib/og/database.rb +26 -12
  40. data/lib/og/enchant.rb +50 -16
  41. data/lib/og/meta.rb +15 -15
  42. data/lib/og/observer.rb +53 -0
  43. data/test/glue/tc_property_type_checking.rb +3 -0
  44. data/test/nitro/tc_controller.rb +1 -1
  45. data/test/nitro/tc_dispatcher.rb +1 -1
  46. data/test/nitro/tc_template.rb +32 -0
  47. data/test/og/tc_many_to_many.rb +62 -0
  48. data/test/og/tc_observer.rb +85 -0
  49. data/test/tc_og.rb +16 -2
  50. metadata +12 -14
  51. data/bin/cluster +0 -218
  52. data/examples/why_wiki/wiki.yml +0 -6
  53. data/examples/wiki.yml +0 -1
  54. data/lib/nitro/ui/select.rb +0 -40
  55. data/lib/nitro/ui/sitemap.rb +0 -183
  56. data/test/nitro/ui/tc_sitemap.rb +0 -37
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 266 2005-02-28 14:50:48Z gmosx $
3
+ # $Id: og.rb 270 2005-03-07 17:52:16Z gmosx $
4
4
 
5
5
  require 'glue'
6
6
  require 'glue/logger'
@@ -15,7 +15,7 @@ require 'glue/validation'
15
15
  # Og (ObjectGraph) is an efficient, yet simple Object-Relational
16
16
  # mapping library.
17
17
  #
18
- # == Features
18
+ # === Features
19
19
  #
20
20
  # The library provides the following features:
21
21
  #
@@ -30,12 +30,13 @@ require 'glue/validation'
30
30
  # + Thread safety.
31
31
  # + SQL transactions.
32
32
  # + Lifecycle callbacks.
33
+ # + Lifecycle observers.
33
34
  # + Transparent support for cascading deletes for all backends.
34
35
  # + Hierarchical structures (preorder traversal, materialized paths)
35
36
  # + Works safely as part of distributed application.
36
37
  # + Simple implementation.
37
38
  #
38
- # == Meta language
39
+ # === Meta language
39
40
  #
40
41
  # primary_key :pid (NOT IMPLEMENTED)
41
42
  # name_key :name (NOT IMPLEMENTED)
@@ -44,13 +45,24 @@ require 'glue/validation'
44
45
  # many_to_many Role, :roles
45
46
  # sql_index :pid
46
47
  #
47
- # == Design
48
+ # === Property Metadata
49
+ #
50
+ # Og defines, reserves and uses the following property
51
+ # metadata types:
52
+ #
53
+ # [+:sql_index+]
54
+ # Create an sql index for this property.
55
+ #
56
+ # [+:unique+]
57
+ # This value of the property must be unique.
58
+ #
59
+ # [+:name_key+]
60
+ # This property is used as name-key.
61
+ #
62
+ # === Design
48
63
  #
49
64
  # Keep the main classes backend agnostic.
50
- #--
51
- # Try to make the methods work with oids. Do NOT implement descendants
52
- # use a root id (rid).
53
- #++
65
+ #
54
66
  # For class ids we use the name instead of a hash. Class ids are
55
67
  # typically not used in querys, they are stored for completeness.
56
68
  # If we store a hash we cannot reclaim the class thus invalidating
@@ -68,8 +80,10 @@ require 'glue/validation'
68
80
  # The og.xxx methods are more flexible and allow you to use
69
81
  # multiple databases for example.
70
82
  #
71
- # == Managed Objects Lifecycle Callbacks
83
+ # === Managed Objects Lifecycle Callbacks
72
84
  #
85
+ # * og_pre_read
86
+ # * og_post_read
73
87
  # * og_pre_insert
74
88
  # * og_post_insert
75
89
  # * og_pre_update
@@ -81,7 +95,7 @@ require 'glue/validation'
81
95
  # A class level callback is used for delete because typically you call
82
96
  # delete with an oid and not an object to avoid a deserialization.
83
97
  #
84
- # == Future
98
+ # === Future
85
99
  #
86
100
  # * Support prepared statements (pgsql)
87
101
  # * Support stored procedures (pgsql)
@@ -97,7 +111,7 @@ module Og
97
111
 
98
112
  # The version.
99
113
 
100
- Version = '0.11.0'
114
+ Version = '0.12.0'
101
115
 
102
116
  # Library path.
103
117
 
data/lib/og/adapter.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # * George Moschovitis <gm@navel.gr>
2
2
  # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: adapter.rb 266 2005-02-28 14:50:48Z gmosx $
3
+ # $Id: adapter.rb 270 2005-03-07 17:52:16Z gmosx $
4
4
 
5
5
  require 'yaml'
6
6
  require 'singleton'
@@ -20,6 +20,11 @@ class Adapter
20
20
  # A mapping between Ruby and backend Datastore types.
21
21
 
22
22
  attr_accessor :typemap
23
+
24
+ # A map for casting Ruby types to SQL safe textual
25
+ # representations.
26
+
27
+ attr_accessor :typecast
23
28
 
24
29
  # Lookup the adapter instance from the adapter name.
25
30
 
@@ -29,6 +34,9 @@ class Adapter
29
34
  end
30
35
 
31
36
  def initialize
37
+ # The default mappings, should be valid for most
38
+ # RDBMS.
39
+
32
40
  @typemap = {
33
41
  Integer => 'integer',
34
42
  Fixnum => 'integer',
@@ -41,6 +49,19 @@ class Adapter
41
49
  Array => 'text',
42
50
  Hash => 'text'
43
51
  }
52
+
53
+ # The :s: is a marker that will be replaced with the
54
+ # actual value to be casted. The default parameter of
55
+ # the Hash handles all other types (Object, Array, etc)
56
+
57
+ @typecast = Hash.new("'#\{#{self.class}.escape(:s:.to_yaml)\}'").update(
58
+ Integer => "\#\{:s:\}",
59
+ Float => "\#\{:s:\}",
60
+ String => "'#\{#{self.class}.escape(:s:)\}'",
61
+ Time => "'#\{#{self.class}.timestamp(:s:)\}'",
62
+ Date => "'#\{#{self.class}.date(:s:)\}'",
63
+ TrueClass => "#\{:s: ? \"'t'\" : 'NULL' \}"
64
+ )
44
65
  end
45
66
 
46
67
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -105,7 +126,7 @@ class Adapter
105
126
  # Create a new connection to the backend.
106
127
 
107
128
  def new_connection(db)
108
- return Og::Connection.new(db)
129
+ return Connection.new(db)
109
130
  end
110
131
 
111
132
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -145,8 +166,8 @@ class Adapter
145
166
  # You may want to override this method to map an existing
146
167
  # database schema using Og.
147
168
 
148
- def self.join_table(klass1, klass2)
149
- "og_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}"
169
+ def self.join_table(klass1, klass2, field)
170
+ "og_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}_#{field}"
150
171
  end
151
172
 
152
173
  # Return an sql string evaluator for the property.
@@ -222,7 +243,10 @@ class Adapter
222
243
  if default = p.meta[:default]
223
244
  field << " DEFAULT #{default.inspect} NOT NULL"
224
245
  end
225
-
246
+
247
+ # set unique
248
+ field << " UNIQUE" if p.meta[:unique]
249
+
226
250
  # attach extra sql
227
251
  if extra_sql = p.meta[:extra_sql]
228
252
  field << " #{extra_sql}"
@@ -266,9 +290,19 @@ class Adapter
266
290
  end
267
291
 
268
292
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
269
- # :section: Managed object enchant methods
293
+ # :section: Precompile lifecycle methods.
270
294
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
271
295
 
296
+ # Precompile some code that gets executed all the time.
297
+ # Deletion code is not precompiled, because it is not used
298
+ # as frequently.
299
+
300
+ def eval_lifecycle_methods(klass, db)
301
+ eval_og_insert(klass, db)
302
+ eval_og_update(klass, db)
303
+ eval_og_read(klass, db)
304
+ end
305
+
272
306
  # Generate the property for oid.
273
307
 
274
308
  def eval_og_oid(klass)
@@ -281,6 +315,9 @@ class Adapter
281
315
  # The generated code sets the oid when inserting!
282
316
 
283
317
  def eval_og_insert(klass, db)
318
+
319
+ # Attach object callbacks.
320
+
284
321
  if klass.instance_methods.include?('og_pre_insert')
285
322
  pre_cb = 'og_pre_insert(conn);'
286
323
  else
@@ -300,7 +337,36 @@ class Adapter
300
337
  if klass.instance_methods.include?('og_post_insert_update')
301
338
  post_cb << 'og_post_insert_update(conn);'
302
339
  end
303
-
340
+
341
+ # Attach observers.
342
+
343
+ if observers = klass.__meta[:og_observers]
344
+ observers.each_with_index do |o, idx|
345
+ if o.is_a?(Class)
346
+ obs = "#{o}.instance"
347
+ o = o.instance
348
+ else
349
+ obs = "self.class.__meta[:og_observers][#{idx}]"
350
+ end
351
+
352
+ if o.respond_to?(:og_pre_insert)
353
+ pre_cb << "#{obs}.og_pre_insert(conn, self);"
354
+ end
355
+
356
+ if o.respond_to?(:og_post_insert)
357
+ post_cb << "#{obs}.og_post_insert(conn, self);"
358
+ end
359
+
360
+ if o.respond_to?(:og_pre_insert_update)
361
+ pre_cb << "#{obs}.og_pre_insert_update(conn, self);"
362
+ end
363
+
364
+ if o.respond_to?(:og_post_insert_update)
365
+ post_cb << "#{obs}.og_post_insert_update(conn, self);"
366
+ end
367
+ end
368
+ end
369
+
304
370
  klass.class_eval %{
305
371
  def og_insert(conn)
306
372
  #{insert_code(klass, db, pre_cb, post_cb)}
@@ -320,6 +386,8 @@ class Adapter
320
386
 
321
387
  sql = "UPDATE #{klass::DBTABLE} SET #{updates.join(', ')} WHERE oid=#\{@oid\}"
322
388
 
389
+ # Attach object callbacks.
390
+
323
391
  if klass.instance_methods.include?('og_pre_update')
324
392
  pre_cb = 'og_pre_update(conn);'
325
393
  else
@@ -340,6 +408,35 @@ class Adapter
340
408
  post_cb << 'og_post_insert_update(conn);'
341
409
  end
342
410
 
411
+ # Attach observers.
412
+
413
+ if observers = klass.__meta[:og_observers]
414
+ observers.each_with_index do |o, idx|
415
+ if o.is_a?(Class)
416
+ obs = "#{o}.instance"
417
+ o = o.instance
418
+ else
419
+ obs = "self.class.__meta[:og_observers][#{idx}]"
420
+ end
421
+
422
+ if o.respond_to?(:og_pre_update)
423
+ pre_cb << "#{obs}.og_pre_update(conn, self);"
424
+ end
425
+
426
+ if o.respond_to?(:og_post_update)
427
+ post_cb << "#{obs}.og_post_update(conn, self);"
428
+ end
429
+
430
+ if o.respond_to?(:og_pre_insert_update)
431
+ pre_cb << "#{obs}.og_pre_insert_update(conn, self);"
432
+ end
433
+
434
+ if o.respond_to?(:og_post_insert_update)
435
+ post_cb << "#{obs}.og_post_insert_update(conn, self);"
436
+ end
437
+ end
438
+ end
439
+
343
440
  klass.class_eval %{
344
441
  def og_update(conn)
345
442
  #{pre_cb}
@@ -367,9 +464,46 @@ class Adapter
367
464
  end
368
465
  end
369
466
 
467
+ # Attach object callbacks.
468
+
469
+ if klass.instance_methods.include?('og_pre_read')
470
+ pre_cb = 'og_pre_read(conn);'
471
+ else
472
+ pre_cb = ''
473
+ end
474
+
475
+ if klass.instance_methods.include?('og_post_read')
476
+ post_cb = 'og_post_read(conn);'
477
+ else
478
+ post_cb = ''
479
+ end
480
+
481
+ # Attach observers.
482
+
483
+ if observers = klass.__meta[:og_observers]
484
+ observers.each_with_index do |o, idx|
485
+ if o.is_a?(Class)
486
+ obs = "#{o}.instance"
487
+ o = o.instance
488
+ else
489
+ obs = "self.class.__meta[:og_observers][#{idx}]"
490
+ end
491
+
492
+ if o.respond_to?(:og_pre_read)
493
+ pre_cb << "#{obs}.og_pre_read(conn, self);"
494
+ end
495
+
496
+ if o.respond_to?(:og_post_read)
497
+ post_cb << "#{obs}.og_post_read(conn, self);"
498
+ end
499
+ end
500
+ end
501
+
370
502
  klass.class_eval %{
371
503
  def og_read(res, tuple = nil)
504
+ #{pre_cb}
372
505
  #{code.join('; ')}
506
+ #{post_cb}
373
507
  end
374
508
  }
375
509
  end
@@ -1,6 +1,6 @@
1
1
  # * George Moschovitis <gm@navel.gr>
2
2
  # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: mysql.rb 266 2005-02-28 14:50:48Z gmosx $
3
+ # $Id: mysql.rb 270 2005-03-07 17:52:16Z gmosx $
4
4
 
5
5
  require 'mysql'
6
6
 
@@ -19,6 +19,7 @@ class MysqlAdapter < Adapter
19
19
  def initialize
20
20
  super
21
21
  @typemap.update(TrueClass => 'tinyint')
22
+ @typecast.update(TrueClass => "#\{:s: ? \"1\" : 'NULL' \}")
22
23
  end
23
24
 
24
25
  def self.escape(str)
@@ -118,6 +119,43 @@ class MysqlAdapter < Adapter
118
119
  res.free if res
119
120
  end
120
121
 
122
+ def create_fields(klass)
123
+ fields = []
124
+
125
+ klass.__props.each do |p|
126
+ klass.sql_index(p.symbol) if p.meta[:sql_index]
127
+
128
+ field = "#{p.symbol}"
129
+
130
+ if p.meta and p.meta[:sql]
131
+ field << " #{p.meta[:sql]}"
132
+ else
133
+ field << " #{@typemap[p.klass]}"
134
+
135
+ if p.meta
136
+ # set default value (gmosx: not that useful in the
137
+ # current implementation).
138
+ if default = p.meta[:default]
139
+ field << " DEFAULT #{default.inspect} NOT NULL"
140
+ end
141
+
142
+ # set unique
143
+ # FIXME: correctly handle UNIQUE constrain.
144
+ # field << " UNIQUE" if p.meta[:unique]
145
+
146
+ # attach extra sql
147
+ if extra_sql = p.meta[:extra_sql]
148
+ field << " #{extra_sql}"
149
+ end
150
+ end
151
+ end
152
+
153
+ fields << field
154
+ end
155
+
156
+ return fields
157
+ end
158
+
121
159
  def create_table(klass, db)
122
160
  conn = db.get_connection
123
161
 
@@ -165,11 +203,11 @@ class MysqlAdapter < Adapter
165
203
  if klass.__meta and joins = klass.__meta[:sql_join]
166
204
  for data in joins
167
205
  # the class to join to and some options.
168
- join_class, options = *data
206
+ join_name, join_class, options = *data
169
207
 
170
208
  # gmosx: dont use DBTABLE here, perhaps the join class
171
209
  # is not managed yet.
172
- join_table = "#{self.class.join_table(klass, join_class)}"
210
+ join_table = "#{self.class.join_table(klass, join_class, join_name)}"
173
211
  join_src = "#{self.class.encode(klass)}_oid"
174
212
  join_dst = "#{self.class.encode(join_class)}_oid"
175
213
  begin
@@ -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 266 2005-02-28 14:50:48Z gmosx $
4
+ # $Id: oracle.rb 270 2005-03-07 17:52:16Z gmosx $
5
5
 
6
6
  require 'oracle'
7
7
 
@@ -28,6 +28,7 @@ class OracleAdapter < Adapter
28
28
  Array => 'varchar2(1024)',
29
29
  Hash => 'varchar2(1024)'
30
30
  )
31
+ @typecast.update(TrueClass => "#\{:s: ? \"1\" : 'NULL' \}")
31
32
  end
32
33
 
33
34
  def self.timestamp(time = Time.now)
@@ -190,11 +191,11 @@ class OracleAdapter < Adapter
190
191
  if klass.__meta and joins = klass.__meta[:sql_join]
191
192
  for data in joins
192
193
  # the class to join to and some options.
193
- join_class, options = *data
194
+ join_name, join_class, options = *data
194
195
 
195
196
  # gmosx: dont use DBTABLE here, perhaps the join class
196
197
  # is not managed yet.
197
- join_table = "#{self.class.join_table(klass, join_class)}"
198
+ join_table = "#{self.class.join_table(klass, join_class, join_name)}"
198
199
  join_src = "#{self.class.encode(klass)}_oid"
199
200
  join_dst = "#{self.class.encode(join_class)}_oid"
200
201
  begin
@@ -1,6 +1,6 @@
1
1
  # * George Moschovitis <gm@navel.gr>
2
2
  # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: psql.rb 266 2005-02-28 14:50:48Z gmosx $
3
+ # $Id: psql.rb 270 2005-03-07 17:52:16Z gmosx $
4
4
 
5
5
  require 'postgres'
6
6
 
@@ -137,11 +137,11 @@ class PsqlAdapter < Adapter
137
137
  if klass.__meta and joins = klass.__meta[:sql_join]
138
138
  for data in joins
139
139
  # the class to join to and some options.
140
- join_class, options = *data
140
+ join_name, join_class, options = *data
141
141
 
142
142
  # gmosx: dont use DBTABLE here, perhaps the join class
143
143
  # is not managed yet.
144
- join_table = "#{self.class.join_table(klass, join_class)}"
144
+ join_table = "#{self.class.join_table(klass, join_class, join_name)}"
145
145
  join_src = "#{self.class.encode(klass)}_oid"
146
146
  join_dst = "#{self.class.encode(join_class)}_oid"
147
147
  begin