merb-core 0.9.8 → 0.9.9

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 (57) hide show
  1. data/CONTRIBUTORS +33 -0
  2. data/README +7 -3
  3. data/Rakefile +3 -3
  4. data/lib/merb-core.rb +165 -94
  5. data/lib/merb-core/bootloader.rb +469 -100
  6. data/lib/merb-core/config.rb +79 -3
  7. data/lib/merb-core/constants.rb +24 -2
  8. data/lib/merb-core/controller/abstract_controller.rb +172 -67
  9. data/lib/merb-core/controller/exceptions.rb +50 -6
  10. data/lib/merb-core/controller/merb_controller.rb +215 -108
  11. data/lib/merb-core/controller/mime.rb +36 -12
  12. data/lib/merb-core/controller/mixins/authentication.rb +52 -7
  13. data/lib/merb-core/controller/mixins/conditional_get.rb +14 -0
  14. data/lib/merb-core/controller/mixins/controller.rb +90 -58
  15. data/lib/merb-core/controller/mixins/render.rb +34 -10
  16. data/lib/merb-core/controller/mixins/responder.rb +40 -16
  17. data/lib/merb-core/controller/template.rb +37 -16
  18. data/lib/merb-core/core_ext/hash.rb +9 -0
  19. data/lib/merb-core/core_ext/kernel.rb +92 -41
  20. data/lib/merb-core/dispatch/dispatcher.rb +29 -45
  21. data/lib/merb-core/dispatch/request.rb +186 -82
  22. data/lib/merb-core/dispatch/router.rb +141 -53
  23. data/lib/merb-core/dispatch/router/behavior.rb +296 -139
  24. data/lib/merb-core/dispatch/router/resources.rb +51 -19
  25. data/lib/merb-core/dispatch/router/route.rb +76 -23
  26. data/lib/merb-core/dispatch/session.rb +80 -36
  27. data/lib/merb-core/dispatch/session/container.rb +31 -15
  28. data/lib/merb-core/dispatch/session/cookie.rb +51 -22
  29. data/lib/merb-core/dispatch/session/memcached.rb +10 -6
  30. data/lib/merb-core/dispatch/session/memory.rb +17 -5
  31. data/lib/merb-core/dispatch/session/store_container.rb +21 -9
  32. data/lib/merb-core/dispatch/worker.rb +16 -2
  33. data/lib/merb-core/gem_ext/erubis.rb +4 -0
  34. data/lib/merb-core/plugins.rb +13 -0
  35. data/lib/merb-core/rack.rb +1 -0
  36. data/lib/merb-core/rack/adapter.rb +1 -0
  37. data/lib/merb-core/rack/adapter/abstract.rb +95 -17
  38. data/lib/merb-core/rack/adapter/irb.rb +50 -5
  39. data/lib/merb-core/rack/application.rb +27 -5
  40. data/lib/merb-core/rack/handler/mongrel.rb +6 -6
  41. data/lib/merb-core/rack/helpers.rb +33 -0
  42. data/lib/merb-core/rack/middleware/conditional_get.rb +1 -1
  43. data/lib/merb-core/rack/middleware/path_prefix.rb +3 -3
  44. data/lib/merb-core/rack/middleware/static.rb +11 -7
  45. data/lib/merb-core/server.rb +134 -69
  46. data/lib/merb-core/tasks/gem_management.rb +153 -80
  47. data/lib/merb-core/tasks/merb_rake_helper.rb +12 -4
  48. data/lib/merb-core/tasks/stats.rake +1 -1
  49. data/lib/merb-core/test/helpers/mock_request_helper.rb +29 -22
  50. data/lib/merb-core/test/helpers/request_helper.rb +1 -1
  51. data/lib/merb-core/test/helpers/route_helper.rb +50 -4
  52. data/lib/merb-core/test/matchers/request_matchers.rb +2 -36
  53. data/lib/merb-core/test/matchers/view_matchers.rb +32 -22
  54. data/lib/merb-core/test/run_specs.rb +6 -5
  55. data/lib/merb-core/test/test_ext/rspec.rb +6 -19
  56. data/lib/merb-core/version.rb +1 -1
  57. metadata +5 -4
