rails 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rails might be problematic. Click here for more details.

Files changed (177) hide show
  1. data/CHANGELOG +1020 -10
  2. data/MIT-LICENSE +1 -1
  3. data/README +110 -60
  4. data/Rakefile +74 -139
  5. data/bin/about +1 -1
  6. data/bin/console +1 -1
  7. data/bin/destroy +1 -1
  8. data/bin/generate +1 -1
  9. data/bin/performance/request +3 -0
  10. data/bin/plugin +1 -1
  11. data/bin/process/{spinner → inspector} +1 -1
  12. data/bin/rails +10 -12
  13. data/bin/runner +1 -1
  14. data/bin/server +1 -1
  15. data/{lib/rails_info.rb → builtin/rails_info/rails/info.rb} +33 -14
  16. data/builtin/rails_info/rails/info_controller.rb +9 -0
  17. data/builtin/rails_info/rails/info_helper.rb +2 -0
  18. data/builtin/rails_info/rails_info_controller.rb +2 -0
  19. data/configs/apache.conf +1 -1
  20. data/configs/databases/frontbase.yml +28 -0
  21. data/configs/databases/mysql.yml +54 -0
  22. data/configs/databases/oracle.yml +39 -0
  23. data/configs/databases/postgresql.yml +48 -0
  24. data/configs/databases/sqlite2.yml +16 -0
  25. data/configs/databases/sqlite3.yml +19 -0
  26. data/configs/initializers/inflections.rb +10 -0
  27. data/configs/initializers/mime_types.rb +5 -0
  28. data/configs/lighttpd.conf +29 -15
  29. data/configs/routes.rb +27 -11
  30. data/doc/README_FOR_APP +1 -1
  31. data/environments/boot.rb +103 -14
  32. data/environments/development.rb +5 -6
  33. data/environments/environment.rb +36 -30
  34. data/environments/production.rb +2 -3
  35. data/environments/test.rb +5 -2
  36. data/fresh_rakefile +2 -2
  37. data/helpers/application.rb +8 -2
  38. data/helpers/test_helper.rb +10 -0
  39. data/html/404.html +27 -5
  40. data/html/422.html +30 -0
  41. data/html/500.html +27 -5
  42. data/html/index.html +6 -6
  43. data/html/javascripts/application.js +2 -0
  44. data/html/javascripts/controls.js +532 -319
  45. data/html/javascripts/dragdrop.js +521 -133
  46. data/html/javascripts/effects.js +708 -442
  47. data/html/javascripts/prototype.js +3393 -953
  48. data/html/robots.txt +5 -1
  49. data/lib/code_statistics.rb +2 -2
  50. data/lib/commands/console.rb +18 -9
  51. data/lib/commands/performance/profiler.rb +25 -9
  52. data/lib/commands/performance/request.rb +6 -0
  53. data/lib/commands/plugin.rb +196 -96
  54. data/lib/commands/process/inspector.rb +68 -0
  55. data/lib/commands/process/reaper.rb +90 -71
  56. data/lib/commands/process/spawner.rb +188 -21
  57. data/lib/commands/process/spinner.rb +3 -3
  58. data/lib/commands/runner.rb +28 -7
  59. data/lib/commands/server.rb +20 -9
  60. data/lib/commands/servers/base.rb +31 -0
  61. data/lib/commands/servers/lighttpd.rb +60 -26
  62. data/lib/commands/servers/mongrel.rb +69 -0
  63. data/lib/commands/servers/webrick.rb +18 -11
  64. data/lib/console_app.rb +30 -0
  65. data/lib/console_sandbox.rb +2 -2
  66. data/lib/console_with_helpers.rb +26 -0
  67. data/lib/dispatcher.rb +3 -78
  68. data/lib/fcgi_handler.rb +98 -64
  69. data/lib/initializer.rb +323 -194
  70. data/lib/rails/plugin/loader.rb +150 -0
  71. data/lib/rails/plugin/locator.rb +78 -0
  72. data/lib/rails/plugin.rb +84 -0
  73. data/lib/{rails_version.rb → rails/version.rb} +1 -1
  74. data/lib/rails_generator/base.rb +85 -25
  75. data/lib/rails_generator/commands.rb +122 -40
  76. data/lib/rails_generator/generated_attribute.rb +42 -0
  77. data/lib/rails_generator/generators/applications/app/USAGE +0 -7
  78. data/lib/rails_generator/generators/applications/app/app_generator.rb +67 -28
  79. data/lib/rails_generator/generators/components/controller/USAGE +11 -12
  80. data/lib/rails_generator/generators/components/controller/controller_generator.rb +2 -3
  81. data/lib/rails_generator/generators/components/controller/templates/functional_test.rb +1 -11
  82. data/lib/rails_generator/generators/components/controller/templates/{view.rhtml → view.html.erb} +0 -0
  83. data/lib/rails_generator/generators/components/integration_test/USAGE +8 -0
  84. data/lib/rails_generator/generators/components/integration_test/integration_test_generator.rb +16 -0
  85. data/lib/rails_generator/generators/components/integration_test/templates/integration_test.rb +10 -0
  86. data/lib/rails_generator/generators/components/mailer/USAGE +9 -11
  87. data/lib/rails_generator/generators/components/mailer/mailer_generator.rb +10 -8
  88. data/lib/rails_generator/generators/components/mailer/templates/fixture.erb +3 -0
  89. data/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml +0 -3
  90. data/lib/rails_generator/generators/components/mailer/templates/unit_test.rb +9 -25
  91. data/lib/rails_generator/generators/components/mailer/templates/view.erb +3 -0
  92. data/lib/rails_generator/generators/components/mailer/templates/view.rhtml +0 -3
  93. data/lib/rails_generator/generators/components/migration/USAGE +23 -8
  94. data/lib/rails_generator/generators/components/migration/migration_generator.rb +15 -2
  95. data/lib/rails_generator/generators/components/migration/templates/migration.rb +7 -3
  96. data/lib/rails_generator/generators/components/model/USAGE +21 -11
  97. data/lib/rails_generator/generators/components/model/model_generator.rb +28 -1
  98. data/lib/rails_generator/generators/components/model/templates/fixtures.yml +18 -4
  99. data/lib/rails_generator/generators/components/model/templates/migration.rb +16 -0
  100. data/lib/rails_generator/generators/components/model/templates/unit_test.rb +2 -4
  101. data/lib/rails_generator/generators/components/observer/USAGE +13 -0
  102. data/lib/rails_generator/generators/components/observer/observer_generator.rb +16 -0
  103. data/lib/rails_generator/generators/components/observer/templates/observer.rb +2 -0
  104. data/lib/rails_generator/generators/components/observer/templates/unit_test.rb +8 -0
  105. data/lib/rails_generator/generators/components/plugin/USAGE +10 -18
  106. data/lib/rails_generator/generators/components/plugin/plugin_generator.rb +6 -0
  107. data/lib/rails_generator/generators/components/plugin/templates/MIT-LICENSE +20 -0
  108. data/lib/rails_generator/generators/components/plugin/templates/README +10 -1
  109. data/lib/rails_generator/generators/components/plugin/templates/Rakefile +1 -1
  110. data/lib/rails_generator/generators/components/plugin/templates/USAGE +1 -1
  111. data/lib/rails_generator/generators/components/plugin/templates/init.rb +1 -1
  112. data/lib/rails_generator/generators/components/plugin/templates/install.rb +1 -0
  113. data/lib/rails_generator/generators/components/plugin/templates/plugin.rb +1 -1
  114. data/lib/rails_generator/generators/components/plugin/templates/tasks.rake +1 -1
  115. data/lib/rails_generator/generators/components/plugin/templates/uninstall.rb +1 -0
  116. data/lib/rails_generator/generators/components/resource/USAGE +23 -0
  117. data/lib/rails_generator/generators/components/resource/resource_generator.rb +74 -0
  118. data/lib/rails_generator/generators/components/resource/templates/controller.rb +2 -0
  119. data/lib/rails_generator/generators/components/resource/templates/functional_test.rb +8 -0
  120. data/lib/rails_generator/generators/components/resource/templates/helper.rb +2 -0
  121. data/lib/rails_generator/generators/components/scaffold/USAGE +24 -31
  122. data/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb +45 -137
  123. data/lib/rails_generator/generators/components/scaffold/templates/controller.rb +65 -34
  124. data/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb +23 -76
  125. data/lib/rails_generator/generators/components/scaffold/templates/layout.html.erb +17 -0
  126. data/lib/rails_generator/generators/components/scaffold/templates/style.css +5 -5
  127. data/lib/rails_generator/generators/components/scaffold/templates/view_edit.html.erb +19 -0
  128. data/lib/rails_generator/generators/components/scaffold/templates/view_index.html.erb +24 -0
  129. data/lib/rails_generator/generators/components/scaffold/templates/view_new.html.erb +18 -0
  130. data/lib/rails_generator/generators/components/scaffold/templates/view_show.html.erb +10 -0
  131. data/lib/rails_generator/generators/components/session_migration/USAGE +6 -11
  132. data/lib/rails_generator/generators/components/session_migration/session_migration_generator.rb +7 -1
  133. data/lib/rails_generator/generators/components/session_migration/templates/migration.rb +8 -7
  134. data/lib/rails_generator/lookup.rb +46 -12
  135. data/lib/rails_generator/options.rb +11 -8
  136. data/lib/rails_generator/scripts/destroy.rb +23 -0
  137. data/lib/rails_generator/scripts.rb +7 -4
  138. data/lib/rails_generator/secret_key_generator.rb +160 -0
  139. data/lib/rails_generator/spec.rb +1 -1
  140. data/lib/rails_generator.rb +1 -1
  141. data/lib/railties_path.rb +1 -1
  142. data/lib/ruby_version_check.rb +17 -0
  143. data/lib/source_annotation_extractor.rb +62 -0
  144. data/lib/tasks/annotations.rake +23 -0
  145. data/lib/tasks/databases.rake +328 -133
  146. data/lib/tasks/documentation.rake +72 -68
  147. data/lib/tasks/framework.rake +99 -58
  148. data/lib/tasks/log.rake +9 -0
  149. data/lib/tasks/misc.rake +2 -17
  150. data/lib/tasks/rails.rb +2 -2
  151. data/lib/tasks/routes.rake +17 -0
  152. data/lib/tasks/statistics.rake +10 -8
  153. data/lib/tasks/testing.rake +99 -31
  154. data/lib/tasks/tmp.rake +37 -0
  155. data/lib/test_help.rb +8 -5
  156. data/lib/webrick_server.rb +11 -16
  157. metadata +312 -272
  158. data/bin/breakpointer +0 -3
  159. data/builtin/controllers/rails_info_controller.rb +0 -11
  160. data/configs/database.yml +0 -85
  161. data/lib/binding_of_caller.rb +0 -85
  162. data/lib/breakpoint.rb +0 -523
  163. data/lib/breakpoint_client.rb +0 -196
  164. data/lib/commands/breakpointer.rb +0 -1
  165. data/lib/rails_generator/generators/components/scaffold/templates/form.rhtml +0 -3
  166. data/lib/rails_generator/generators/components/scaffold/templates/form_scaffolding.rhtml +0 -1
  167. data/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml +0 -13
  168. data/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml +0 -9
  169. data/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml +0 -27
  170. data/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml +0 -8
  171. data/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml +0 -8
  172. data/lib/rails_generator/generators/components/web_service/USAGE +0 -28
  173. data/lib/rails_generator/generators/components/web_service/templates/api_definition.rb +0 -5
  174. data/lib/rails_generator/generators/components/web_service/templates/controller.rb +0 -8
  175. data/lib/rails_generator/generators/components/web_service/templates/functional_test.rb +0 -19
  176. data/lib/rails_generator/generators/components/web_service/web_service_generator.rb +0 -29
  177. data/lib/tasks/javascripts.rake +0 -6
