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,10 @@ module Merb
3
3
  class BootLoader
4
4
 
5
5
  # def self.subclasses
6
- #---
7
- # @semipublic
6
+ #
7
+ # @api plugin
8
8
  cattr_accessor :subclasses, :after_load_callbacks, :before_load_callbacks, :finished
9
- self.subclasses, self.after_load_callbacks,
9
+ self.subclasses, self.after_load_callbacks,
10
10
  self.before_load_callbacks, self.finished = [], [], [], []
11
11
 
12
12
  class << self
@@ -16,29 +16,44 @@ module Merb
16
16
  #
17
17
  # ==== Parameters
18
18
  # klass<Class>:: The class inheriting from Merb::BootLoader.
19
+ #
20
+ # ==== Returns
21
+ # nil
22
+ #
23
+ # @api plugin
19
24
  def inherited(klass)
20
25
  subclasses << klass.to_s
21
26
  super
22
27
  end
23
28
 
29
+ # Execute this boot loader after the specified boot loader.
30
+ #
24
31
  # ==== Parameters
25
32
  # klass<~to_s>::
26
33
  # The boot loader class after which this boot loader should be run.
27
34
  #
28
- #---
29
- # @public
35
+ # ==== Returns
36
+ # nil
37
+ #
38
+ # @api plugin
30
39
  def after(klass)
31
40
  move_klass(klass, 1)
41
+ nil
32
42
  end
33
43
 
44
+ # Execute this boot loader before the specified boot loader.
45
+ #
34
46
  # ==== Parameters
35
47
  # klass<~to_s>::
36
48
  # The boot loader class before which this boot loader should be run.
37
49
  #
38
- #---
39
- # @public
50
+ # ==== Returns
51
+ # nil
52
+ #
53
+ # @api plugin
40
54
  def before(klass)
41
55
  move_klass(klass, 0)
56
+ nil
42
57
  end
43
58
 
44
59
  # Move a class that is inside the bootloader to some place in the Array,
@@ -49,15 +64,26 @@ module Merb
49
64
  # The klass to move the bootloader relative to
50
65
  # where<Integer>::
51
66
  # 0 means insert it before; 1 means insert it after
67
+ #
68
+ # ==== Returns
69
+ # nil
70
+ #
71
+ # @api private
52
72
  def move_klass(klass, where)
53
73
  index = Merb::BootLoader.subclasses.index(klass.to_s)
54
74
  if index
55
75
  Merb::BootLoader.subclasses.delete(self.to_s)
56
76
  Merb::BootLoader.subclasses.insert(index + where, self.to_s)
57
77
  end
78
+ nil
58
79
  end
59
80
 
60
81
  # Runs all boot loader classes by calling their run methods.
82
+ #
83
+ # ==== Returns
84
+ # nil
85
+ #
86
+ # @api plugin
61
87
  def run
62
88
  Merb.started = true
63
89
  subklasses = subclasses.dup
@@ -74,8 +100,9 @@ module Merb
74
100
  self.finished << bootloader
75
101
  end
76
102
  self.subclasses = subklasses
103
+ nil
77
104
  end
78
-
105
+
79
106
  # Determines whether or not a specific bootloader has finished yet.
80
107
  #
81
108
  # ==== Parameters
@@ -83,6 +110,8 @@ module Merb
83
110
  #
84
111
  # ==== Returns
85
112
  # Boolean:: Whether or not the bootloader has finished.
113
+ #
114
+ # @api private
86
115
  def finished?(bootloader)
87
116
  self.finished.include?(bootloader.to_s)
88
117
  end
@@ -92,8 +121,8 @@ module Merb
92
121
  # ==== Returns
93
122
  # nil
94
123
  #
95
- #---
96
- # @public
124
+ # @api plugin
125
+ # @overridable
97
126
  def default_framework
98
127
  %w[view model helper controller mailer part].each do |component|
99
128
  Merb.push_path(component.to_sym, Merb.root_path("app/#{component}s"))
@@ -102,6 +131,7 @@ module Merb
102
131
  Merb.push_path(:config, Merb.root_path("config"), nil)
103
132
  Merb.push_path(:router, Merb.dir_for(:config), (Merb::Config[:router_file] || "router.rb"))
104
133
  Merb.push_path(:lib, Merb.root_path("lib"), nil)
134
+ Merb.push_path(:merb, Merb.root_path("merb"))
105
135
  Merb.push_path(:log, Merb.log_path, nil)
106
136
  Merb.push_path(:public, Merb.root_path("public"), nil)
107
137
  Merb.push_path(:stylesheet, Merb.dir_for(:public) / "stylesheets", nil)
@@ -110,24 +140,26 @@ module Merb
110
140
  nil
111
141
  end
112
142
 
143
+ # Execute a block of code after the app loads.
144
+ #
113
145
  # ==== Parameters
114
146
  # &block::
115
147
  # A block to be added to the callbacks that will be executed after the
116
148
  # app loads.
117
149
  #
118
- #---
119
- # @public
150
+ # @api public
120
151
  def after_app_loads(&block)
121
152
  after_load_callbacks << block
122
153
  end
123
154
 
155
+ # Execute a block of code before the app loads but after dependencies load.
156
+ #
124
157
  # ==== Parameters
125
158
  # &block::
126
159
  # A block to be added to the callbacks that will be executed before the
127
160
  # app loads.