@@ -3,10 +3,24 @@ module Merb
3
3
 
4
4
  attr_accessor :thread
5
5
 
6
+ # ==== Returns
7
+ # Merb::Worker:: instance of a worker.
8
+ #
9
+ # @api private
10
+ def self.start
11
+ new
12
+ end
13
+
14
+ # Creates a new worker thread that loops over the work queue.
15
+ #
16
+ # @api private
6
17
  def initialize
7
18
  @thread = Thread.new { loop { process_queue } }
8
19
  end
9
20
 
21
+ # Processes tasks in the Merb::Dispatcher.work_queue.
22
+ #
23
+ # @api private
10
24
  def process_queue
11
25
  begin
12
26
  while blk = Merb::Dispatcher.work_queue.pop
@@ -21,8 +35,8 @@ module Merb
21
35
  rescue Exception => e
22
36
  Merb.logger.warn! %Q!Worker Thread Crashed with Exception:\n#{Merb.exception(e)}\nRestarting Worker Thread!
23
37
  retry
24
- end
38
+ end
25
39
  end
26
40
 
27
41
  end
28
- end
42
+ end
@@ -1,5 +1,9 @@
1
1
  require 'erubis'
2
2
  module Erubis
3
+ # This adds support for embedding the return value of a block call:
4
+ # <%= foo do %>...<% end =%>
5
+ #
6
+ # @api private
3
7
  module Basic::Converter
4
8
  def convert_input(src, input)
5
9
  pat = @pattern
@@ -2,10 +2,15 @@ module Merb
2
2
 
3
3
  module Plugins
4
4
 
5
+ # Returns the configuration settings hash for plugins. This is prepopulated from
6
+ # Merb.root / "config/plugins.yml" if it is present.
7
+ #
5
8
  # ==== Returns
6
9
  # Hash::
7
10
  # The configuration loaded from Merb.root / "config/plugins.yml" or, if
8
11
  # the load fails, an empty hash whose default value is another Hash.
12
+ #
13
+ # @api plugin
9
14
  def self.config
10
15
  @config ||= begin
11
16
  # this is so you can do Merb.plugins.config[:helpers][:awesome] = "bar"
@@ -25,12 +30,16 @@ module Merb
25
30
 
26
31
  # ==== Returns
27
32
  # Array(String):: All Rakefile load paths Merb uses for plugins.
33
+ #
34
+ # @api plugin
28
35
  def self.rakefiles
29
36
  Merb.rakefiles
30
37
  end
31
38
 
32
39
  # ==== Returns
33
40
  # Array(String):: All Generator load paths Merb uses for plugins.
41
+ #
42
+ # @api plugin
34
43
  def self.generators
35
44
  Merb.generators
36
45
  end
@@ -49,6 +58,8 @@ module Merb
49
58
  # if defined(Merb::Plugins)
50
59
  # Merb::Plugins.add_rakefiles "merb_sequel" / "merbtasks"
51
60
  # end
61
+ #
62
+ # @api plugin
52
63
  def self.add_rakefiles(*rakefiles)
53
64
  Merb.add_rakefiles(*rakefiles)
54
65
  end
@@ -60,6 +71,8 @@ module Merb
60
71
  #
61
72
  # This is the recommended way to register your plugin's generators
62
73
  # in Merb.
74
+ #
75
+ # @api plugin
63
76
  def self.add_generators(*generators)
64
77
  Merb.add_generators(*generators)
65
78
  end
@@ -23,5 +23,6 @@ module Merb
23
23
  autoload :ConditionalGet, 'merb-core/rack/middleware/conditional_get'