@@ -0,0 +1,68 @@
1
+ require 'optparse'
2
+
3
+ if RUBY_PLATFORM =~ /(:?mswin|mingw)/ then abort("Inspector is only for Unix") end
4
+
5
+ OPTIONS = {
6
+ :pid_path => File.expand_path(RAILS_ROOT + '/tmp/pids'),
7
+ :pattern => "dispatch.*.pid",
8
+ :ps => "ps -o pid,state,user,start,time,pcpu,vsz,majflt,command -p %s"
9
+ }
10
+
11
+ class Inspector
12
+ def self.inspect(pid_path, pattern)
13
+ new(pid_path, pattern).inspect
14
+ end
15
+
16
+ def initialize(pid_path, pattern)
17
+ @pid_path, @pattern = pid_path, pattern
18
+ end
19
+
20
+ def inspect
21
+ header = `#{OPTIONS[:ps] % 1}`.split("\n")[0] + "\n"
22
+ lines = pids.collect { |pid| `#{OPTIONS[:ps] % pid}`.split("\n")[1] }
23
+
24
+ puts(header + lines.join("\n"))
25
+ end
26
+
27
+ private
28
+ def pids
29
+ pid_files.collect do |pid_file|
30
+ File.read(pid_file).to_i
31
+ end
32
+ end
33
+
34
+ def pid_files
35
+ Dir.glob(@pid_path + "/" + @pattern)
36
+ end
37
+ end
38
+
39
+
40
+ ARGV.options do |opts|
41
+ opts.banner = "Usage: inspector [options]"
42
+
43
+ opts.separator ""
44
+
45
+ opts.on <<-EOF
46
+ Description:
47
+ Displays system information about Rails dispatchers (or other processes that use pid files) through
48
+ the ps command.
49
+
50
+ Examples:
51
+ inspector # default ps on all tmp/pids/dispatch.*.pid files
52
+ inspector -s 'ps -o user,start,majflt,pcpu,vsz -p %s' # custom ps, %s is where the pid is interleaved
53
+ EOF
54
+
55
+ opts.on(" Options:")
56
+
57
+ opts.on("-s", "--ps=command", "default: #{OPTIONS[:ps]}", String) { |v| OPTIONS[:ps] = v }
58
+ opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String) { |v| OPTIONS[:pid_path] = v }
59
+ opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String) { |v| OPTIONS[:pattern] = v }
60
+
61
+ opts.separator ""
62
+
63
+ opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
64
+
65
+ opts.parse!
66
+ end
67
+
68
+ Inspector.inspect(OPTIONS[:pid_path], OPTIONS[:pattern])
@@ -2,91 +2,104 @@ require 'optparse'
2
2
  require 'net/http'