128
161
  #
129
- #---
130
- # @public
162
+ # @api public
131
163
  def before_app_loads(&block)
132
164
  before_load_callbacks << block
133
165
  end
@@ -144,20 +176,33 @@ end
144
176
  class Merb::BootLoader::Logger < Merb::BootLoader
145
177
 
146
178
  # Sets Merb.logger to a new logger created based on the config settings.
179
+ #
180
+ # ==== Returns
181
+ # nil
182
+ #
183
+ # @api plugin
147
184
  def self.run
148
185
  Merb::Config[:log_level] ||= begin
149
186
  if Merb.environment == "production"
150
187
  Merb::Logger::Levels[:warn]
151
188
  else
152
189
  Merb::Logger::Levels[:debug]
153
- end
190
+ end
154
191
  end
155
-
192
+
156
193
  Merb::Config[:log_stream] = Merb.log_stream
157
-
194
+
158
195
  print_warnings
196
+
197
+ nil
159
198
  end
160
-
199
+
200
+ # Print a warning if the installed version of rubygems is not supported
201
+ #
202
+ # ==== Returns
203
+ # nil
204
+ #
205
+ # @api private
161
206
  def self.print_warnings
162
207
  if Gem::Version.new(Gem::RubyGemsVersion) < Gem::Version.new("1.1")
163
208
  Merb.fatal! "Merb requires Rubygems 1.1 and later. " \
@@ -174,19 +219,32 @@ class Merb::BootLoader::DropPidFile < Merb::BootLoader
174
219
  class << self
175
220
 
176
221
  # Stores a PID file if Merb is running daemonized or clustered.
222
+ #
223
+ # ==== Returns
224
+ # nil
225
+ #
226
+ # @api plugin
177
227
  def run
178
228
  Merb::Server.store_pid("main") #if Merb::Config[:daemonize] || Merb::Config[:cluster]
229
+ nil
179
230
  end
180
231
  end
181
232
  end
182
233
 
183
234
  # Setup some useful defaults
184
235
  class Merb::BootLoader::Defaults < Merb::BootLoader
236
+ # Sets up the defaults
237
+ #
238
+ # ==== Returns
239
+ # nil
240
+ #
241
+ # @api plugin
185
242
  def self.run
186
243
  Merb::Request.http_method_overrides.concat([
187
244
  proc { |c| c.params[:_method] },
188
245
  proc { |c| c.env['HTTP_X_HTTP_METHOD_OVERRIDE'] }
189
246
  ])
247
+ nil
190
248
  end
191
249
  end
192
250
 
@@ -230,12 +288,24 @@ class Merb::BootLoader::BuildFramework < Merb::BootLoader
230
288
  class << self
231
289
 
232
290
  # Builds the framework directory structure.
291
+ #
292
+ # ==== Returns
293
+ # nil
233
294
  def run
234
295
  build_framework
296
+ nil
235
297
  end
236
298
 
237
- # This method should be overridden in init.rb before Merb.start to set up
238
- # a different framework structure.
299
+ # Sets up merb paths to support the app's file layout. First, config/framework.rb is checked,
300
+ # next we look for Merb.root/framework.rb, finally we use the default merb layout (Merb::BootLoader.default_framework)
301
+ #
302
+ # This method can be overriden to support other application layouts.
303
+ #
304
+ # ==== Returns
305
+ # nil
306
+ #
307
+ # @api plugin
308
+ # @overridable
239
309
  def build_framework
240
310
  if File.exists?(Merb.root / "config" / "framework.rb")
241
311
  require Merb.root / "config" / "framework"
@@ -248,12 +318,17 @@ class Merb::BootLoader::BuildFramework < Merb::BootLoader
248
318
  path = Array(path)
249
319
  Merb.push_path(name, path.first, path.length == 2 ? path[1] : "**/*.rb")
250
320
  end
321
+ nil
251
322
  end
252
323
  end
253
324
  end
254
325
 
255
326
  class Merb::BootLoader::Dependencies < Merb::BootLoader
256
327
 
328
+ # ==== Returns
329
+ # Array[Gem::Dependency]:: The dependencies regiestered in init.rb.
330
+ #
331
+ # @api plugin
257
332
  cattr_accessor :dependencies
258
333
  self.dependencies = []
259
334
 
@@ -266,20 +341,41 @@ class Merb::BootLoader::Dependencies < Merb::BootLoader
266
341
  # before or after insertion methods. Since these are loaded from this
267
342
  # bootloader (Dependencies), they can only adapt the bootloaders that
268
343
  # haven't been loaded up until this point.
269
-
344
+ #
345
+ # ==== Returns
346
+ # nil
347
+ #
348
+ # @api plugin
270
349
  def self.run
271
350
  set_encoding
272
- load_initfile
273
- load_env_config
351
+ # this is crucial: load init file with all the preferences
352
+ # then environment init file, then start enabling specific
353
+ # components, load dependencies and update logger.
354
+ unless Merb::disabled?(:initfile)
355
+ load_initfile
356
+ load_env_config
357
+ end
274
358
  enable_json_gem unless Merb::disabled?(:json)
275
359
  load_dependencies
276
360
  update_logger
361
+ nil
277
362
  end
278
363
 
364
+ # Load each dependency that has been declared so far.
365
+ #
366
+ # ==== Returns
367
+ # nil
368
+ #
369
+ # @api private
279
370
  def self.load_dependencies