24
24
  autoload :Csrf, 'merb-core/rack/middleware/csrf'
25
25
  autoload :StreamWrapper, 'merb-core/rack/stream_wrapper'
26
+ autoload :Helpers, 'merb-core/rack/helpers'
26
27
  end # Rack
27
28
  end # Merb
@@ -5,6 +5,7 @@ module Merb
5
5
  class Adapter
6
6
 
7
7
  class << self
8
+ # Get a rack adapter by id.
8
9
  # ==== Parameters
9
10
  # id<String>:: The identifier of the Rack adapter class to retrieve.
10
11
  #
@@ -2,30 +2,83 @@ module Merb
2
2
  module Rack
3
3
  class AbstractAdapter
4
4
 
5
+ # This method is designed to be overridden in a rack adapter. It
6
+ # will be called to start a server created with the new_server method.
7
+ # This is called from the AbstractAdapter start method.
8
+ #
9
+ # @api plugin
10
+ # @overridable
11
+ def self.start_server
12
+ raise NotImplemented
13
+ end
14
+
15
+ # This method is designed to be overridden in a rack adapter. It will
16
+ # be called to create a new instance of the server for the adapter to
17
+ # start. The adapter should attempt to bind to a port at this point.
18
+ # This is called from the AbstractAdapter start method.
19
+ #
20
+ # ==== Parameters
21
+ # port<Integer>:: The port the server should listen on
22
+ #
23
+ # @api plugin
24
+ # @overridable
25
+ def self.new_server(port)
26
+ raise NotImplemented
27
+ end
28
+
29
+ # This method is designed to be overridden in a rack adapter. It will
30
+ # be called to stop the adapter server.
31
+ #
32
+ # ==== Parameters
33
+ # status<Integer>:: The exit status the adapter should exit with.
34
+ #
35
+ # ==== Returns
36
+ # Boolean:: True if the server was properly stopped.
37
+ #
38
+ # @api plugin
39
+ # @overridable
40
+ def self.stop(status)
41
+ raise NotImplemented
42
+ end
43
+
5
44
  # Spawn a new worker process at a port.
45
+ #
46
+ # ==== Parameters
47
+ # port<Integer>:: The port to start the worker process on.
48
+ #
49
+ # @api private
6
50
  def self.spawn_worker(port)
7
- child_pid = Kernel.fork
8
- start_at_port(port, @opts) unless child_pid
51
+ worker_pid = Kernel.fork
52
+ start_at_port(port, @opts) unless worker_pid
9
53
 
10
- # If we have a child_pid, we're in the parent. If we're
11
- throw(:new_worker) unless child_pid
54
+ # If we have a worker_pid, we're in the parent.
55
+ throw(:new_worker) unless worker_pid
12
56
 
13
- @pids[port] = child_pid
14
- $CHILDREN = @pids.values
57
+ @pids[port] = worker_pid
58
+ $WORKERS = @pids.values
15
59
  end
16
60
 
17
61
  # The main start method for bootloaders that support forking.
62
+ # This method launches the adapters which inherit using the
63
+ # new_server and start_server methods. This method should not
64
+ # be overridden in adapters which want to fork.
65
+ #
66
+ # ==== Parameters
67
+ # opts<Hash>:: A hash of options
68
+ # socket: the socket to bind to
69
+ # port: the port to bind to
70
+ # cluster: the number
71
+ #
72
+ # @api private
18
73
  def self.start(opts={})
19
74
  @opts = opts
20
- $CHILDREN ||= []
75
+ $WORKERS ||= []
21
76
  parent = nil
22
77
 
23
78
  @pids = {}
24
79
  port = (opts[:socket] || opts[:port]).to_i
25
80
  max_port = Merb::Config[:cluster] ? Merb::Config[:cluster] - 1 : 0
26
81
 
27
- Merb.logger.warn! "Cluster: #{max_port}"
28
-
29
82
  # If we only have a single merb, just start it up and dispense with
