nitro 0.22.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/CHANGELOG +109 -1250
  2. data/INSTALL +3 -2
  3. data/README +5 -4
  4. data/Rakefile +1 -1
  5. data/bin/nitrogen +1 -1
  6. data/doc/AUTHORS +20 -6
  7. data/doc/CHANGELOG.3 +1314 -0
  8. data/doc/RELEASES +90 -0
  9. data/lib/nitro.rb +6 -17
  10. data/lib/nitro/adapter/cgi.rb +11 -0
  11. data/lib/nitro/adapter/webrick.rb +7 -1
  12. data/lib/nitro/caching/stores.rb +4 -6
  13. data/lib/nitro/compiler.rb +1 -1
  14. data/lib/nitro/compiler/errors.rb +1 -1
  15. data/lib/nitro/context.rb +11 -4
  16. data/lib/nitro/controller.rb +0 -3
  17. data/lib/nitro/cookie.rb +4 -3
  18. data/lib/nitro/dispatcher.rb +7 -1
  19. data/lib/nitro/dispatcher/nice.rb +6 -1
  20. data/lib/nitro/element.rb +2 -2
  21. data/lib/nitro/mixin/benchmark.rb +16 -0
  22. data/lib/nitro/mixin/form.rb +171 -55
  23. data/lib/nitro/mixin/rss.rb +1 -1
  24. data/lib/nitro/mixin/xhtml.rb +91 -4
  25. data/lib/nitro/render.rb +25 -6
  26. data/lib/nitro/request.rb +13 -3
  27. data/lib/nitro/scaffold.rb +91 -68
  28. data/lib/nitro/scaffold/relations.rb +54 -0
  29. data/lib/nitro/server.rb +8 -5
  30. data/lib/nitro/server/runner.rb +4 -3
  31. data/lib/nitro/service.rb +3 -1
  32. data/lib/nitro/service/xmlrpc.rb +1 -1
  33. data/lib/nitro/session.rb +69 -51
  34. data/lib/nitro/session/drb.rb +5 -7
  35. data/lib/nitro/session/drbserver.rb +4 -6
  36. data/lib/nitro/session/memory.rb +4 -6
  37. data/lib/part/admin.rb +6 -0
  38. data/lib/part/admin/controller.rb +24 -0
  39. data/lib/part/admin/skin.rb +21 -0
  40. data/lib/part/admin/template/index.xhtml +9 -0
  41. data/proto/public/js/cookies.js +122 -0
  42. data/proto/public/scaffold/edit.xhtml +4 -0
  43. data/proto/public/scaffold/form.xhtml +7 -0
  44. data/proto/public/scaffold/list.xhtml +15 -0
  45. data/proto/public/scaffold/new.xhtml +4 -0
  46. data/proto/public/scaffold/view.xhtml +0 -0
  47. data/proto/script/benchmark +19 -0
  48. data/test/nitro/adapter/tc_cgi.rb +32 -2
  49. data/test/nitro/mixin/tc_xhtml.rb +6 -0
  50. data/test/nitro/tc_dispatcher.rb +0 -17
  51. data/test/nitro/tc_render.rb +58 -0
  52. data/test/nitro/tc_server.rb +2 -0
  53. data/test/nitro/tc_session.rb +16 -0
  54. metadata +104 -85