280
371
  dependencies.each { |dependency| Kernel.load_dependency(dependency) }
372
+ nil
281
373
  end
282
374
 
375
+ # Loads json or json_pure and requires it.
376
+ #
377
+ # ==== Returns
378
+ # nil
283
379
  def self.enable_json_gem
284
380
  gem "json"
285
381
  require "json/ext"
@@ -288,35 +384,83 @@ class Merb::BootLoader::Dependencies < Merb::BootLoader
288
384
  require "json/pure"
289
385
  end
290
386
 
387
+ # Resets the logger and sets the log_stream to Merb::Config[:log_file]
388
+ # if one is specified, falling back to STDOUT.
389
+ #
390
+ # ==== Returns
391
+ # nil
392
+ #
393
+ # @api private
291
394
  def self.update_logger
292
- Merb.logger = nil
293
- STDOUT.puts "Logging to #{Merb::Config[:log_file] || 'stdout'}" unless Merb.testing?
294
- Merb::Config[:log_stream] = File.open(Merb::Config[:log_file], "w+") if Merb::Config[:log_file]
395
+ Merb.reset_logger!
396
+
397
+ # If log file is given, use it and not log stream we have.
398
+ if Merb::Config[:log_file]
399
+ raise "log file should be a string, got: #{Merb::Config[:log_file].inspect}" unless Merb::Config[:log_file].is_a?(String)
400
+ STDOUT.puts "Logging to file at #{Merb::Config[:log_file]}" unless Merb.testing?
401
+ Merb::Config[:log_stream] = File.open(Merb::Config[:log_file], "w+")
402
+ # but if it's not given, fallback to log stream or stdout
403
+ else
404
+ Merb::Config[:log_stream] ||= STDOUT
405
+ end
406
+
407
+ nil
295
408
  end
296
-
409
+
410
+ # Default encoding to UTF8 if it has not already been set to something else.
411
+ #
412
+ # ==== Returns
413
+ # nil
414
+ #
415
+ # @api private
297
416
  def self.set_encoding
298
417
  $KCODE = 'UTF8' if $KCODE == 'NONE' || $KCODE.blank?
418
+ nil
299
419
  end
300
420
 
301
421
  private
302
422
 
303
423
  # Determines the path for the environment configuration file
424
+ #
425
+ # ==== Returns
426
+ # String:: The path to the config file for the environment
427
+ #
428
+ # @api private
304
429
  def self.env_config
305
430
  Merb.dir_for(:config) / "environments" / (Merb.environment + ".rb")
306
431
  end
307
432
 
308
433
  # Checks to see whether or not an environment configuration exists
434
+ #
435
+ # ==== Returns
436
+ # Boolean:: Whether or not the environment configuration file exists.
437
+ #
438
+ # @api private
309
439
  def self.env_config?
310
440
  Merb.environment && File.exist?(env_config)
311
441
  end
312
442
 
313
- # Loads the environment configuration file, if any
443
+ # Loads the environment configuration file, if it is present
444
+ #
445
+ # ==== Returns
446
+ # nil
447
+ #
448
+ # @api private
314
449
  def self.load_env_config
315
- load(env_config) if env_config?
450
+ if env_config?
451
+ STDOUT.puts "Loading #{env_config}" unless Merb.testing?
452
+ load(env_config)
453
+ end
454
+ nil
316
455
  end
317
456
 
318
457
  # Determines the init file to use, if any.
319
458
  # By default Merb uses init.rb from application config directory.
459
+ #
460
+ # ==== Returns
461
+ # nil
462
+ #
463
+ # @api private
320
464
  def self.initfile
321
465
  if Merb::Config[:init_file]
322
466
  Merb::Config[:init_file].chomp(".rb") + ".rb"
@@ -326,10 +470,22 @@ class Merb::BootLoader::Dependencies < Merb::BootLoader
326
470
  end
327
471
 
328
472
  # Loads the init file, should one exist
473
+ #
474
+ # ==== Returns
475
+ # nil
476
+ #
477
+ # @api private
329
478
  def self.load_initfile
330
- load(initfile) if File.exists?(initfile)
479
+ if File.exists?(initfile)
480
+ STDOUT.puts "Loading init file from #{initfile}" unless Merb.testing?
481
+ load(initfile)
482
+ elsif !Merb.testing?
483
+ Merb.fatal! "You are not in a Merb application, or you are in " \
484
+ "a flat application and have not specified the init file. If you " \
485
+ "are trying to create a new merb application, use merb-gen app."
486
+ end
487
+ nil
331
488
  end
332
-
333
489
  end
334
490
 
335
491
  class Merb::BootLoader::MixinSession < Merb::BootLoader
@@ -339,8 +495,13 @@ class Merb::BootLoader::MixinSession < Merb::BootLoader
339
495
  # plugin session stores for example - these need to be loaded in a
340
496
  # before_app_loads block or a BootLoader that runs after MixinSession.
341
497
  #
342
- # Note: access to Merb::Config is needed, so it needs to run after
498
+ # Note: access to Merb::Config is needed, so it needs to run after
343
499
  # Merb::BootLoader::Dependencies is done.
500
+ #
501
+ # ==== Returns
502
+ # nil
503
+ #
504
+ # @api plugin
344
505
  def self.run
345
506
  require 'merb-core/dispatch/session'