3
3
  require 'uri'
4
4
 
5
- if RUBY_PLATFORM =~ /mswin32/ then abort("Reaper is only for Unix") end
5
+ if RUBY_PLATFORM =~ /(:?mswin|mingw)/ then abort("Reaper is only for Unix") end
6
6
 
7
- # Instances of this class represent a single running process. Processes may
8
- # be queried by "keyword" to find those that meet a specific set of criteria.
9
- class ProgramProcess
7
+ class Killer
10
8
  class << self
11
-
12
9
  # Searches for all processes matching the given keywords, and then invokes
13
10
  # a specific action on each of them. This is useful for (e.g.) reloading a
14
11
  # set of processes:
15
12
  #
16
- # ProgramProcess.process_keywords(:reload, "basecamp")
17
- def process_keywords(action, *keywords)
18
- processes = keywords.collect { |keyword| find_by_keyword(keyword) }.flatten
19
-
20
- if processes.empty?
21
- puts "Couldn't find any process matching: #{keywords.join(" or ")}"
22
- else
23
- processes.each do |process|
24
- puts "#{action.capitalize}ing #{process}"
25
- process.send(action)
26
- end
27
- end
13
+ # Killer.process(:reload, "/tmp/pids", "dispatcher.*.pid")
14
+ def process(action, pid_path, pattern, keyword)
15
+ new(pid_path, pattern, keyword).process(action)
28
16
  end
