rails 1.1.6 → 1.2.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 (104) hide show
  1. data/CHANGELOG +267 -2
  2. data/MIT-LICENSE +1 -1
  3. data/README +62 -63
  4. data/Rakefile +26 -15
  5. data/bin/process/inspector +3 -0
  6. data/configs/databases/frontbase.yml +28 -0
  7. data/configs/databases/mysql.yml +3 -2
  8. data/configs/databases/oracle.yml +10 -1
  9. data/configs/databases/sqlite3.yml +3 -0
  10. data/configs/lighttpd.conf +1 -0
  11. data/configs/routes.rb +1 -0
  12. data/environments/boot.rb +4 -3
  13. data/environments/environment.rb +9 -2
  14. data/environments/production.rb +1 -1
  15. data/helpers/application.rb +5 -2
  16. data/html/404.html +27 -5
  17. data/html/500.html +27 -5
  18. data/html/javascripts/controls.js +41 -23
  19. data/html/javascripts/dragdrop.js +105 -76
  20. data/html/javascripts/effects.js +293 -163
  21. data/html/javascripts/prototype.js +897 -389
  22. data/lib/breakpoint.rb +31 -1
  23. data/lib/breakpoint_client.rb +5 -5
  24. data/lib/code_statistics.rb +1 -1
  25. data/lib/commands/performance/profiler.rb +25 -9
  26. data/lib/commands/plugin.rb +69 -23
  27. data/lib/commands/process/inspector.rb +68 -0
  28. data/lib/commands/process/reaper.rb +88 -69
  29. data/lib/commands/process/spawner.rb +148 -33
  30. data/lib/commands/runner.rb +27 -6
  31. data/lib/commands/server.rb +18 -9
  32. data/lib/commands/servers/base.rb +19 -0
  33. data/lib/commands/servers/lighttpd.rb +20 -18
  34. data/lib/commands/servers/mongrel.rb +65 -0
  35. data/lib/console_sandbox.rb +2 -2
  36. data/lib/dispatcher.rb +67 -11
  37. data/lib/fcgi_handler.rb +52 -34
  38. data/lib/initializer.rb +190 -111
  39. data/lib/rails/version.rb +2 -2
  40. data/lib/rails_generator/base.rb +82 -24
  41. data/lib/rails_generator/commands.rb +87 -25
  42. data/lib/rails_generator/generated_attribute.rb +42 -0
  43. data/lib/rails_generator/generators/applications/app/app_generator.rb +13 -10
  44. data/lib/rails_generator/generators/components/controller/controller_generator.rb +1 -2
  45. data/lib/rails_generator/generators/components/mailer/mailer_generator.rb +10 -8
  46. data/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml +1 -1
  47. data/lib/rails_generator/generators/components/mailer/templates/unit_test.rb +4 -4
  48. data/lib/rails_generator/generators/components/mailer/templates/view.rhtml +1 -1
  49. data/lib/rails_generator/generators/components/migration/templates/migration.rb +1 -1
  50. data/lib/rails_generator/generators/components/model/USAGE +19 -12
  51. data/lib/rails_generator/generators/components/model/model_generator.rb +4 -0
  52. data/lib/rails_generator/generators/components/model/templates/fixtures.yml +8 -2
  53. data/lib/rails_generator/generators/components/model/templates/migration.rb +3 -1
  54. data/lib/rails_generator/generators/components/observer/USAGE +15 -0
  55. data/lib/rails_generator/generators/components/observer/observer_generator.rb +16 -0
  56. data/lib/rails_generator/generators/components/observer/templates/observer.rb +2 -0
  57. data/lib/rails_generator/generators/components/observer/templates/unit_test.rb +10 -0
  58. data/lib/rails_generator/generators/components/plugin/plugin_generator.rb +4 -0
  59. data/lib/rails_generator/generators/components/plugin/templates/uninstall.rb +1 -0
  60. data/lib/rails_generator/generators/components/resource/resource_generator.rb +76 -0
  61. data/lib/rails_generator/generators/components/resource/templates/USAGE +18 -0
  62. data/lib/rails_generator/generators/components/resource/templates/controller.rb +2 -0
  63. data/lib/rails_generator/generators/components/resource/templates/fixtures.yml +11 -0
  64. data/lib/rails_generator/generators/components/resource/templates/functional_test.rb +20 -0
  65. data/lib/rails_generator/generators/components/resource/templates/helper.rb +2 -0
  66. data/lib/rails_generator/generators/components/resource/templates/migration.rb +13 -0
  67. data/lib/rails_generator/generators/components/resource/templates/model.rb +2 -0
  68. data/lib/rails_generator/generators/components/resource/templates/unit_test.rb +10 -0
  69. data/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb +10 -1
  70. data/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb +11 -7
  71. data/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml +5 -1
  72. data/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml +2 -2
  73. data/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml +1 -1
  74. data/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml +2 -2
  75. data/lib/rails_generator/generators/components/scaffold_resource/USAGE +29 -0
  76. data/lib/rails_generator/generators/components/scaffold_resource/scaffold_resource_generator.rb +92 -0
  77. data/lib/rails_generator/generators/components/scaffold_resource/templates/controller.rb +79 -0
  78. data/lib/rails_generator/generators/components/scaffold_resource/templates/fixtures.yml +11 -0
  79. data/lib/rails_generator/generators/components/scaffold_resource/templates/functional_test.rb +57 -0
  80. data/lib/rails_generator/generators/components/scaffold_resource/templates/helper.rb +2 -0
  81. data/lib/rails_generator/generators/components/scaffold_resource/templates/layout.rhtml +17 -0
  82. data/lib/rails_generator/generators/components/scaffold_resource/templates/migration.rb +13 -0
  83. data/lib/rails_generator/generators/components/scaffold_resource/templates/model.rb +2 -0
  84. data/lib/rails_generator/generators/components/scaffold_resource/templates/style.css +74 -0
  85. data/lib/rails_generator/generators/components/scaffold_resource/templates/unit_test.rb +10 -0
  86. data/lib/rails_generator/generators/components/scaffold_resource/templates/view_edit.rhtml +19 -0
  87. data/lib/rails_generator/generators/components/scaffold_resource/templates/view_index.rhtml +24 -0
  88. data/lib/rails_generator/generators/components/scaffold_resource/templates/view_new.rhtml +18 -0
  89. data/lib/rails_generator/generators/components/scaffold_resource/templates/view_show.rhtml +10 -0
  90. data/lib/rails_generator/generators/components/session_migration/session_migration_generator.rb +7 -1
  91. data/lib/rails_generator/generators/components/session_migration/templates/migration.rb +5 -4
  92. data/lib/rails_generator/lookup.rb +1 -2
  93. data/lib/rails_generator/options.rb +6 -3
  94. data/lib/tasks/databases.rake +46 -20
  95. data/lib/tasks/documentation.rake +1 -0
  96. data/lib/tasks/framework.rake +1 -3
  97. data/lib/tasks/pre_namespace_aliases.rake +34 -27
  98. data/lib/tasks/rails.rb +2 -2
  99. data/lib/tasks/statistics.rake +6 -5
  100. data/lib/tasks/testing.rake +28 -13
  101. data/lib/tasks/tmp.rake +8 -1
  102. data/lib/test_help.rb +3 -2
  103. data/lib/webrick_server.rb +6 -8
  104. metadata +50 -9