346
507
  Merb::Controller.send(:include, ::Merb::SessionMixin)
@@ -353,8 +514,14 @@ class Merb::BootLoader::BeforeAppLoads < Merb::BootLoader
353
514
 
354
515
  # Call any before_app_loads hooks that were registered via before_app_loads
355
516
  # in any plugins.
517
+ #
518
+ # ==== Returns
519
+ # nil
520
+ #
521
+ # @api plugin
356
522
  def self.run
357
523
  Merb::BootLoader.before_load_callbacks.each { |x| x.call }
524
+ nil
358
525
  end
359
526
  end
360
527
 
@@ -373,6 +540,18 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
373
540
  class << self
374
541
 
375
542
  # Load all classes from Merb's native load paths.
543
+ #
544
+ # If fork-based loading is used, every time classes are loaded this will return in a new spawner process
545
+ # and boot loading will continue from this point in the boot loading process.
546
+ #
547
+ # If fork-based loading is not in use, this only returns once and does not fork a new
548
+ # process.
549
+ #
550
+ # ==== Returns
551
+ # Returns at least once:
552
+ # nil
553
+ #
554
+ # @api plugin
376
555
  def run
377
556
  # Add models, controllers, helpers and lib to the load path
378
557
  unless @ran
@@ -383,20 +562,26 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
383
562
  end
384
563
 
385
564
  @ran = true
565
+ # process name you see in ps output
386
566
  $0 = "merb#{" : " + Merb::Config[:name] if Merb::Config[:name]} : master"
387
567
 
568
+ # Log the process configuration user defined signal 1 (SIGUSR1) is received.
569
+ Merb.trap("USR1") do
570
+ Merb.logger.fatal! "Configuration:\n#{Merb::Config.to_hash.merge(:pid => $$).to_yaml}\n\n"
571
+ end
572
+
388
573
  if Merb::Config[:fork_for_class_load] && !Merb.testing?
389
574
  start_transaction
390
575
  else
391
576
  Merb.trap('INT') do
392
- Merb.logger.warn! "Killing children"
393
- kill_children
577
+ Merb.logger.warn! "Reaping Workers"
578
+ reap_workers
394
579
  end
395
580
  end
396
581
 
397
582
  # Load application file if it exists - for flat applications
398
583
  load_file Merb.dir_for(:application) if File.file?(Merb.dir_for(:application))
399
-
584
+
400
585
  # Load classes and their requirements
401
586
  Merb.load_paths.each do |component, path|
402
587
  next if path.last.blank? || component == :application || component == :router
@@ -404,18 +589,41 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
404
589
  end
405
590
 
406
591
  Merb::Controller.send :include, Merb::GlobalHelpers
592
+
593
+ nil
407
594
  end
408
-
595
+
409
596
  # Wait for any children to exit, remove the "main" PID, and
410
597
  # exit.
598
+ #
599
+ # ==== Returns
600
+ # (Does not return.)
601
+ #
602
+ # @api private
411
603
  def exit_gracefully
604
+ # wait all workers to exit
412
605
  Process.waitall
606
+ # remove master process pid
413
607
  Merb::Server.remove_pid("main")
608
+ # terminate, workers remove their own pids
609
+ # in on exit hook
414
610
  exit
415
611
  end
416
612
 
417
- # If using fork-based code reloading, set up the BEGIN
418
- # point and set up any signals in the parent and child.
613
+ # Set up the BEGIN point for fork-based loading and sets up
614
+ # any signals in the parent and child. This is done by forking
615
+ # the app. The child process continues on to run the app. The parent
616
+ # process waits for the child process to finish and either forks again
617
+ #
618
+ #
619
+ # ==== Returns
620
+ # Parent Process:
621
+ # (Does not return.)
622
+ # Child Process returns at least once:
623
+ # nil
624
+ #
625
+ # @api private
626
+
419
627
  def start_transaction
420
628
  Merb.logger.warn! "Parent pid: #{Process.pid}"
421
629
  reader, writer = nil, nil
@@ -425,20 +633,25 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
425
633
  end
426
634
 
427
635
  loop do
636
+ # create two connected endpoints
637
+ # we use them for master/workers communication
428
638
  reader, @writer = IO.pipe
429
639
  pid = Kernel.fork
430
640
 
431
- # pid means we're in the parent; only stay in the loop in that case
641
+ # pid means we're in the parent; only stay in the loop if that is case
432
642
  break unless pid
643
+ # writer must be closed so reader can generate EOF condition
433
644
  @writer.close
434
645
 
646
+ # master process stores pid to merb.main.pid
435
647
  Merb::Server.store_pid("main")
436
648
 
437
649
  if Merb::Config[:console_trap]
438
650
  Merb.trap("INT") {}
439
651
  else
652
+ # send ABRT to worker on INT
440
653
  Merb.trap("INT") do
441
- Merb.logger.warn! "Killing children"
654
+ Merb.logger.warn! "Reaping Workers"
442
655
  begin
443
656
  Process.kill("ABRT", pid)
444
657
  rescue SystemCallError
@@ -454,14 +667,26 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
454
667
 
455
668
  reader_ary = [reader]
456
669
  loop do
670
+ # wait for worker to exit and capture exit status
671
+ #
672
+ #
673
+ # WNOHANG specifies that wait2 exists without waiting
674
+ # if no worker processes are ready to be noticed.
457
675
  if exit_status = Process.wait2(pid, Process::WNOHANG)