@@ -1,3 +1,93 @@
1
+ == Version 0.23.0
2
+
3
+ The summer vacations are over and there is a brand new Nitro
4
+ release. There is a preview of the new Scaffolder (also handles
5
+ Og relations), support for Tagging (folksonomy), lots of small
6
+ features and improvements and many bug fixes. Additionally, the
7
+ code has been restructured to utilize the excellent Nano and Mega
8
+ support libraries.
9
+
10
+ Most notable additions:
11
+
12
+ * Scaffolding reloaded. The scaffolding infrastructure is
13
+ reimplemented to generate more flexible code. The automatically
14
+ generated forms allow for visualization and editing of
15
+ Og relations such as HasMany and BelongsTo.
16
+
17
+ For example when rendering a BelongsTo relation all possible
18
+ parents are presented with a select element. When rendering a
19
+ HasMany relation a list of all children is presented.
20
+
21
+ Moreover, an experimental admin component is provided. Just add the
22
+ line:
23
+
24
+ require 'part/admin'
25
+
26
+ and surf
27
+
28
+ http://www.mysite.com/admin
29
+
30
+ To enter a simple administration screen.
31
+
32
+ WARNING: This feature is considered a preview and will be
33
+ improved in a future version.
34
+
35
+ * Major cleanup in the Glue subproject. Some files are moved
36
+ to the nano/mega project. Nano/Mega is now used throughout
37
+ Nitro and really makes development so much easier.
38
+
39
+ * Introduced Og Taggable mixin. It was never easier to add
40
+ tagging to your application.
41
+
42
+ class Article
43
+ include Og::Taggable
44
+ ..
45
+ end
46
+
47
+ article.tag('navel', 'gmosx', 'nitro')
48
+ article.tags
49
+ article.tag_names
50
+ Article.find_with_tags('navel', 'gmosx')
51
+ Article.find_with_any_tag('name', 'gmosx')
52
+
53
+ t = Article::Tag.find_by_name('ruby')
54
+ t.articles
55
+ t.articles.count
56
+
57
+ For an example usage of this Mixin, consult the Spark sources.
58
+
59
+ * Added support for relative and absolute URLs in redirects
60
+ and renders. This feature simplifies the creation of reusable
61
+ components.
62
+
63
+ * Support for assigning compound objects from the request. Here
64
+ is an example:
65
+
66
+ class Article
67
+ property :title, String
68
+ property :body, String
69
+ end
70
+
71
+ <form>
72
+ <input type="text" name="article.title" />
73
+ <input type="text" name="article.body" />
74
+ </form>
75
+
76
+ article = request.assign('article')
77
+
78
+ Alternatively you can use the article[title] article[body]
79
+ notation.
80
+
81
+ * Added simple Benchmarking mixin.
82
+
83
+ * Added support for 'evolving' a single Og managed class. Useful
84
+ when you are in development mode and change your schema.
85
+
86
+ * Added support for session garbage collection.
87
+
88
+ * Many many small bug fixes in Og and Nitro.
89
+
90
+
1
91
  == Version 0.22.0
2
92
 
3
93
  A snapshot of the latest developments. Many requested features
@@ -1,21 +1,12 @@
1
1
  # = Nitro
2
2
  #
3
- # Nitro is an efficient, yet simple engine for developing
4
- # professional Web Applications using Ruby and Javascript.
5
- # Nitro provides a robust infrastructure for scalable
6
- # applications that can be distributed over a server
7
- # cluster. However, Nitro can also power simple web
8
- # applications for deployment on intranets or desktops.
9
- #
10
- # Nitro integrates the powerful Og Object-Relational mapping
11
- # library.
12
- #
13
- # Copyright (c) 2004-2005, George Moschovitis (http://www.gmosx.com)
14
3
  # Copyright (c) 2004-2005, Navel Ltd (http://www.navel.gr)
4
+ # Copyright (c) 2004-2005, George Moschovitis (http://www.gmosx.com)
15
5
  #
16
- # Nitro is copyrighted free software created and maintained by
17
- # George Moschovitis (mailto:gm@navel.gr) and released under the
18
- # standard BSD Licence. For details consult the file doc/LICENCE.
6
+ # Nitro (http://www.nitrohq.com) is copyrighted free software
7
+ # created and maintained by George Moschovitis (mailto:gm@navel.gr)
8
+ # and released under the standard BSD Licence. For details
9
+ # consult the file doc/LICENCE.
19
10
 
20
11
  require 'glue'
21
12
  require 'glue/logger'
@@ -25,7 +16,7 @@ module Nitro
25
16
 
26
17
  # The version.
27
18
 
28
- Version = '0.22.0'
19
+ Version = '0.23.0'
29
20
 
30
21
  # Library path.
31
22
 
@@ -75,5 +66,3 @@ module Nitro
75
66
  end
76
67
 
77
68
  end
78
-
79
- # * George Moschovitis <gm@navel.gr>
@@ -103,6 +103,9 @@ class CgiUtils
103
103
  #
104
104
  # Parameters in the form xxx[] are converted
105
105
  # to arrays.
106
+ #
107
+ # Use the field.attr or field[attr] notation to pass
108
+ # compound objects.
106
109
 