@@ -16,7 +16,13 @@
16
16
  # license please contact me.
17
17
 
18
18
  require 'irb'
19
- require 'binding_of_caller'
19
+ if RUBY_VERSION == '1.8.5'
20
+ def Binding.of_caller(&block)
21
+ raise "Breakpoints are not currently working with Ruby 1.8.5"
22
+ end
23
+ else
24
+ require 'binding_of_caller'
25
+ end
20
26
  require 'drb'
21
27
  require 'drb/acl'
22
28
 
@@ -178,6 +184,30 @@ module Breakpoint
178
184
  end
179
185
  end
180
186
 
187
+ # Prints the source code surrounding the location where the
188
+ # breakpoint was issued.
189
+ def show_source_list(context = 5)
190
+ start_line, break_line, result = source_lines(context, true)
191
+ offset = [(break_line + context).to_s.length, 4].max
192
+ result.each_with_index do |line, i|
193
+ mark = (start_line + i == break_line ? '->' : ' ')
194
+ client.puts("%0#{offset}d%s#{line}" % [start_line + i, mark])
195
+ end
196
+ Pathname.new(@__bp_file).cleanpath.to_s
197
+ end
198
+
199
+ # Prints the call stack.
200
+ def show_call_stack(depth = 10)
201
+ base = Pathname.new(RAILS_ROOT).cleanpath.to_s
202
+ caller[1..depth].each do |line|
203
+ line.sub!(/^[^:]*/) do |path|
204
+ Pathname.new(path).cleanpath.to_s
205
+ end
206
+ client.puts(line.index(base) == 0 ? line[(base.length + 1)..-1] : line)
207
+ end
208
+ "#{Pathname.new(@__bp_file).cleanpath.to_s}:#{@__bp_line}"
209
+ end
210
+
181
211
  # Lets an object that will forward method calls to the breakpoint