458
- exit_status[1] == 128 ? break : exit
676
+ # wait2 returns a 2-tuple of process id and exit
677
+ # status.
678
+ #
679
+ # We do not care about specific pid here.
680
+ exit_status[1] && exit_status[1].exitstatus == 128 ? break : exit
459
681
  end
682
+ # wait for data to become available, timeout in 0.25 of a second
460
683
  if select(reader_ary, nil, nil, 0.25)
461
684
  begin
685
+ # no open writers
462
686
  next if reader.eof?
463
687
  msg = reader.readline
464
688
  if msg =~ /128/
689
+ Process.detach(pid)
465
690
  break
466
691
  else
467
692
  exit_gracefully
@@ -475,39 +700,47 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
475
700
 
476
701
  reader.close
477
702
 
478
- # add traps to the child
703
+ # add traps to the worker
479
704
  if Merb::Config[:console_trap]
480
705
  Merb::Server.add_irb_trap
481
- at_exit { kill_children }
706
+ at_exit { reap_workers }
482
707
  else
483
708
  Merb.trap('INT') {}
484
- Merb.trap('ABRT') { kill_children }
485
- Merb.trap('HUP') { kill_children(128) }
709
+ Merb.trap('ABRT') { reap_workers }
710
+ Merb.trap('HUP') { reap_workers(128) }
486
711
  end
487
712
  end
488
713
 
489
- # Kill any children of the spawner process and exit with
490
- # an appropriate status code.
714
+ # Reap any workers of the spawner process and
715
+ # exit with an appropriate status code.
491
716
  #
492
717
  # Note that exiting the spawner process with a status code
493
718
  # of 128 when a master process exists will cause the
494
719
  # spawner process to be recreated, and the app code reloaded.
495
720
  #
721
+ # ==== Parameters
722
+ # status<Integer>:: The status code to exit with. Defaults to 0.
723
+ #
724
+ # ==== Returns
725
+ # (Does not return.)
726
+ #
727
+ # @api private
496
728
  # @param status<Integer> The status code to exit with
497
- def kill_children(status = 0)
729
+ # @param sig<String> The signal to send to workers
730
+ def reap_workers(status = 0, sig = "ABRT")
498
731
  Merb.exiting = true unless status == 128
499
-
732
+
500
733
  begin
501
734
  @writer.puts(status.to_s) if @writer
502
735
  rescue SystemCallError
503
736
  end
504
-
737
+
505
738
  threads = []
506
-
507
- ($CHILDREN || []).each do |p|
739
+
740
+ ($WORKERS || []).each do |p|
508
741
  threads << Thread.new do
509
742
  begin
510
- Process.kill("ABRT", p)
743
+ Process.kill(sig, p)
511
744
  Process.wait2(p)
512
745
  rescue SystemCallError
513
746
  end
@@ -516,37 +749,52 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
516
749
  threads.each {|t| t.join }
517
750
  exit(status)
518
751
  end
519
-
752
+
753
+ # Loads a file, tracking its modified time and, if necessary, the classes it declared.
754
+ #
520
755
  # ==== Parameters
521
756
  # file<String>:: The file to load.
757
+ #
758
+ # ==== Returns
759
+ # nil
760
+ #
761
+ # @api private
522
762
  def load_file(file)
523
763
  # Don't do this expensive operation unless we need to
524
764
  unless Merb::Config[:fork_for_class_load]
525
765
  klasses = ObjectSpace.classes.dup
526
766
  end
527
-
767
+
528
768
  # Ignore the file for syntax errors. The next time
529
769
  # the file is changed, it'll be reloaded again
530
770
  begin
531
771
  load file
532
- rescue SyntaxError
533
- return
772
+ rescue SyntaxError => e
773
+ Merb.logger.error "Cannot load #{file} because of syntax error: #{e.message}"
534
774
  ensure
535
775
  if Merb::Config[:reload_classes]
536
776
  MTIMES[file] = File.mtime(file)
537
777
  end
538
778
  end
539
-
779
+
540
780
  # Don't do this expensive operation unless we need to
541
781
  unless Merb::Config[:fork_for_class_load]
542
782
  LOADED_CLASSES[file] = ObjectSpace.classes - klasses
543
783
  end
784
+
785
+ nil
544
786
  end
545
-
787
+
546
788
  # Load classes from given paths - using path/glob pattern.
547
789
  #
790
+ # ==== Parameters
548
791
  # *paths<Array>::
549
792
  # Array of paths to load classes from - may contain glob pattern
793
+ #
794
+ # ==== Returns
795
+ # nil
796
+ #
797
+ # @api private
550
798
  def load_classes(*paths)
551
799
  orphaned_classes = []
552
800
  paths.flatten.each do |path|
@@ -560,20 +808,44 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
560
808
  end
561
809
  load_classes_with_requirements(orphaned_classes)
562
810
  end
563
-
811
+
812
+ # Reloads the classes in the specified file. If fork-based loading is used,
813
+ # this causes the current processes to be killed and and all classes to be
814
+ # reloaded. If class-based loading is not in use, the classes declared in that file
815
+ # are removed and the file is reloaded.
816
+ #
564
817
  # ==== Parameters
565
818
  # file<String>:: The file to reload.