107
110
  def self.parse_query_string(query_string)
108
111
  params = {}
@@ -117,12 +120,20 @@ class CgiUtils
117
120
  val = CGI.unescape(val) unless val.nil?
118
121
 
119
122
  if key =~ /(.*)\[\]$/
123
+ # Multiple values, for example a checkbox collection.
124
+ # Stored as an array.
120
125
  if params.has_key?($1)
121
126
  params[$1] << val
122
127
  else
123
128
  params[$1] = [val]
124
129
  end
130
+ elsif key =~ /(.*)\[(.*)\]$/ or key =~ /(.*)\.(.*)$/
131
+ # A compound object with attributes.
132
+ # Stored as a Hash.
133
+ params[$1] ||= {}
134
+ params[$1][$2] = val
125
135
  else
136
+ # Standard single valued parameter.
126
137
  params[key] = val.nil? ? nil : val
127
138
  end
128
139
  end
@@ -26,7 +26,11 @@ class Webrick
26
26
  else
27
27
  wblog = STDERR
28
28
  end
29
- @webrick = WEBrick::HTTPServer.new(
29
+
30
+ webrick_options = server.options.dup
31
+ require 'webrick/https' if webrick_options[:SSLEnable]
32
+
33
+ webrick_options.update(
30
34
  :BindAddress => server.address,
31
35
  :Port => server.port,
32
36
  :DocumentRoot => server.public_root,
@@ -35,6 +39,7 @@ class Webrick
35
39
  [wblog, WEBrick::AccessLog::REFERER_LOG_FORMAT]
36
40
  ]
37
41
  )
42
+ @webrick = WEBrick::HTTPServer.new(webrick_options)
38
43
 
39
44
  trap('INT') { @webrick.shutdown }
40
45
 
@@ -159,3 +164,4 @@ end
159
164
  end
160
165
 
161
166
  # * George Moschovitis <gm@navel.gr>
167
+ # * Guillaume Pierronnet <guillaume.pierronnet@laposte.net>
@@ -1,10 +1,6 @@
1
- # * George Moschovitis <gm@navel.gr>
2
- # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: stores.rb 182 2005-07-22 10:07:50Z gmosx $
4
-
5
1
  require 'fileutils'
6
2
 
7
- require 'glue/hash'
3
+ require 'mega/synchash'
8
4
 
9
5
  module Nitro
10
6
 
@@ -14,7 +10,7 @@ module Caching
14
10
 
15
11
  # Cached fragments are stored in memory.
16
12
 
17
- class MemoryStore < Glue::SafeHash
13
+ class MemoryStore < SyncHash
18
14
 
19
15
  def read(name, options = {})
20
16
  self[name]
@@ -82,3 +78,5 @@ module Caching
82
78
  end
83
79
 
84
80
  end
81
+
82
+ # * George Moschovitis <gm@navel.gr>
@@ -1,4 +1,4 @@
1
- require 'facet/object/singleton_class'
1
+ require 'nano/object/singleton_class'
2
2
 
3
3
  require 'glue/template'
4
4
  require 'nitro/compiler/errors'
@@ -1,4 +1,4 @@
1
- require 'facet/string/demodulize'
1
+ require 'mega/orm_support'
2
2
 
3
3
  module Nitro
4
4
 
@@ -1,3 +1,5 @@
1
+ require 'nano/object/assign_with'
2
+
1
3
  require 'nitro/request'
2
4
  require 'nitro/response'
3
5
  require 'nitro/render'
@@ -79,23 +81,28 @@ class Context
79
81
  EXCLUDED_PARAMETERS = %w{ oid name }
80
82
 
81
83
  def fill(obj, name = nil)
82
- # if an object is passed create an instance.
84
+ # If an class is passed create an instance.
83
85
  obj = obj.new if obj.is_a?(Class)
84
86
 
85
87
  @params.each do |param, val|
86
88
  begin
87
89
  # gmosx: DO NOT escape by default !!!
88
90
  if not EXCLUDED_PARAMETERS.include?(param)
89
- obj.send("__force_#{param}", val)
91
+ if val.is_a? Hash
92
+ obj.send("__force_hash_#{param}", val)
93
+ else
94
+ obj.send("__force_#{param}", val)
95
+ end
90
96
  end
