require 'open3' module Rails module Upgrading class ApplicationChecker def initialize @issues = [] raise NotInRailsAppError unless in_rails_app? end def in_rails_app? File.exist?("config/environment.rb") end # Run all the check methods def run the_methods = (self.public_methods - Object.methods) - ["run", "initialize"] the_methods.each {|m| send m } end # Check for deprecated ActiveRecord calls def check_ar_methods files = [] ["find(:all", "find(:first", ":conditions =>", ":joins =>"].each do |v| lines = grep_for(v, "app/") files += extract_filenames(lines) || [] end unless files.empty? alert( "Soon-to-be-deprecated ActiveRecord calls", "Methods such as find(:all), find(:first), finds with conditions, and the :joins option will soon be deprecated.", "http://m.onkey.org/2010/1/22/active-record-query-interface", files ) end lines = grep_for("named_scope", "app/models/") files = extract_filenames(lines) if files alert( "named_scope is now just scope", "The named_scope method has been renamed to just scope.", "http://github.com/rails/rails/commit/d60bb0a9e4be2ac0a9de9a69041a4ddc2e0cc914", files ) end end # Check for deprecated router syntax def check_routes lines = ["map.", "ActionController::Routing::Routes", ".resources"].map do |v| grep_for(v, "config/routes.rb").empty? ? nil : true end.compact unless lines.empty? alert( "Old router API", "The router API has totally changed.", "http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/", "config/routes.rb" ) end end # Check for old (pre-application.rb) environment.rb file def check_environment unless File.exist?("config/application.rb") alert( "New file needed: config/application.rb", "You need to add a config/application.rb.", "http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade", "config/application.rb" ) end lines = grep_for("config.", "config/environment.rb") unless lines.empty? alert( "Old environment.rb", "environment.rb doesn't do what it used to; you'll need to move some of that into application.rb.", "http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade", "config/environment.rb" ) end end # Check for old-style config.gem calls def check_gems lines = grep_for("config.gem ", "config/*.rb") files = extract_filenames(lines) if files alert( "Old gem bundling (config.gems)", "The old way of bundling is gone now. You need a Gemfile for bundler.", "http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade", files ) end end # Checks for old mailer syntax in both mailer classes and those # classes utilizing the mailers def check_mailers lines = grep_for("deliver_", "app/models/ #{base_path}app/controllers/ #{base_path}app/observers/") files = extract_filenames(lines) if files alert( "Deprecated ActionMailer API", "You're using the old ActionMailer API to send e-mails in a controller, model, or observer.", "http://lindsaar.net/2010/1/26/new-actionmailer-api-in-rails-3", files ) end files = [] ["recipients ", "attachment ", "subject ", "from "].each do |v| lines = grep_for(v, "app/models/") files += extract_filenames(lines) || [] end unless files.empty? alert( "Old ActionMailer class API", "You're using the old API in a mailer class.", "http://lindsaar.net/2010/1/26/new-actionmailer-api-in-rails-3", files ) end end # Checks for old-style generators def check_generators generators = Dir.glob(base_path + "vendor/plugins/**/generators/**/") unless generators.empty? files = generators.map do |g| grep_for("def manifest", g).empty? ? g : nil end.compact if files alert( "Old Rails generator API", "A plugin in the app is using the old generator API (a new one may be available at http://github.com/trydionel/rails3-generators).", "http://blog.plataformatec.com.br/2010/01/discovering-rails-3-generators/", files ) end end end # Checks a list of known broken plugins and gems def check_plugins # This list is off the wiki; will need to be updated often, esp. since RSpec is working on it bad_plugins = ["rspec", "rspec-rails", "hoptoad", "authlogic", "nifty-generators", "restful_authentication", "searchlogic", "cucumber", "cucumber-rails", "devise", "inherited_resources"] bad_plugins = bad_plugins.map do |p| p if File.exist?("#{base_path}vendor/plugins/#{p}") || !Dir.glob("#{base_path}vendor/gems/#{p}-*").empty? end.compact unless bad_plugins.empty? alert( "Known broken plugins", "At least one plugin in your app is broken (according to the wiki). Most of project maintainers are rapidly working towards compatability, but do be aware you may encounter issues.", "http://wiki.rubyonrails.org/rails/version3/plugins_and_gems", bad_plugins ) end end private # Find a string in a set of files; calls +find_with_grep+ and +find_with_rak+ # depending on platform. # # TODO: Figure out if this works on Windows. def grep_for(text, where = "./") # If they're on Windows, they probably don't have grep. @probably_has_grep ||= (Config::CONFIG['host_os'].downcase =~ /mswin|windows|mingw/).nil? if @probably_has_grep find_with_grep(text, base_path + where) else find_with_rak(text, base_path + where) end end # Sets a base path for finding files; mostly for testing def base_path Dir.pwd + "/" end # Use the grep utility to find a string in a set of files def find_with_grep(text, where) value = "" Open3.popen3("grep -r '#{text}' #{where}") do |stdin, stdout, stderr| value = stdout.read end value end # Use the rak gem to grep the files (not yet implemented) def find_with_rak(text, where) value = "" Open3.popen3("rak --nogroup -l '#{Regexp.escape(text)}' #{where}") do |stdin, stdout, stderr| value = stdout.read end value end # Extract the filenames from the grep output def extract_filenames(output) if @probably_has_grep extract_filenames_from_grep(output) else extract_filenames_from_rak(output) end end def extract_filenames_from_grep(output) return nil if output.empty? # I hate rescue nil as much as the next guy but I have a reason here at least... fnames = output.split("\n").map do |fn| fn.match(/^(.+?):/)[1] rescue nil end.compact fnames.uniq end def extract_filenames_from_rak(output) return nil if output.empty? output.split("\n").uniq end # Terminal colors, borrowed from Thor CLEAR = "\e[0m" BOLD = "\e[1m" RED = "\e[31m" YELLOW = "\e[33m" CYAN = "\e[36m" WHITE = "\e[37m" # Show an upgrade alert to the user def alert(title, text, more_info_url, culprits) if Config::CONFIG['host_os'].downcase =~ /mswin|windows|mingw/ basic_alert(title, text, more_info_url, culprits) else color_alert(title, text, more_info_url, culprits) end end # Show an upgrade alert to the user. If we're on Windows, we can't # use terminal colors, hence this method. def basic_alert(title, text, more_info_url, culprits) puts "** " + title puts text puts "More information: #{more_info_url}" puts puts "The culprits: " culprits.each do |c| puts "\t- #{c}" end puts end # Show a colorful alert to the user def color_alert(title, text, more_info_url, culprits) puts "#{RED}#{BOLD}#{title}#{CLEAR}" puts "#{WHITE}#{text}" puts "#{BOLD}More information:#{CLEAR} #{CYAN}#{more_info_url}" puts puts "#{WHITE}The culprits: " culprits.each do |c| puts "#{YELLOW}\t- #{c}" end ensure puts "#{CLEAR}" end end end end