30
83
  # the spawner/worker setup.
31
84
  if max_port == 0
@@ -36,7 +89,7 @@ module Merb
36
89
  $0 = process_title(:spawner, port)
37
90
 
38
91
  # For each port, spawn a new worker. The parent will continue in
39
- # the loop, while the child will throw :new_worker and be booted
92
+ # the loop, while the worker will throw :new_worker and be booted
40
93
  # out of the loop.
41
94
  catch(:new_worker) do
42
95
  0.upto(max_port) do |i|
@@ -64,7 +117,7 @@ module Merb
64
117
  ensure
65
118
  # If there was no worker with that PID, the status was non-0
66
119
  # (we send back a status of 128 when ABRT is called on a
67
- # child, and Merb.fatal! exits with a status of 1), or if
120
+ # worker, and Merb.fatal! exits with a status of 1), or if
68
121
  # Merb is in the process of exiting, *then* don't respawn.
69
122
  # Note that processes killed with kill -9 will return no
70
123
  # exitstatus, and we respawn them.
@@ -88,11 +141,21 @@ module Merb
88
141
 
89
142
  end
90
143
 
144
+ # Fork a server on the specified port and start the app.
145
+ #
146
+ # ==== Parameters
147
+ # port<Integer>:: The port to start the server on
148
+ # opts<Hash>:: The hash of options, defaults to the @opts
149
+ # instance variable.
150
+ #
151
+ # @api private
91
152
  def self.start_at_port(port, opts = @opts)
92
153
  at_exit do
93
154
  Merb::Server.remove_pid(port)
94
155
  end
95
156
 
157
+ Merb::Worker.start
158
+
96
159
  # If Merb is daemonized, trap INT. If it's not daemonized,
97
160
  # we let the master process' ctrl-c control the cluster
98
161
  # of workers.
@@ -111,6 +174,7 @@ module Merb
111
174
  # In daemonized mode or not, support HUPing the process to
112
175
  # restart it.
113
176
  Merb.trap('HUP') do
177
+ Merb.exiting = true
114
178
  stop
115
179
  Merb.logger.warn! "Exiting port #{port} on #{Process.pid}\n"
116
180
  exit_process
@@ -118,6 +182,7 @@ module Merb
118
182
 
119
183
  # ABRTing the process will kill it, and it will not be respawned.
120
184
  Merb.trap('ABRT') do
185
+ Merb.exiting = true
121
186
  stopped = stop(128)
122
187
  Merb.logger.warn! "Exiting port #{port}\n" if stopped
123
188
  exit_process(128)
@@ -129,9 +194,9 @@ module Merb
129
194
  # Store the PID for this worker
130
195
  Merb::Server.store_pid(port)
131
196
 
132
- Merb::Config[:log_delimiter] = "#{$0} ~ "
197
+ Merb::Config[:log_delimiter] = "#{process_title(:worker, port)} ~ "
133
198
 
134
- Merb.logger = nil
199
+ Merb.reset_logger!
135
200
  Merb.logger.warn!("Starting #{self.name.split("::").last} at port #{port}")
136
201
 
137
202
  # If we can't connect to the port, keep trying until we can. Print
@@ -144,8 +209,8 @@ module Merb
144
209
  new_server(port)
145
210
  rescue Errno::EADDRINUSE
146
211
  unless printed_warning
147
- Merb.logger.warn! "Couldn't bind to port #{port}."
148
- Merb.logger.warn! "Waiting for it to become available"
212
+ Merb.logger.warn! "Port #{port} is in use, " \
213
+ "Waiting for it to become available."
149
214
  printed_warning = true
150
215
  end
151
216
 
@@ -163,11 +228,24 @@ module Merb
163
228
  start_server
164
229
  end
165
230
 
166
- # This can be overridden in adapters, but shouldn't need to be.
231
+ # Exit the process with the specified status.
232
+ #
233
+ # ==== Parameters
234
+ # status<Integer>:: The exit code of the process.
235
+ #
236
+ # @api private
167
237
  def self.exit_process(status = 0)