91
- rescue NameError
97
+ rescue NameError => ex
92
98
  next
93
99
  end
94
100
  end
95
-
101
+
96
102
  return obj
97
103
  end
98
104
  alias_method :populate, :fill
105
+ alias_method :assign, :fill
99
106
  end
100
107
 
101
108
  end
@@ -217,6 +217,3 @@ class SimpleController < Controller
217
217
  end
218
218
 
219
219
  end
220
-
221
- # * George Moschovitis <gm@navel.gr>
222
- # * James Britt <james_b@neurogami.com>
@@ -8,13 +8,14 @@ class Cookie
8
8
  attr_accessor :domain, :path, :secure
9
9
  attr_accessor :comment, :max_age
10
10
 
11
- def initialize(name, value)
11
+ def initialize(name = nil, value = nil, expires = nil)
12
12
  @name = name
13
13
  @value = value
14
+ self.expires = expires
14
15
  @version = 0 # Netscape Cookie
15
16
  @path = '/' # gmosx: KEEP this!
16
- @domain = @secure = @comment = @max_age =
17
- @expires = @comment_url = @discard = @port = nil
17
+ @domain = @secure = @comment = @max_age = nil
18
+ @comment_url = @discard = @port = nil
18
19
  end
19
20
 
20
21
  def expires=(t)
@@ -1,4 +1,4 @@
1
- require 'facet/object/special_class'
1
+ require 'nano/object/singleton_class'
2
2
 
3
3
  require 'nitro/controller'
4
4
  require 'nitro/routing'
@@ -86,6 +86,12 @@ class Dispatcher
86
86
  end
87
87
 
88
88
  auto_mixin(c)
89
+
90
+ # Perform mount-time initialization of the controller.
91
+
92
+ if c.respond_to? :mounted
93
+ c.mounted
94
+ end
89
95
 
90
96
  # Try to setup a template_root if none is defined:
91
97
 
@@ -8,6 +8,9 @@ class Dispatcher
8
8
 
9
9
  # An alternative dispatching algorithm that handles
10
10
  # implicit nice urls. Subdirectories are not supported.
11
+ #
12
+ # Returns the dispatcher class, the action name and the
13
+ # base url. For the root path, the base url is nil.
11
14
 
12
15
  def dispatch(path, context)
13
16
  path = route(path, context)
@@ -18,7 +21,7 @@ class Dispatcher
18
21
  if klass = controller_class_for("/#{parts.first}")
19
22
  base = "/#{parts.shift}"
20
23
  else
21
- base = ROOT
24
+ base = nil
22
25
  klass = controller_class_for(ROOT)
23
26
  end
24
27
 
@@ -30,6 +33,8 @@ class Dispatcher
30
33
  context.headers['QUERY_STRING'] = "#{parts.join(';')};#{context.headers['QUERY_STRING']}"
31
34
  end
32
35
 
36
+ base = nil if base == ROOT
37
+
33
38
  return klass, "#{action}_action", base
34
39
  end
35
40
  end
@@ -1,8 +1,8 @@
1
1
  require 'rexml/document'
2
2
  require 'rexml/streamlistener'
3
3
 
4
- require 'facet/string/capitalized%3F'
5
- require 'facet/string/camelize'
4
+ require 'nano/string/capitalized%3F'
5
+ require 'nano/string/camelize'
6
6
 
7
7
  require 'glue/configuration'
8
8
 
@@ -0,0 +1,16 @@
1
+ require 'benchmark'
2
+
3
+ module Nitro
4
+
5
+ module BenchmarkMixin
6
+
7
+ def benchmark(message = 'Benchmarking')
8
+ real = Benchmark.realtime { yield }
9
+ Logger.info "#{message}: time = #{'%.5f' % real} ms."
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+
16
+ # * George Moschovitis <gm@navel.gr>
@@ -1,88 +1,204 @@
1
- require 'glue/hash'
1
+ require 'nano/inflect'
2
2
 
3
3
  module Nitro
4
4
 
5
5
  # A collection of useful helpers for creating and manipulating
6
6
  # Forms.
7
+ #--
8
+ # FIXME: cleanup this file, move stuff to scaffold, allow
9
+ # overrides.
10
+ #++
7
11
 