29
17
 
30
- # Searches for all processes matching the given keyword:
31
- #
32
- # ProgramProcess.find_by_keyword("basecamp")
33
- def find_by_keyword(keyword)
34
- process_lines_with_keyword(keyword).split("\n").collect { |line|
35
- next if line =~ /inq|ps axww|grep|spawn-fcgi|spawner|reaper/
36
- pid, *command = line.split
37
- new(pid, command.join(" "))
38
- }.compact
18
+ # Forces the (rails) application to reload by sending a +HUP+ signal to the
19
+ # process.
20
+ def reload(pid)
21
+ `kill -s HUP #{pid}`
39
22
  end
40
23
 
41
- private
42
- def process_lines_with_keyword(keyword)
43
- `ps axww -o 'pid command' | grep #{keyword}`
44
- end
45
- end
24
+ # Force the (rails) application to restart by sending a +USR2+ signal to the
25
+ # process.
26
+ def restart(pid)
27
+ `kill -s USR2 #{pid}`
28
+ end
46
29
 
47
- # Create a new ProgramProcess instance that represents the process with the
48
- # given pid, running the given command.
49
- def initialize(pid, command)
50
- @pid, @command = pid, command
51
- end
30
+ # Forces the (rails) application to gracefully terminate by sending a
31
+ # +TERM+ signal to the process.
32
+ def graceful(pid)
33
+ `kill -s TERM #{pid}`
34
+ end
52
35
 
53
- # Forces the (rails) application to reload by sending a +HUP+ signal to the
54
- # process.
55
- def reload
56
- `kill -s HUP #{@pid}`
57
- end
36
+ # Forces the (rails) application to terminate immediately by sending a -9
37
+ # signal to the process.
38
+ def kill(pid)
39
+ `kill -9 #{pid}`
40
+ end
58
41
 
59
- # Forces the (rails) application to gracefully terminate by sending a
60
- # +TERM+ signal to the process.
61
- def graceful
62
- `kill -s TERM #{@pid}`
42
+ # Send a +USR1+ signal to the process.
43
+ def usr1(pid)
44
+ `kill -s USR1 #{pid}`
45
+ end
63
46
  end
64
47
 
65
- # Forces the (rails) application to terminate immediately by sending a -9
66
- # signal to the process.
67
- def kill
68
- `kill -9 #{@pid}`
48
+ def initialize(pid_path, pattern, keyword=nil)
49
+ @pid_path, @pattern, @keyword = pid_path, pattern, keyword
69
50
  end
70
51
 
71
- # Send a +USR1+ signal to the process.
72
- def usr1
73
- `kill -s USR1 #{@pid}`
74
- end
52
+ def process(action)
53
+ pids = find_processes
75
54
 
76
- # Force the (rails) application to restart by sending a +USR2+ signal to the
77
- # process.
78
- def restart
79
- `kill -s USR2 #{@pid}`
55
+ if pids.empty?
56
+ warn "Couldn't find any pid file in '#{@pid_path}' matching '#{@pattern}'"
57
+ warn "(also looked for processes matching #{@keyword.inspect})" if @keyword
58
+ else
59
+ pids.each do |pid|
60
+ puts "#{action.capitalize}ing #{pid}"
61
+ self.class.send(action, pid)
62
+ end
63
+
64
+ delete_pid_files if terminating?(action)
65
+ end
80
66
  end
67
+
68
+ private
69
+ def terminating?(action)
70
+ [ "kill", "graceful" ].include?(action)
71
+ end
72
+
73
+ def find_processes
74
+ files = pid_files
75
+ if files.empty?
76
+ find_processes_via_grep
77
+ else
78
+ files.collect { |pid_file| File.read(pid_file).to_i }
79
+ end
80
+ end
81
81
 
82
- def to_s #:nodoc:
83
- "[#{@pid}] #{@command}"
84
- end
82
+ def find_processes_via_grep
83
+ lines = `ps axww -o 'pid command' | grep #{@keyword}`.split(/\n/).
84
+ reject { |line| line =~ /inq|ps axww|grep|spawn-fcgi|spawner|reaper/ }
85
+ lines.map { |line| line[/^\s*(\d+)/, 1].to_i }
86
+ end
87
+
88
+ def delete_pid_files
89
+ pid_files.each { |pid_file| File.delete(pid_file) }
90
+ end
91
+
92
+ def pid_files
93
+ Dir.glob(@pid_path + "/" + @pattern)
94
+ end
85
95
  end
86
96
 
97
+
87
98
  OPTIONS = {
88
- :action => "restart",
89
- :dispatcher => File.expand_path(RAILS_ROOT + '/public/dispatch.fcgi')
99
+ :action => "restart",
100
+ :pid_path => File.expand_path(RAILS_ROOT + '/tmp/pids'),
101
+ :pattern => "dispatch.[0-9]*.pid",
102
+ :dispatcher => File.expand_path("#{RAILS_ROOT}/public/dispatch.fcgi")
90
103
  }