168
238
  exit(status)
169
239
  end
170
240
 
241
+ # Set the process title.
242
+ #
243
+ # ==== Parameters
244
+ # whoami<Symbol>:: Either :spawner for the master process or :worker for any of the worker
245
+ # processes.
246
+ # port<Integer>:: The base port that the app is running on.
247
+ #
248
+ # @api private
171
249
  def self.process_title(whoami, port)
172
250
  name = Merb::Config[:name]
173
251
  app = "merb#{" : #{name}" if (name && name != "merb")}"
@@ -179,7 +257,7 @@ module Merb
179
257
  "socket#{'s' if max_port > 0 && whoami != :worker} #{numbers} "\
180
258
  "#{file ? file : "#{Merb.log_path}/#{name}.#{port}.sock"}"
181
259
  else
182
- "port#{'s' if max_port > 0 && whoami != :worker}"
260
+ "port#{'s' if max_port > 0 && whoami != :worker} #{port}"
183
261
  end
184
262
  "#{app} : #{whoami} (#{listening_on})"
185
263
  end
@@ -1,15 +1,60 @@
1
1
  module Merb
2
2
  module Rack
3
3
  class Console
4
- # ==== Parameters
5
- # name<~to_sym, Hash>:: The name of the route to generate.
6
- # params<Hash>:: The params to use in the route generation.
4
+ # There are three possible ways to use this method. First, if you have a named route,
5
+ # you can specify the route as the first parameter as a symbol and any paramters in a
6
+ # hash. Second, you can generate the default route by just passing the params hash,
7
+ # just passing the params hash. Finally, you can use the anonymous parameters. This
8
+ # allows you to specify the parameters to a named route in the order they appear in the
9
+ # router.
10
+ #
11
+ # ==== Parameters(Named Route)
12
+ # name<Symbol>::
13
+ # The name of the route.
14
+ # args<Hash>::
15
+ # Parameters for the route generation.
16
+ #
17
+ # ==== Parameters(Default Route)
18
+ # args<Hash>::
19
+ # Parameters for the route generation. This route will use the default route.
20
+ #
21
+ # ==== Parameters(Anonymous Parameters)
22
+ # name<Symbol>::
23
+ # The name of the route.
24
+ # args<Array>::
25
+ # An array of anonymous parameters to generate the route
26
+ # with. These parameters are assigned to the route parameters
27
+ # in the order that they are passed.
7
28
  #
8
29
  # ==== Returns
9
30
  # String:: The generated URL.
10
31
  #
11
- # ==== Alternatives
12
- # If name is a hash, it will be merged with params.
32
+ # ==== Examples
33
+ # Named Route
34
+ #
35
+ # Merb::Router.prepare do
36
+ # match("/articles/:title").to(:controller => :articles, :action => :show).name("articles")
37
+ # end
38
+ #
39
+ # url(:articles, :title => "new_article")
40
+ #
41
+ # Default Route
42
+ #
43
+ # Merb::Router.prepare do
44
+ # default_routes
45
+ # end
46
+ #
47
+ # url(:controller => "articles", :action => "new")
48
+ #
49
+ # Anonymous Paramters
50
+ #
51
+ # Merb::Router.prepare do
52
+ # match("/articles/:year/:month/:title").to(:controller => :articles, :action => :show).name("articles")
53
+ # end
54
+ #
55
+ # url(:articles, 2008, 10, "test_article")
56
+ #
57
+ # @api public
13
58
  def url(name, *args)
14
59
  args << {}
15
60
  Merb::Router.url(name, *args)
@@ -2,24 +2,46 @@ module Merb
2
2
  module Rack
3
3
  class Application
4
4
 