8
12
  module FormMixin
9
13
 
10
- # Render a standard form for the given Entity (ie an
11
- # object with attribute metadata).
14
+ def self.included(base)
15
+ base.send :include, XhtmlMixin
16
+ end
17
+
18
+ private
19
+
20
+ # Render a standard form for the given Object. The object
21
+ # should include attribute metadata.
22
+ #--
23
+ # TODO: get info, for example localization mode from session,
24
+ # if this module is mixed in a Render.
25
+ #++
26
+
27
+ def form_for(obj, options = {})
28
+ method = options.fetch(:method, 'post')
29
+ action = options.fetch(:action, "save_#{obj.class.name.underscore}")
30
+ submit = options.fetch(:submit, 'Save')
31
+
32
+ action = "#{@base}/#{action}" unless action =~ /\//
33
+
34
+ str = %{<form action="#{action}" method="post">}
35
+ if obj.oid
36
+ str << %{
37
+ <input type="hidden" name="oid" value="#{obj.oid}" />}
38
+ end
39
+ str << %{
40
+ #{tags_for(obj, options)}
41
+ <br />
42
+ <input type="submit" value="#{submit}" /> or <a href="#@base/#{Scaffolding.class_to_list(obj.class)}">Cancel</a>
43
+ </form>
44
+ }
45
+ return str
46
+ end
47
+
48
+ # Render a standard form tags for the given Object. The object
49
+ # should include attribute metadata.
50
+ #
12
51
  # If show_all is false then apply field filtering.
13
52
  #
53
+ # For extra flexibility and to keep semantics this helper
54
+ # emits a <dl> structure. You can use CSS to style the
55
+ # list to fit your overal design.
56
+ #
14
57
  # Example:
15
58
  #
16
59
  # <p>
17
60
  # <form name="test">
18
- # #{form_for entry}
61
+ # #{tags_for entry}
19
62
  # </form>
20
63
  # </p>
21
- #--
22
- # TODO: get info, for example localization mode from session,
23
- # if this module is mixed in a Render.
24
- #++
25
64
 
26
- def form_for(obj, lc = nil, show_all = false)
27
- str = '<dl>'
65
+ def tags_for(obj, options = {})
66
+ str = prologue()
28
67
 
29
68
  for p in obj.class.properties
30
- unless show_all
31
- next if :oid == p.symbol
32
- end
33
-
34
- if p.klass.ancestors.include?(Integer) or
35
- p.klass.ancestors.include?(Float)
36
- str << %{
37
- <dt><label for="#{p.symbol}">#{p.symbol}</label></dt>
38
- <dd>
39
- <input type="text" id="#{p.symbol}" name="#{p.symbol}" value="#{obj.send(p.symbol)}" />
40
- </dd>
41
- }
42
- elsif p.klass.ancestors.include?(String)
43
- str << %{
44
- <dt><label for="#{p.symbol}">#{p.symbol}</label></dt>
45
- <dd>
46
- }
47
- val = obj.send(p.symbol)
48
- if :textarea == p.meta[:ui]
49
- str << %{
50
- <textarea id="#{p.symbol}" name="#{p.symbol}">#{val}</textarea>
51
- }
69
+ next if :oid == p.symbol unless options[:all]
70
+ ancestors = p.klass.ancestors
71
+ if ancestors.include?(Numeric)
72
+ unless p.meta[:relation]
73
+ str << field_tag(obj, p, options)
74
+ end
75
+ elsif ancestors.include?(String)
76
+ if p.metadata[:editor] == :textarea
77
+ str << textarea_tag(obj, p, options)
52
78
  else
53
- str << %{
54
- <input type="text" id="#{p.symbol}" name="#{p.symbol}" value="#{val}" />
55
- }
79
+ str << field_tag(obj, p, options)
56
80
  end