182
212
  # client. This is useful for outputting longer things at the client
183
213
  # and so on. You can for example do these things:
@@ -30,13 +30,13 @@ ARGV.options do |opts|
30
30
  "connections from the server.",
31
31
  "Default: Find a good URI automatically.",
32
32
  "Example: -c druby://localhost:12345"
33
- ) { |Options[:ClientURI]| }
33
+ ) { |v| Options[:ClientURI] = v }
34
34
 
35
35
  opts.on("-s", "--server-uri=uri",
36
36
  "Connect to the server specified at the",
37
37
  "specified uri.",
38
38
  "Default: druby://localhost:42531"
39
- ) { |Options[:ServerURI]| }
39
+ ) { |v| Options[:ServerURI] = v }
40
40
 
41
41
  opts.on("-R", "--retry-delay=delay", Integer,
42
42
  "Automatically try to reconnect to the",
@@ -45,14 +45,14 @@ ARGV.options do |opts|
45
45
  "A value of 0 disables automatical",
46
46
  "reconnecting completely.",
47
47
  "Default: 10"
48
- ) { |Options[:RetryDelay]| }
48
+ ) { |v| Options[:RetryDelay] = v }
49
49
 
50
50
  opts.on("-P", "--[no-]permanent",
51
51
  "Run the breakpoint client in permanent mode.",
52
52
  "This means that the client will keep continue",
53
53
  "running even after the server has closed the",
54
54
  "connection. Useful for example in Rails."
55
- ) { |Options[:Permanent]| }
55
+ ) { |v| Options[:Permanent] = v }
56
56
 
57
57
  opts.on("-V", "--[no-]verbose",
58
58
  "Run the breakpoint client in verbose mode.",
@@ -60,7 +60,7 @@ ARGV.options do |opts|
60
60
  "individual breakpoints. This might help in seeing",
61
61
  "that the breakpoint client is still alive, but adds",
62
62
  "quite a bit of clutter."
63
- ) { |Options[:Verbose]| }
63
+ ) { |v| Options[:Verbose] = v }
64
64
 
65
65
  opts.separator ""
66
66
 
@@ -83,7 +83,7 @@ class CodeStatistics #:nodoc:
83
83
  loc_over_m = (statistics["codelines"] / statistics["methods"]) - 2 rescue loc_over_m = 0
84
84
 
85
85
  start = if TEST_TYPES.include? name
86
- "| #{name.ljust(18)} "
86
+ "| #{name.ljust(20)} "
87
87
  else
88
88
  "| #{name.ljust(20)} "
89
89
  end
@@ -1,5 +1,5 @@
1
1
  if ARGV.empty?
2
- $stderr.puts "Usage: ./script/performance/profiler 'Person.expensive_method(10)' [times]"
2
+ $stderr.puts "Usage: ./script/performance/profiler 'Person.expensive_method(10)' [times] [flat|graph|graph_html]"
3
3
  exit(1)
4
4
  end
5
5
 
@@ -16,14 +16,30 @@ end
16
16
 
17
17
  # Use the ruby-prof extension if available. Fall back to stdlib profiler.
18
18
  begin