91
104
 
92
105
  ARGV.options do |opts|
@@ -96,9 +109,13 @@ ARGV.options do |opts|
96
109
 
97
110
  opts.on <<-EOF
98
111
  Description:
99
- The reaper is used to restart, reload, gracefully exit, and forcefully exit FCGI processes
100
- running a Rails Dispatcher. This is commonly done when a new version of the application
101
- is available, so the existing processes can be updated to use the latest code.
112
+ The reaper is used to restart, reload, gracefully exit, and forcefully exit processes
113
+ running a Rails Dispatcher (or any other process responding to the same signals). This
114
+ is commonly done when a new version of the application is available, so the existing
115
+ processes can be updated to use the latest code.
116
+
117
+ It uses pid files to work on the processes and by default assume them to be located
118
+ in RAILS_ROOT/tmp/pids.
102
119
 
103
120
  The reaper actions are:
104
121
 
@@ -110,15 +127,17 @@ ARGV.options do |opts|
110
127
  Restart is the most common and default action.
111
128
 
112
129
  Examples:
113
- reaper # restarts the default dispatcher
114
- reaper -a reload
115
- reaper -a exit -d /my/special/dispatcher.fcgi
130
+ reaper # restarts the default dispatchers
131
+ reaper -a reload # reload the default dispatchers
132
+ reaper -a kill -r *.pid # kill all processes that keep pids in tmp/pids
116
133
  EOF
117
134
 
118
135
  opts.on(" Options:")
119
136
 
120
- opts.on("-a", "--action=name", "reload|graceful|kill (default: #{OPTIONS[:action]})", String) { |OPTIONS[:action]| }
121
- opts.on("-d", "--dispatcher=path", "default: #{OPTIONS[:dispatcher]}", String) { |OPTIONS[:dispatcher]| }
137
+ opts.on("-a", "--action=name", "reload|graceful|kill (default: #{OPTIONS[:action]})", String) { |v| OPTIONS[:action] = v }
138
+ opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String) { |v| OPTIONS[:pid_path] = v }
139
+ opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String) { |v| OPTIONS[:pattern] = v }
140
+ opts.on("-d", "--dispatcher=path", "DEPRECATED. default: #{OPTIONS[:dispatcher]}", String) { |v| OPTIONS[:dispatcher] = v }
122
141
 
123
142
  opts.separator ""
124
143
 
@@ -127,4 +146,4 @@ ARGV.options do |opts|
127
146
  opts.parse!
128
147
  end
129
148
 
130
- ProgramProcess.process_keywords(OPTIONS[:action], OPTIONS[:dispatcher])
149
+ Killer.process(OPTIONS[:action], OPTIONS[:pid_path], OPTIONS[:pattern], OPTIONS[:dispatcher])
@@ -1,45 +1,200 @@
1
+ require 'active_support'
1
2
  require 'optparse'
3
+ require 'socket'
4
+ require 'fileutils'
2
5
 
3
- def spawn(port)
4
- print "Starting FCGI on port: #{port}\n "
5
- system("#{OPTIONS[:spawner]} -f #{OPTIONS[:dispatcher]} -p #{port}")
6
+ def daemonize #:nodoc:
7
+ exit if fork # Parent exits, child continues.
8
+ Process.setsid # Become session leader.
9
+ exit if fork # Zap session leader. See [1].
10
+ Dir.chdir "/" # Release old working directory.
11
+ File.umask 0000 # Ensure sensible umask. Adjust as needed.
12
+ STDIN.reopen "/dev/null" # Free file descriptors and
13
+ STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
14
+ STDERR.reopen STDOUT # STDOUT/ERR should better go to a logfile.
6
15
  end
7
16
 