819
+ #
820
+ # ==== Returns
821
+ # When fork-based loading is used:
822
+ # (Does not return.)
823
+ # When fork-based loading is not in use:
824
+ # nil
825
+ #
826
+ # @api private
566
827
  def reload(file)
567
828
  if Merb::Config[:fork_for_class_load]
568
- kill_children(128)
829
+ reap_workers(128)
569
830
  else
570
831
  remove_classes_in_file(file) { |f| load_file(f) }
571
832
  end
572
833
  end
573
-
834
+
835
+ # Removes all classes declared in the specified file. Any hashes which use classes as keys
836
+ # will be protected provided they have been added to Merb.klass_hashes. These hashes have their
837
+ # keys substituted with placeholders before the file's classes are unloaded. If a block is provided,
838
+ # it is called before the substituted keys are reconstituted.
839
+ #
574
840
  # ==== Parameters
575
841
  # file<String>:: The file to remove classes for.
576
- # &block:: A block to call with the file that has been removed.
842
+ # &block:: A block to call with the file that has been removed before klass_hashes are updated
843
+ # to use the current values of the constants they used as keys.
844
+ #
845
+ # ==== Returns
846
+ # nil
847
+ #
848
+ # @api private
577
849
  def remove_classes_in_file(file, &block)
578
850
  Merb.klass_hashes.each { |x| x.protect_keys! }
579
851
  if klasses = LOADED_CLASSES.delete(file)
@@ -581,15 +853,26 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
581
853
  end
582
854
  yield file if block_given?
583
855
  Merb.klass_hashes.each {|x| x.unprotect_keys!}
856
+ nil
584
857
  end
585
858
 
859
+ # Removes the specified class.
860
+ #
861
+ # Additionally, removes the specified class from the subclass list of every superclass that
862
+ # tracks it's subclasses in an array returned by _subclasses_list. Classes that wish to use this
863
+ # functionality are required to alias the reader for their list of subclasses
864
+ # to _subclasses_list. Plugins for ORMs and other libraries should keep this in mind.
865
+ #
586
866
  # ==== Parameters
587
867
  # const<Class>:: The class to remove.
868
+ #
869
+ # ==== Returns
870
+ # nil
871
+ #
872
+ # @api private
588
873
  def remove_constant(const)
589
874
  # This is to support superclasses (like AbstractController) that track
590
- # their subclasses in a class variable. Classes that wish to use this
591
- # functionality are required to alias it to _subclasses_list. Plugins
592
- # for ORMs and other libraries should keep this in mind.
875
+ # their subclasses in a class variable.
593
876
  superklass = const
594
877
  until (superklass = superklass.superclass).nil?
595
878
  if superklass.respond_to?(:_subclasses_list)
@@ -607,16 +890,22 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
607
890
  rescue NameError
608
891
  Merb.logger.debug("Failed to remove constant #{object} from #{base}")
609
892
  end
893
+ nil
610
894
  end
611
-
895
+
612
896
  private
613
897
 
614
- # "Better loading" of classes. If a class fails to load due to a NameError
898
+ # "Better loading" of classes. If a file fails to load due to a NameError
615
899
  # it will be added to the failed_classes and load cycle will be repeated unless
616
900
  # no classes load.
617
901
  #
618
902
  # ==== Parameters
619
903
  # klasses<Array[Class]>:: Classes to load.
904
+ #
905
+ # ==== Returns
906
+ # nil
907
+ #
908
+ # @api private
620
909
  def load_classes_with_requirements(klasses)
621
910
  klasses.uniq!
622
911
 
@@ -646,7 +935,7 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
646
935
  if klasses.size == size_at_start && klasses.size != 0
647
936
  # Write all remaining failed classes and their exceptions to the log
648
937
  messages = error_map.only(*failed_classes).map do |klass, e|