57
- str << %{
58
- </dd>
59
- }
60
- elsif p.klass.ancestors.include?(TrueClass)
61
- str << %{
62
- <dt><label for="#{p.symbol}">#{p.symbol}</label></dt>
81
+ elsif ancestors.include?(TrueClass)
82
+ str << checkbox_tag(obj, p, options)
83
+ elsif ancestors.include?(Time)
84
+ str << datetime_tag(obj, p, options)
85
+ end
86
+ end
87
+
88
+ for rel in obj.class.relations
89
+ case rel
90
+ when Og::BelongsTo
91
+ str << belongs_to_tag(obj, rel, options)
92
+ when Og::HasMany
93
+ str << has_many_tag(obj, rel, options)
94
+ end
95
+ end
96
+
97
+ str << epilogue()
98
+
99
+ return str
100
+ end
101
+
102
+ def belongs_to_tag(obj, rel, options)
103
+ entities = rel.target_class.all
104
+ labels = entities.map { |e| e.to_s }
105
+ values = entities.map { |e| e.oid }
106
+ element(
107
+ label(rel.name),
108
+ %{
109
+ <select id="#{rel.name}" name="#{rel.name}">
110
+ #{options(:labels => labels, :values => values, :selected => 1)}
111
+ </select>
112
+ }
113
+ )
114
+ end
115
+
116
+ def has_many_tag(obj, rel, options)
117
+ entities = obj.send(rel.target_plural_name) if obj.saved?
118
+ unless entities.empty?
119
+ str = entities.inject('') do |acc, e|
120
+ acc << "<tr><td>#{e.to_edit_link(@base)}</td></tr>"
121
+ end
122
+ str = "<table>#{str}</table>"
123
+ else
124
+ str = 'No entities found.<br /><br />'
125
+ end
126
+ element(
127
+ label(rel.name) + '&nbsp;<a href="#">Add</a>',
128
+ %{
129
+ #{str}
130
+ }
131
+ )
132
+ end
133
+
134
+ def field_tag(obj, p, options)
135
+ %{
136
+ <dt>#{label(p.symbol)}</dt>
63
137
  <dd>
64
- <input type="checkbox" id="#{p.symbol}" name="#{p.symbol}" />
138
+ <input type="text" id="#{p.symbol}" name="#{p.symbol}" value="#{obj.send(p.symbol)}" style="width: 250px" />
65
139
  </dd>
66
- }
67
- elsif p.klass.ancestors.include?(Time)
68
- str << %{
69
- <dt><label for="#{p.symbol}">#{p.symbol}</label></dt>
140
+ }
141
+ end
142
+
143
+ def textarea_tag(obj, p, options)
144
+ %{
145
+ <dt>#{label(p.symbol)}</dt>
70
146
  <dd>
71
- <input type="text" id="#{p.symbol}" name="#{p.symbol}" value="#{obj.send(p.symbol)}" />
147
+ <textarea id="#{p.symbol}" name="#{p.symbol}">#{obj.send(p.symbol)}</textarea>
72
148
  </dd>
73
- }
74
- end
75
- end
149
+ }
150
+ end
76
151
 
77
- str << %{
78
- </dl>}
152
+ def checkbox_tag(obj, p, options)
153
+ %{
154
+ <dt>
155
+ <input type="checkbox" id="#{p.symbol}" name="#{p.symbol}" />&nbsp;
156
+ #{label(p.symbol)}
157
+ </dt>
158
+ <dd>#{}</dd>
159
+ }
160
+ end
79
161
 
80
- self << str
162
+ def datetime_tag(obj, p, options)
163
+ element(
164
+ label(p.symbol),
165
+ datetime_select(obj.send(p.symbol), :name => p.symbol.to_s)
166
+ )
167
+ end
168
+
169
+ def label(sym)
170
+ %|<label for="#{sym}">#{sym.to_s.humanize}</label>|
171
+ end
172
+
173
+ # :section: Relations.
174
+
175
+
176
+ # :section: General formating methods. Override to customize.
177
+
178
+ # Emit form prologue.
179
+
180
+ def prologue
181
+ '<dl>'
182
+ end
183
+
184
+ # Emit form epilogue.
185
+
186
+ def epilogue
187
+ '</dl>'
81
188
  end
82
- alias_method :build_form, :form_for
83
189
 
190
+ # Emit a form element.
191
+
192
+ def element(label, element)
193
+ %{
194
+ <dt>#{label}</dt>
195
+ <dd>
196
+ #{element}
197
+ </dd>
198
+ }
199
+ end
84
200
  end
85
201
 
86
202
  end
87
203
 
88
- # * George Moschovitis
204
+ # * George Moschovitis <gm@navel.gr>