17
+ class Spawner
18
+ def self.record_pid(name = "#{OPTIONS[:process]}.spawner", id = Process.pid)
19
+ FileUtils.mkdir_p(OPTIONS[:pids])
20
+ File.open(File.expand_path(OPTIONS[:pids] + "/#{name}.pid"), "w+") { |f| f.write(id) }
21
+ end
22
+
23
+ def self.spawn_all
24
+ OPTIONS[:instances].times do |i|
25
+ port = OPTIONS[:port] + i
26
+ print "Checking if something is already running on #{OPTIONS[:address]}:#{port}..."
27
+
28
+ begin
29
+ srv = TCPServer.new(OPTIONS[:address], port)
30
+ srv.close
31
+ srv = nil
32
+
33
+ puts "NO"
34
+ puts "Starting dispatcher on port: #{OPTIONS[:address]}:#{port}"
35
+
36
+ FileUtils.mkdir_p(OPTIONS[:pids])
37
+ spawn(port)
38
+ rescue
39
+ puts "YES"
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ class FcgiSpawner < Spawner
46
+ def self.spawn(port)
47
+ cmd = "#{OPTIONS[:spawner]} -f #{OPTIONS[:dispatcher]} -p #{port} -P #{OPTIONS[:pids]}/#{OPTIONS[:process]}.#{port}.pid"
48
+ cmd << " -a #{OPTIONS[:address]}" if can_bind_to_custom_address?
49
+ system(cmd)
50
+ end
51
+
52
+ def self.can_bind_to_custom_address?
53
+ @@can_bind_to_custom_address ||= /^\s-a\s/.match `#{OPTIONS[:spawner]} -h`
54
+ end
55
+ end
56
+
57
+ class MongrelSpawner < Spawner
58
+ def self.spawn(port)
59
+ cmd =
60
+ "mongrel_rails start -d " +
61
+ "-a #{OPTIONS[:address]} " +
62
+ "-p #{port} " +
63
+ "-P #{OPTIONS[:pids]}/#{OPTIONS[:process]}.#{port}.pid " +
64
+ "-e #{OPTIONS[:environment]} " +
65
+ "-c #{OPTIONS[:rails_root]} " +
66
+ "-l #{OPTIONS[:rails_root]}/log/mongrel.log"
67
+
68
+ # Add prefix functionality to spawner's call to mongrel_rails
69
+ # Digging through monrel's project subversion server, the earliest
70
+ # Tag that has prefix implemented in the bin/mongrel_rails file
71
+ # is 0.3.15 which also happens to be the earilest tag listed.
72
+ # References: http://mongrel.rubyforge.org/svn/tags
73
+ if Mongrel::Const::MONGREL_VERSION.to_f >=0.3 && !OPTIONS[:prefix].nil?
74
+ cmd = cmd + " --prefix #{OPTIONS[:prefix]}"
75
+ end
76
+ system(cmd)
77
+ end
78
+
79
+ def self.can_bind_to_custom_address?
80
+ true
81
+ end
82
+ end
83
+
84
+
85
+ begin
86
+ require_library_or_gem 'fcgi'
87
+ rescue Exception
88
+ # FCGI not available
89
+ end
90
+
91
+ begin
92
+ require_library_or_gem 'mongrel'
93
+ rescue Exception
94
+ # Mongrel not available
95
+ end
96
+
97
+ server = case ARGV.first
98
+ when "fcgi", "mongrel"
99
+ ARGV.shift
100
+ else
101
+ if defined?(Mongrel)
102
+ "mongrel"
103
+ elsif RUBY_PLATFORM !~ /(:?mswin|mingw)/ && !silence_stderr { `spawn-fcgi -version` }.blank? && defined?(FCGI)
104
+ "fcgi"
105
+ end
106
+ end
107
+
108
+ case server
109
+ when "fcgi"
110
+ puts "=> Starting FCGI dispatchers"
111
+ spawner_class = FcgiSpawner
112
+ when "mongrel"
113
+ puts "=> Starting mongrel dispatchers"
114
+ spawner_class = MongrelSpawner
115
+ else
116
+ puts "Neither FCGI (spawn-fcgi) nor Mongrel was installed and available!"
117
+ exit(0)
118
+ end
119
+
120
+
121
+
8
122
  OPTIONS = {
9
123
  :environment => "production",
10
124
  :spawner => '/usr/bin/env spawn-fcgi',
11
- :dispatcher => File.expand_path(RAILS_ROOT + '/public/dispatch.fcgi'),
125
+ :dispatcher => File.expand_path(RELATIVE_RAILS_ROOT + '/public/dispatch.fcgi'),
126
+ :pids => File.expand_path(RELATIVE_RAILS_ROOT + "/tmp/pids"),
127
+ :rails_root => File.expand_path(RELATIVE_RAILS_ROOT),
128
+ :process => "dispatch",
12
129
  :port => 8000,
13
- :instances => 3
130
+ :address => '0.0.0.0',
131
+ :instances => 3,
132
+ :repeat => nil,
133
+ :prefix => nil
14
134
  }
15
135
 
16
136
  ARGV.options do |opts|
17
- opts.banner = "Usage: spawner [options]"
137
+ opts.banner = "Usage: spawner [platform] [options]"
18
138
 
19
139
  opts.separator ""
20
140
 
21
141
  opts.on <<-EOF
22
142
  Description:
23
- The spawner is a wrapper for spawn-fcgi that makes it easier to start multiple FCGI
24
- processes running the Rails dispatcher. The spawn-fcgi command is included with the lighttpd
25
- web server, but can be used with both Apache and lighttpd (and any other web server supporting
26
- externally managed FCGI processes).
143
+ The spawner is a wrapper for spawn-fcgi and mongrel that makes it
144
+ easier to start multiple processes running the Rails dispatcher. The
145
+ spawn-fcgi command is included with the lighttpd web server, but can
146
+ be used with both Apache and lighttpd (and any other web server
147
+ supporting externally managed FCGI processes). Mongrel automatically
148
+ ships with with mongrel_rails for starting dispatchers.
149
+
150
+ The first choice you need to make is whether to spawn the Rails
151
+ dispatchers as FCGI or Mongrel. By default, this spawner will prefer
152
+ Mongrel, so if that's installed, and no platform choice is made,
153
+ Mongrel is used.
27
154
 
28
- You decide a starting port (default is 8000) and the number of FCGI process instances you'd
29
- like to run. So if you pick 9100 and 3 instances, you'll start processes on 9100, 9101, and 9102.
155
+ Then decide a starting port (default is 8000) and the number of FCGI
156
+ process instances you'd like to run. So if you pick 9100 and 3
157
+ instances, you'll start processes on 9100, 9101, and 9102.
30
158
 