5
+ # The main rack application call method. This is the entry point from rack (and the webserver)
6
+ # to your application.
7
+ #
8
+ # ==== Parameters
9
+ # env<Hash>:: A rack request of parameters.
10
+ #
11
+ # ==== Returns
12
+ # <Array>:: A rack response of [status<Integer>, headers<Hash>, body<String, Stream>]
13
+ #
14
+ # @api private
5
15
  def call(env)
6
16
  begin
7
- controller = ::Merb::Dispatcher.handle(Merb::Request.new(env))
17
+ rack_response = ::Merb::Dispatcher.handle(Merb::Request.new(env))
8
18
  rescue Object => e
9
- return [500, {Merb::Const::CONTENT_TYPE => "text/html"}, e.message + "<br/>" + e.backtrace.join("<br/>")]
19
+ return [500, {Merb::Const::CONTENT_TYPE => Merb::Const::TEXT_SLASH_HTML}, e.message + Merb::Const::BREAK_TAG + e.backtrace.join(Merb::Const::BREAK_TAG)]
10
20
  end
11
- Merb.logger.info "\n\n"
21
+ Merb.logger.info Merb::Const::DOUBLE_NEWLINE
12
22
  Merb.logger.flush
13
23
 
14
24
  # unless controller.headers[Merb::Const::DATE]
15
25
  # require "time"
16
26
  # controller.headers[Merb::Const::DATE] = Time.now.rfc2822.to_s
17
27
  # end
18
- controller.rack_response
28
+ rack_response
19
29
  end
20
30
 
31
+ # Determines whether this request is a "deferred_action", usually a long request.
32
+ # Rack uses this method to detemine whether to use an evented request or a deferred
33
+ # request in evented rack handlers.
34
+ #
35
+ # ==== Parameters
36
+ # env<Hash>:: The rack request
37
+ #
38
+ # ==== Returns
39
+ # Boolean::
40
+ # True if the request should be deferred.
41
+ #
42
+ # @api private
21
43
  def deferred?(env)
22
- path = env[Merb::Const::PATH_INFO] ? env[Merb::Const::PATH_INFO].chomp('/') : ""
44
+ path = env[Merb::Const::PATH_INFO] ? env[Merb::Const::PATH_INFO].chomp(Merb::Const::SLASH) : Merb::Const::EMPTY_STRING
23
45
  if path =~ Merb.deferred_actions
24
46
  Merb.logger.info! "Deferring Request: #{path}"
25
47
  true
@@ -54,10 +54,10 @@ module Merb
54
54
  # response<HTTPResponse>:: The response object to write response to.
55
55
  def process(request, response)
56
56
  env = {}.replace(request.params)
57
- env.delete "HTTP_CONTENT_TYPE"
58
- env.delete "HTTP_CONTENT_LENGTH"
57
+ env.delete Merb::Const::HTTP_CONTENT_TYPE
58
+ env.delete Merb::Const::HTTP_CONTENT_LENGTH
59
59
 
60
- env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
60
+ env[Merb::Const::SCRIPT_NAME] = Merb::Const::EMPTY_STRING if env[Merb::Const::SCRIPT_NAME] == Merb::Const::SLASH
61
61
 
62
62
  env.update({"rack.version" => [0,1],
63
63
  "rack.input" => request.body || StringIO.new(""),
@@ -69,8 +69,8 @@ module Merb
69
69
 
70
70
  "rack.url_scheme" => "http"
71
71
  })
72
- env["QUERY_STRING"] ||= ""
73
- env.delete "PATH_INFO" if env["PATH_INFO"] == ""
72
+ env[Merb::Const::QUERY_STRING] ||= ""
73
+ env.delete Merb::Const::PATH_INFO if env[Merb::Const::PATH_INFO] == Merb::Const::EMPTY_STRING
74
74
 
75
75
  status, headers, body = @app.call(env)
76
76
 
@@ -93,4 +93,4 @@ module Merb
93
93
  end
94
94
  end
95
95
  end
96
- end
96
+ end