19
- require 'prof'
20
- $stderr.puts 'Using the ruby-prof extension.'
21
- Prof.clock_mode = Prof::GETTIMEOFDAY
22
- Prof.start
23
- profile_me
24
- results = Prof.stop
25
- require 'rubyprof_ext'
26
- Prof.print_profile(results, $stderr)
19
+ begin
20
+ require "ruby-prof"
21
+ $stderr.puts 'Using the ruby-prof extension.'
22
+ RubyProf.clock_mode = RubyProf::WALL_TIME
23
+ RubyProf.start
24
+ profile_me
25
+ results = RubyProf.stop
26
+ if ARGV[2]
27
+ printer_class = RubyProf.const_get((ARGV[2] + "_printer").classify)
28
+ else
29
+ printer_class = RubyProf::FlatPrinter
30
+ end
31
+ printer = printer_class.new(results)
32
+ printer.print($stderr, 0)
33
+ rescue LoadError
34
+ require "prof"
35
+ $stderr.puts 'Using the old ruby-prof extension.'
36
+ Prof.clock_mode = Prof::GETTIMEOFDAY
37
+ Prof.start
38
+ profile_me
39
+ results = Prof.stop
40
+ require 'rubyprof_ext'
41
+ Prof.print_profile(results, $stderr)
42
+ end
27
43
  rescue LoadError
28
44
  require 'profiler'
29
45
  $stderr.puts 'Using the standard Ruby profiler.'
@@ -136,7 +136,7 @@ class RailsEnvironment
136
136
  Tempfile.open("svn-set-prop") do |file|
137
137
  file.write(items)
138
138
  file.flush
139
- system("svn propset -q svn:externals -F #{file.path} \"#{root}/vendor/plugins\"")
139
+ system("svn propset -q svn:externals -F \"#{file.path}\" \"#{root}/vendor/plugins\"")
140
140
  end
141
141
  end
142
142
 
@@ -150,10 +150,18 @@ class Plugin
150
150
  guess_name(uri)
151
151
  end
152
152
 
153
+ def self.find(name)
154
+ name =~ /\// ? new(name) : Repositories.instance.find_plugin(name)
155
+ end
156
+
153
157
  def to_s
154
158
  "#{@name.ljust(30)}#{@uri}"
155
159
  end
156
160
 
161
+ def svn_url?
162
+ @uri =~ /svn(?:\+ssh)?:\/\/*/
163
+ end
164
+
157
165
  def installed?
158
166
  File.directory?("#{rails_env.root}/vendor/plugins/#{name}") \
159
167
  or rails_env.externals.detect{ |name, repo| self.uri == repo }
@@ -161,7 +169,7 @@ class Plugin
161
169
 
162
170
  def install(method=nil, options = {})
163
171
  method ||= rails_env.best_install_method?
164
- method = :export if method == :http and @uri =~ /svn:\/\/*/
172
+ method = :export if method == :http and svn_url?
165
173
 
166
174
  uninstall if installed? and options[:force]
167
175
 
@@ -177,6 +185,7 @@ class Plugin
177
185
  path = "#{rails_env.root}/vendor/plugins/#{name}"
178
186
  if File.directory?(path)
179
187
  puts "Removing 'vendor/plugins/#{name}'" if $verbose
188
+ run_uninstall_hook
180
189
  rm_r path
181
190
  else
182
191
  puts "Plugin doesn't exist: #{path}"
@@ -187,6 +196,20 @@ class Plugin
187
196
  rails_env.externals = externals
188
197
  end
189
198
 
199
+ def info
200
+ tmp = "#{rails_env.root}/_tmp_about.yml"
201
+ if svn_url?
202
+ cmd = "svn export #{@uri} \"#{rails_env.root}/#{tmp}\""
203
+ puts cmd if $verbose
204
+ system(cmd)
205
+ end
206
+ open(svn_url? ? tmp : File.join(@uri, 'about.yml')) do |stream|
207
+ stream.read
208
+ end rescue "No about.yml found in #{uri}"
209
+ ensure
210
+ FileUtils.rm_rf tmp if svn_url?
211
+ end
212
+
190
213
  private
191
214
 
192
215
  def run_install_hook
@@ -194,6 +217,11 @@ class Plugin
194
217
  load install_hook_file if File.exists? install_hook_file
195
218
  end
196
219
 
220
+ def run_uninstall_hook
221
+ uninstall_hook_file = "#{rails_env.root}/vendor/plugins/#{name}/uninstall.rb"
222
+ load uninstall_hook_file if File.exists? uninstall_hook_file
223
+ end
224
+
197
225
  def install_using_export(options = {})
198
226
  svn_command :export, options
199
227
  end
@@ -442,7 +470,7 @@ module Commands
442
470
  options.parse!(general)
443
471
 
444
472
  command = general.shift
445
- if command =~ /^(list|discover|install|source|unsource|sources|remove|update)$/
473
+ if command =~ /^(list|discover|install|source|unsource|sources|remove|update|info)$/
446
474
  command = Commands.const_get(command.capitalize).new(self)