31
- Examples:
32
- spawner # starts instances on 8000, 8001, and 8002
33
- spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to 9109
159
+ By setting the repeat option, you get a protection loop, which will
160
+ attempt to restart any FCGI processes that might have been exited or
161
+ outright crashed.
162
+
163
+ You can select bind address for started processes. By default these
164
+ listen on every interface. For single machine installations you would
165
+ probably want to use 127.0.0.1, hiding them form the outside world.
166
+
167
+ Examples:
168
+ spawner # starts instances on 8000, 8001, and 8002
169
+ # using Mongrel if available.
170
+ spawner fcgi # starts instances on 8000, 8001, and 8002
171
+ # using FCGI.
172
+ spawner mongrel -i 5 # starts instances on 8000, 8001, 8002,
173
+ # 8003, and 8004 using Mongrel.
174
+ spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to
175
+ # 9109 using Mongrel if available.
176
+ spawner -p 9100 -r 5 # starts 3 instances counting from 9100 to
177
+ # 9102 and attempts start them every 5
178
+ # seconds.
179
+ spawner -a 127.0.0.1 # starts 3 instances binding to localhost
34
180
  EOF
35
181
 
36
182
  opts.on(" Options:")
37
183
 
38
- opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |OPTIONS[:port]| }
39
- opts.on("-i", "--instances=number", Integer, "Number of instances (default: #{OPTIONS[:instances]})") { |OPTIONS[:instances]| }
40
- opts.on("-e", "--environment=name", String, "test|development|production (default: #{OPTIONS[:environment]})") { |OPTIONS[:environment]| }
41
- opts.on("-s", "--spawner=path", String, "default: #{OPTIONS[:spawner]}") { |OPTIONS[:spawner]| }
42
- opts.on("-d", "--dispatcher=path", String, "default: #{OPTIONS[:dispatcher]}") { |dispatcher| OPTIONS[:dispatcher] = File.expand_path(dispatcher) }
184
+ opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |OPTIONS[:port]| }
185
+
186
+ if spawner_class.can_bind_to_custom_address?
187
+ opts.on("-a", "--address=ip", String, "Bind to IP address (default: #{OPTIONS[:address]})") { |OPTIONS[:address]| }
188
+ end
189
+
190
+ opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |v| OPTIONS[:port] = v }
191
+ opts.on("-i", "--instances=number", Integer, "Number of instances (default: #{OPTIONS[:instances]})") { |v| OPTIONS[:instances] = v }
192
+ opts.on("-r", "--repeat=seconds", Integer, "Repeat spawn attempts every n seconds (default: off)") { |v| OPTIONS[:repeat] = v }
193
+ opts.on("-e", "--environment=name", String, "test|development|production (default: #{OPTIONS[:environment]})") { |v| OPTIONS[:environment] = v }
194
+ opts.on("-P", "--prefix=path", String, "URL prefix for Rails app. [Used only with Mongrel > v0.3.15]: (default: #{OPTIONS[:prefix]})") { |v| OPTIONS[:prefix] = v }
195
+ opts.on("-n", "--process=name", String, "default: #{OPTIONS[:process]}") { |v| OPTIONS[:process] = v }
196
+ opts.on("-s", "--spawner=path", String, "default: #{OPTIONS[:spawner]}") { |v| OPTIONS[:spawner] = v }
197
+ opts.on("-d", "--dispatcher=path", String, "default: #{OPTIONS[:dispatcher]}") { |dispatcher| OPTIONS[:dispatcher] = File.expand_path(dispatcher) }
43
198
 
44
199
  opts.separator ""
45
200
 
@@ -49,4 +204,16 @@ ARGV.options do |opts|
49
204
  end
50
205
 
51
206
  ENV["RAILS_ENV"] = OPTIONS[:environment]
52
- OPTIONS[:instances].times { |i| spawn(OPTIONS[:port] + i) }
207
+
208
+ if OPTIONS[:repeat]
209
+ daemonize
210
+ trap("TERM") { exit }
211
+ spawner_class.record_pid
212
+
213
+ loop do
214
+ spawner_class.spawn_all
215
+ sleep(OPTIONS[:repeat])
216
+ end
217
+ else
218
+ spawner_class.spawn_all
219
+ end
@@ -36,9 +36,9 @@ ARGV.options do |opts|
36
36
 
37
37
  opts.on(" Options:")
38
38
 
39
- opts.on("-c", "--command=path", String) { |OPTIONS[:command]| }
40
- opts.on("-i", "--interval=seconds", Float) { |OPTIONS[:interval]| }
41
- opts.on("-d", "--daemon") { |OPTIONS[:daemon]| }
39
+ opts.on("-c", "--command=path", String) { |v| OPTIONS[:command] = v }
40
+ opts.on("-i", "--interval=seconds", Float) { |v| OPTIONS[:interval] = v }
41
+ opts.on("-d", "--daemon") { |v| OPTIONS[:daemon] = v }
42
42
 
43
43
  opts.separator ""
44
44
 
@@ -1,27 +1,48 @@
1
1
  require 'optparse'
2
2
 