649
- ["Could not load #{klass}:\n\n#{e.message} - (#{e.class})",
938
+ ["Could not load #{klass}:\n\n#{e.message} - (#{e.class})",
650
939
  "#{(e.backtrace || []).join("\n")}"]
651
940
  end
652
941
  messages.each { |msg, trace| Merb.logger.fatal!("#{msg}\n\n#{trace}") }
@@ -654,26 +943,35 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
654
943
  end
655
944
  break if(klasses.size == size_at_start || klasses.size == 0)
656
945
  end
946
+
947
+ nil
657
948
  end
658
949
 
659
950
  end
660
951
 
661
952
  end
662
953
 
954
+ # Loads the router file. This needs to happen after everything else is loaded while merb is starting up to ensure
955
+ # the router has everything it needs to run.
663
956
  class Merb::BootLoader::Router < Merb::BootLoader
664
957
  class << self
665
958
 
959
+ # load the router file
960
+ #
961
+ # ==== Returns
962
+ # nil
963
+ #
964
+ # @api plugin
666
965
  def run
667
966
  Merb::BootLoader::LoadClasses.load_file(router_file) if router_file
967
+
968
+ nil
668
969
  end
669
-
670
- def reload!
671
- if router_file
672
- Merb::Router.reset!
673
- Merb::BootLoader::LoadClasses.reload(router_file)
674
- end
675
- end
676
-
970
+
971
+ # Tries to find the router file.
972
+ #
973
+ # ==== Returns
974
+ # String:: The path to the router file if it exists, nil otherwise.
677
975
  def router_file
678
976
  @router_file ||= begin
679
977
  if File.file?(router = Merb.dir_for(:router) / Merb.glob_for(:router))
@@ -681,22 +979,32 @@ class Merb::BootLoader::Router < Merb::BootLoader
681
979
  end
682
980
  end
683
981
  end
684
-
982
+
685
983
  end
686
984
  end
687
985
 
986
+ # Precompiles all non-partial templates.
688
987
  class Merb::BootLoader::Templates < Merb::BootLoader
689
988
  class << self
690
989
 
691
- # Loads the templates into the Merb::InlineTemplates module.
990
+ # Loads all non-partial templates into the Merb::InlineTemplates module.
991
+ #
992
+ # ==== Returns
993
+ # Array[String]:: The list of template files which were loaded.
994
+ #
995
+ # @api plugin
692
996
  def run
693
997
  template_paths.each do |path|
694
998
  Merb::Template.inline_template(File.open(path))
695
999
  end
696
1000
  end
697
1001
 
1002
+ # Finds a list of templates to load.
1003
+ #
698
1004
  # ==== Returns
699
- # Array[String]:: Template files found.
1005
+ # Array[String]:: All found template files whose basename does not begin with "_".
1006
+ #
1007
+ # @api private
700
1008
  def template_paths
701
1009
  extension_glob = "{#{Merb::Template.template_extensions.join(',')}}"
702
1010
 
@@ -735,6 +1043,11 @@ end
735
1043
  class Merb::BootLoader::MimeTypes < Merb::BootLoader
736
1044
 
737
1045
  # Registers the default MIME types.
1046
+ #
1047
+ # ==== Returns
1048
+ # nil
1049
+ #
1050
+ # @api plugin
738
1051
  def self.run
739
1052
  Merb.add_mime_type(:all, nil, %w[*/*])
740
1053
  Merb.add_mime_type(:yaml, :to_yaml, %w[application/x-yaml text/yaml], :charset => "utf-8")
@@ -743,47 +1056,62 @@ class Merb::BootLoader::MimeTypes < Merb::BootLoader
743
1056
  Merb.add_mime_type(:xml, :to_xml, %w[application/xml text/xml application/x-xml], {:charset => "utf-8"}, 0.9998)
744
1057
  Merb.add_mime_type(:js, :to_json, %w[text/javascript application/javascript application/x-javascript], :charset => "utf-8")
745
1058
  Merb.add_mime_type(:json, :to_json, %w[application/json text/x-json], :charset => "utf-8")
1059
+ nil
746
1060
  end
747
1061
  end
748
1062
 
1063
+ # Set up cookies support in Merb::Controller and Merb::Request
749
1064
  class Merb::BootLoader::Cookies < Merb::BootLoader
750
-
1065
+
1066
+ # Set up cookies support in Merb::Controller and Merb::Request
1067
+ #
1068
+ # ==== Returns
1069
+ # nil
1070
+ #
1071
+ # @api plugin
751
1072
  def self.run
752
1073
  require 'merb-core/dispatch/cookies'
753
1074
  Merb::Controller.send(:include, Merb::CookiesMixin)
754
1075
  Merb::Request.send(:include, Merb::CookiesMixin::RequestMixin)
1076
+ nil
755
1077
  end
756
-
1078
+
757
1079
  end
758
1080
 
759
1081
  class Merb::BootLoader::SetupSession < Merb::BootLoader
760
1082
 
761
1083
  # Enable the configured session container(s); any class that inherits from
762
1084
  # SessionContainer will be considered by its session_store_type attribute.
1085
+ #
1086
+ # ==== Returns
1087
+ # nil
1088
+ #
1089
+ # @api plugin
763
1090
  def self.run
764
1091
  # Require all standard session containers.
765
1092
  Dir[Merb.framework_root / "merb-core" / "dispatch" / "session" / "*.rb"].each do |file|
766
1093
  base_name = File.basename(file, ".rb")
767
1094
  require file unless base_name == "container" || base_name == "store_container"
768
1095
  end
769
-
1096
+
770
1097
  # Set some defaults.
771
1098
  Merb::Config[:session_id_key] ||= "_session_id"
772
-
1099
+
773
1100
  # List of all session_stores from :session_stores and :session_store config options.
774
1101
  config_stores = Merb::Config.session_stores
775
-
1102
+
776
1103
  # Register all configured session stores - any loaded session container class
777
1104
  # (subclassed from Merb::SessionContainer) will be available for registration.
778
1105
  Merb::SessionContainer.subclasses.each do |class_name|
779
- if(store = Object.full_const_get(class_name)) &&
1106
+ if(store = Object.full_const_get(class_name)) &&
780
1107
  config_stores.include?(store.session_store_type)
781
1108
  Merb::Request.register_session_type(store.session_store_type, class_name)
782
1109
  end
783
1110
  end
784
-
1111
+
785
1112
  # Mixin the Merb::Session module to add app-level functionality to sessions
786
1113
  Merb::SessionContainer.send(:include, Merb::Session)
1114
+ nil
787
1115
  end
788
1116
 
789
1117
  end
@@ -792,14 +1120,26 @@ class Merb::BootLoader::AfterAppLoads < Merb::BootLoader
792
1120
 
793
1121
  # Call any after_app_loads hooks that were registered via after_app_loads in
794
1122
  # init.rb.
1123
+ #
1124
+ # ==== Returns
1125
+ # nil
1126
+ #
1127
+ # @api plugin
795
1128
  def self.run
796
1129
  Merb::BootLoader.after_load_callbacks.each {|x| x.call }
1130
+ nil
797
1131
  end
798
1132
  end
799
1133
 
800
1134
  # In case someone's running a sparse app, the default exceptions require the
801
1135
  # Exceptions class.
802
1136
  class Merb::BootLoader::SetupStubClasses < Merb::BootLoader
1137
+ # Declares empty Application and Exception controllers.
1138
+ #
1139
+ # ==== Returns
1140
+ # nil
1141
+ #
1142
+ # @api plugin
803
1143
  def self.run
804
1144
  unless defined?(Exceptions)
805
1145
  Object.class_eval <<-RUBY
@@ -811,31 +1151,34 @@ class Merb::BootLoader::SetupStubClasses < Merb::BootLoader
811
1151
  end
812
1152
  RUBY
813
1153
  end
1154
+ nil
814
1155
  end
815
1156
  end
816
1157
 
817
1158
  class Merb::BootLoader::ChooseAdapter < Merb::BootLoader
818
1159
 
819
1160
  # Choose the Rack adapter/server to use and set Merb.adapter.
1161
+ #
1162
+ # ==== Returns
1163
+ # nil
1164
+ #
1165
+ # @api plugin
820
1166
  def self.run
821
1167
  Merb.adapter = Merb::Rack::Adapter.get(Merb::Config[:adapter])
822
1168
  end
823
1169
  end
824
1170
 
825
- class Merb::BootLoader::StartWorkerThread < Merb::BootLoader
826
-
827
- # Choose the Rack adapter/server to use and set Merb.adapter.
828
- def self.run
829
- Merb::Worker.new
830
- end
831
- end
832
-
833
1171
  class Merb::BootLoader::RackUpApplication < Merb::BootLoader
834
1172
  # Setup the Merb Rack App or read a rackup file located at
835
1173
  # Merb::Config[:rackup] with the same syntax as the
836
1174
  # rackup tool that comes with rack. Automatically evals the file in
837
1175
  # the context of a Rack::Builder.new { } block. Allows for mounting
838
1176
  # additional apps or middleware.
1177
+ #
1178
+ # ==== Returns
1179
+ # nil
1180
+ #
1181
+ # @api plugin
839
1182
  def self.run
840
1183
  require 'rack'
841
1184
  if File.exists?(Merb.dir_for(:config) / "rack.rb")
@@ -855,12 +1198,23 @@ class Merb::BootLoader::RackUpApplication < Merb::BootLoader
855
1198
  }.to_app
856
1199
  end
857
1200
 
1201
+ nil
858
1202
  end
859
1203
  end
860
1204
 
861
1205
  class Merb::BootLoader::ReloadClasses < Merb::BootLoader
862
1206
 
863
1207
  class TimedExecutor
1208
+ # Executes the associated block every @seconds@ seconds in a separate thread.
1209
+ #
1210
+ # ==== Parameters
1211
+ # seconds<Integer>:: Number of seconds to sleep in between runs of &block.
1212
+ # &block:: The block to execute periodically.
1213
+ #
1214
+ # ==== Returns
1215
+ # Thread:: The thread executing the block periodically.
1216
+ #
1217
+ # @api private
864
1218
  def self.every(seconds, &block)
865
1219
  Thread.abort_on_exception = true
866
1220
  Thread.new do
@@ -873,7 +1227,14 @@ class Merb::BootLoader::ReloadClasses < Merb::BootLoader
873
1227
  end
874
1228
  end
875
1229
 
876
- # Setup the class reloader if it's been specified in config.
1230
+ # Set up the class reloader if class reloading is enabled. This checks periodically
1231
+ # for modifications to files loaded by the LoadClasses BootLoader and reloads them
1232
+ # when they are modified.
1233
+ #
1234
+ # ==== Returns
1235
+ # nil
1236
+ #
1237
+ # @api plugin
877
1238
  def self.run
878
1239
  return unless Merb::Config[:reload_classes]
879
1240
 
@@ -883,7 +1244,7 @@ class Merb::BootLoader::ReloadClasses < Merb::BootLoader
883
1244
  next unless glob
884
1245
  paths << Dir[path / glob]
885
1246
  end
886
-
1247
+
887
1248
  if Merb.dir_for(:application) && File.file?(Merb.dir_for(:application))
888
1249
  paths << Merb.dir_for(:application)
889
1250
  end
@@ -895,15 +1256,23 @@ class Merb::BootLoader::ReloadClasses < Merb::BootLoader
895
1256
  reload(paths)
896
1257
  end
897
1258
 
1259
+ nil
898
1260
  end
899
1261
 
900
- # Reloads all files.
1262
+ # Reloads all files which have been modified since they were last loaded.
1263
+ #
1264
+ # ==== Returns
1265
+ # nil
1266
+ #
1267
+ # @api private
901
1268
  def self.reload(paths)
902
1269
  paths.each do |file|
903
- next if LoadClasses::MTIMES[file] &&
1270
+ next if LoadClasses::MTIMES[file] &&
904
1271
  LoadClasses::MTIMES[file] == File.mtime(file)
905
-
1272
+
906
1273
  LoadClasses.reload(file)
907
1274
  end
1275
+
1276
+ nil
908
1277
  end
909
1278
  end