447
475
  command.parse!(sub)
448
476
  else
@@ -549,8 +577,8 @@ module Commands
549
577
  def options
550
578
  OptionParser.new do |o|
551
579
  o.set_summary_indent(' ')
552
- o.banner = "Usage: #{@base_command.script_name} source REPOSITORY"
553
- o.define_head "Add a new repository."
580
+ o.banner = "Usage: #{@base_command.script_name} source REPOSITORY [REPOSITORY [REPOSITORY]...]"
581
+ o.define_head "Add new repositories to the default search list."
554
582
  end
555
583
  end
556
584
 
@@ -656,13 +684,17 @@ module Commands
656
684
  puts "Scraping #{uri}" if $verbose
657
685
  dupes = []
658
686
  content = open(uri).each do |line|
659
- if line =~ /<a[^>]*href=['"]([^'"]*)['"]/ or line =~ /(svn:\/\/[^<|\n]*)/
660
- uri = $1
661
- if uri =~ /\/plugins\// and uri !~ /\/browser\//
662
- uri = extract_repository_uri(uri)
663
- yield uri unless dupes.include?(uri) or Repositories.instance.exist?(uri)
664
- dupes << uri
687
+ begin
688
+ if line =~ /<a[^>]*href=['"]([^'"]*)['"]/ || line =~ /(svn:\/\/[^<|\n]*)/
689
+ uri = $1
690
+ if uri =~ /^\w+:\/\// && uri =~ /\/plugins\// && uri !~ /\/browser\// && uri !~ /^http:\/\/wiki\.rubyonrails/ && uri !~ /http:\/\/instiki/
691
+ uri = extract_repository_uri(uri)
692
+ yield uri unless dupes.include?(uri) || Repositories.instance.exist?(uri)
693
+ dupes << uri
694
+ end
665
695
  end
696
+ rescue
697
+ puts "Problems scraping '#{uri}': #{$!.to_s}"
666
698
  end
667
699
  end
668
700
  end
@@ -729,19 +761,12 @@ module Commands
729
761
  environment = @base_command.environment
730
762
  install_method = determine_install_method
731
763
  puts "Plugins will be installed using #{install_method}" if $verbose
732
- args.each do |name|
733
- if name =~ /\// then
734
- ::Plugin.new(name).install(install_method, @options)
735
- else
736
- plugin = Repositories.instance.find_plugin(name)
737
- unless plugin.nil?
738
- plugin.install(install_method, @options)
739
- else
740
- puts "Plugin not found: #{name}"
741
- exit 1
742
- end
743
- end
764
+ args.each do |name|
765
+ ::Plugin.find(name).install(install_method, @options)
744
766
  end
767
+ rescue
768
+ puts "Plugin not found: #{args.inspect}"
769
+ exit 1
745
770
  end
746
771
  end
747
772
 
@@ -802,6 +827,27 @@ module Commands
802
827
  end
803
828
  end
804
829
 
830
+ class Info
831
+ def initialize(base_command)
832
+ @base_command = base_command
833
+ end
834
+
835
+ def options
836
+ OptionParser.new do |o|
837
+ o.set_summary_indent(' ')
838
+ o.banner = "Usage: #{@base_command.script_name} info name [name]..."
839
+ o.define_head "Shows plugin info at {url}/about.yml."
840
+ end
841
+ end
842
+
843
+ def parse!(args)
844
+ options.parse!(args)
845
+ args.each do |name|
846
+ puts ::Plugin.find(name).info
847
+ puts
848
+ end
849
+ end
850
+ end
805
851
  end
806
852
 
807
853
  class RecursiveHTTPFetcher
@@ -0,0 +1,68 @@
1
+ require 'optparse'
2
+
3
+ if RUBY_PLATFORM =~ /mswin32/ 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])
@@ -4,89 +4,102 @@ require 'uri'
4
4
 
5
5
  if RUBY_PLATFORM =~ /mswin32/ 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
137
  opts.on("-a", "--action=name", "reload|graceful|kill (default: #{OPTIONS[:action]})", String) { |v| OPTIONS[:action] = v }
121
- opts.on("-d", "--dispatcher=path", "default: #{OPTIONS[:dispatcher]}", String) { |v| OPTIONS[:dispatcher] = 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])