3
3
  options = { :environment => (ENV['RAILS_ENV'] || "development").dup }
4
+ code_or_file = nil
4
5
 
5
- ARGV.options do |opts|
6
+ ARGV.clone.options do |opts|
6
7
  script_name = File.basename($0)
7
- opts.banner = "Usage: runner 'puts Person.find(1).name' [options]"
8
+ opts.banner = "Usage: #{$0} [options] ('Some.ruby(code)' or a filename)"
8
9
 
9
10
  opts.separator ""
10
11
 
11
12
  opts.on("-e", "--environment=name", String,
12
13
  "Specifies the environment for the runner to operate under (test/development/production).",
13
- "Default: development") { |options[:environment]| }
14
+ "Default: development") { |v| options[:environment] = v }
14
15
 
15
16
  opts.separator ""
16
17
 
17
18
  opts.on("-h", "--help",
18
- "Show this help message.") { puts opts; exit }
19
-
20
- opts.parse!
19
+ "Show this help message.") { $stderr.puts opts; exit }
20
+
21
+ if RUBY_PLATFORM !~ /mswin/
22
+ opts.separator ""
23
+ opts.separator "You can also use runner as a shebang line for your scripts like this:"
24
+ opts.separator "-------------------------------------------------------------"
25
+ opts.separator "#!/usr/bin/env #{File.expand_path($0)}"
26
+ opts.separator ""
27
+ opts.separator "Product.find(:all).each { |p| p.price *= 2 ; p.save! }"
28
+ opts.separator "-------------------------------------------------------------"
29
+ end
30
+
31
+ opts.order! { |o| code_or_file ||= o } rescue retry
21
32
  end
22
33
 
34
+ ARGV.delete(code_or_file)
35
+
23
36
  ENV["RAILS_ENV"] = options[:environment]
24
37
  RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV)
25
38
 
26
39
  require RAILS_ROOT + '/config/environment'
27
- eval(ARGV.first)
40
+
41
+ if code_or_file.nil?
42
+ $stderr.puts "Run '#{$0} -h' for help."
43
+ exit 1
44
+ elsif File.exists?(code_or_file)
45
+ eval(File.read(code_or_file))
46
+ else
47
+ eval(code_or_file)
48
+ end
@@ -1,4 +1,5 @@
1
1
  require 'active_support'
2
+ require 'fileutils'
2
3
 
3
4
  begin
4
5
  require_library_or_gem 'fcgi'
@@ -6,23 +7,33 @@ rescue Exception
6
7
  # FCGI not available
7
8
  end
8
9
 
10
+ begin
11
+ require_library_or_gem 'mongrel'
12
+ rescue Exception
13
+ # Mongrel not available
14
+ end
15
+
9
16
  server = case ARGV.first
10
- when "lighttpd"
11
- ARGV.shift
12
- when "webrick"
17
+ when "lighttpd", "mongrel", "webrick"
13
18
  ARGV.shift
14
19
  else
15
- if RUBY_PLATFORM !~ /mswin/ && !silence_stderr { `lighttpd -version` }.blank? && defined?(FCGI)
20
+ if defined?(Mongrel)
21
+ "mongrel"
22
+ elsif RUBY_PLATFORM !~ /(:?mswin|mingw)/ && !silence_stderr { `lighttpd -version` }.blank? && defined?(FCGI)
16
23
  "lighttpd"
17
24
  else
18
25
  "webrick"
19
26
  end
20
27
  end
21
28
 
22
- if server == "webrick"
23
- puts "=> Booting WEBrick..."
24
- else
25
- puts "=> Booting lighttpd (use 'script/server webrick' to force WEBrick)"
29
+ case server
30
+ when "webrick"
31
+ puts "=> Booting WEBrick..."
32
+ when "lighttpd"
33
+ puts "=> Booting lighttpd (use 'script/server webrick' to force WEBrick)"
34
+ when "mongrel"
35
+ puts "=> Booting Mongrel (use 'script/server webrick' to force WEBrick)"
26
36
  end
27
37
 
28
- require "commands/servers/#{server}"
38
+ %w(cache pids sessions sockets).each { |dir_to_make| FileUtils.mkdir_p(File.join(RAILS_ROOT, 'tmp', dir_to_make)) }
39
+ require "commands/servers/#{server}"
@@ -0,0 +1,31 @@
1
+ def tail(log_file)
2
+ cursor = File.size(log_file)
3
+ last_checked = Time.now
4
+ tail_thread = Thread.new do
5
+ File.open(log_file, 'r') do |f|
6
+ loop do
7
+ f.seek cursor
8
+ if f.mtime > last_checked
9
+ last_checked = f.mtime
10
+ contents = f.read
11
+ cursor += contents.length
12
+ print contents
13
+ end
14
+ sleep 1
15
+ end
16
+ end
17
+ end
18
+ tail_thread
19
+ end
20
+
21
+ def start_debugger
22
+ begin
23
+ require_library_or_gem 'ruby-debug'
24
+ Debugger.start
25
+ Debugger.settings[:autoeval] = true if Debugger.respond_to?(:settings)
26
+ puts "=> Debugger enabled"
27
+ rescue Exception
28
+ puts "You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'"
29
+ exit
30
+ end
31
+ end