diff options
Diffstat (limited to 'railties/lib')
74 files changed, 1107 insertions, 859 deletions
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index e9c96c7b43..ee48043a50 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -46,14 +46,14 @@ module Rails def backtrace_cleaner @backtrace_cleaner ||= begin - # Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded + # Relies on Active Support, so we have to lazy load to postpone definition until Active Support has been loaded require "rails/backtrace_cleaner" Rails::BacktraceCleaner.new end end # Returns a Pathname object of the current Rails project, - # otherwise it returns nil if there is no project: + # otherwise it returns +nil+ if there is no project: # # Rails.root # # => #<Pathname:/Users/someuser/some/path/project> @@ -100,7 +100,7 @@ module Rails end # Returns a Pathname object of the public folder of the current - # Rails project, otherwise it returns nil if there is no project: + # Rails project, otherwise it returns +nil+ if there is no project: # # Rails.public_path # # => #<Pathname:/Users/someuser/some/path/project/public> diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index b01196e3ed..f96432c89f 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -274,7 +274,7 @@ module Rails # Sends the initializers to the +initializer+ method defined in the # Rails::Initializable module. Each Rails::Application class has its own # set of initializers, as defined by the Initializable module. - def initializer(name, opts={}, &block) + def initializer(name, opts = {}, &block) self.class.initializer(name, opts, &block) end @@ -347,7 +347,7 @@ module Rails # Initialize the application passing the given group. By default, the # group is :default - def initialize!(group=:default) #:nodoc: + def initialize!(group = :default) #:nodoc: raise "Application has been already initialized." if @initialized run_initializers(group, self) @initialized = true @@ -394,8 +394,8 @@ module Rails shared_secrets = all_secrets["shared"] env_secrets = all_secrets[Rails.env] - secrets.merge!(shared_secrets.symbolize_keys) if shared_secrets - secrets.merge!(env_secrets.symbolize_keys) if env_secrets + secrets.merge!(shared_secrets.deep_symbolize_keys) if shared_secrets + secrets.merge!(env_secrets.deep_symbolize_keys) if env_secrets end # Fallback to config.secret_key_base if secrets.secret_key_base isn't set diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index 26ef3822ba..973b746068 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -7,9 +7,11 @@ Rails::AppLoader.exec_app require "rails/ruby_version_check" Signal.trap("INT") { puts; exit(1) } +require "rails/command" + if ARGV.first == "plugin" ARGV.shift - require "rails/commands/plugin" + Rails::Command.invoke :plugin, ARGV else - require "rails/commands/application" + Rails::Command.invoke :application, ARGV end diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index b3d88147a5..9c4bd16aad 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -106,7 +106,7 @@ class CodeStatistics #:nodoc: code = calculate_code tests = calculate_tests - puts " Code LOC: #{code} Test LOC: #{tests} Code to Test Ratio: 1:#{sprintf("%.1f", tests.to_f/code)}" + puts " Code LOC: #{code} Test LOC: #{tests} Code to Test Ratio: 1:#{sprintf("%.1f", tests.to_f / code)}" puts "" end end diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb new file mode 100644 index 0000000000..6065e78fd1 --- /dev/null +++ b/railties/lib/rails/command.rb @@ -0,0 +1,99 @@ +require "active_support" +require "active_support/dependencies/autoload" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/hash/transform_values" + +require "thor" + +module Rails + module Command + extend ActiveSupport::Autoload + + autoload :Behavior + autoload :Base + + include Behavior + + class << self + def hidden_commands # :nodoc: + @hidden_commands ||= [] + end + + def environment # :nodoc: + ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" + end + + # Receives a namespace, arguments and the behavior to invoke the command. + def invoke(namespace, args = [], **config) + namespace = namespace.to_s + namespace = "help" if namespace.blank? || Thor::HELP_MAPPINGS.include?(namespace) + namespace = "version" if %w( -v --version ).include? namespace + + if command = find_by_namespace(namespace) + command.perform(namespace, args, config) + else + find_by_namespace("rake").perform(namespace, args, config) + end + end + + # Rails finds namespaces similar to thor, it only adds one rule: + # + # Command names must end with "_command.rb". This is required because Rails + # looks in load paths and loads the command just before it's going to be used. + # + # find_by_namespace :webrat, :rails, :integration + # + # Will search for the following commands: + # + # "rails:webrat", "webrat:integration", "webrat" + # + # Notice that "rails:commands:webrat" could be loaded as well, what + # Rails looks for is the first and last parts of the namespace. + def find_by_namespace(name) # :nodoc: + lookups = [ name, "rails:#{name}" ] + + lookup(lookups) + + namespaces = subclasses.index_by(&:namespace) + namespaces[(lookups & namespaces.keys).first] + end + + # Returns the root of the Rails engine or app running the command. + def root + if defined?(ENGINE_ROOT) + Pathname.new(ENGINE_ROOT) + elsif defined?(APP_PATH) + Pathname.new(File.expand_path("../..", APP_PATH)) + end + end + + def print_commands # :nodoc: + sorted_groups.each { |b, n| print_list(b, n) } + end + + def sorted_groups # :nodoc: + lookup! + + groups = (subclasses - hidden_commands).group_by { |c| c.namespace.split(":").first } + groups.transform_values! { |commands| commands.flat_map(&:printing_commands).sort } + + rails = groups.delete("rails") + [[ "rails", rails ]] + groups.sort.to_a + end + + protected + def command_type + @command_type ||= "command" + end + + def lookup_paths + @lookup_paths ||= %w( rails/commands commands ) + end + + def file_lookup_paths + @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_command.rb" ] + end + end + end +end diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb new file mode 100644 index 0000000000..31b656ec31 --- /dev/null +++ b/railties/lib/rails/command/actions.rb @@ -0,0 +1,42 @@ +module Rails + module Command + module Actions + # Change to the application's path if there is no config.ru file in current directory. + # This allows us to run `rails server` from other directories, but still get + # the main config.ru and properly set the tmp directory. + def set_application_directory! + Dir.chdir(File.expand_path("../../", APP_PATH)) unless File.exist?(File.expand_path("config.ru")) + end + + if defined?(ENGINE_PATH) + def require_application_and_environment! + require ENGINE_PATH + end + + def load_tasks + Rake.application.init("rails") + Rake.application.load_rakefile + end + + def load_generators + engine = ::Rails::Engine.find(ENGINE_ROOT) + Rails::Generators.namespace = engine.railtie_namespace + engine.load_generators + end + else + def require_application_and_environment! + require APP_PATH + Rails.application.require_environment! + end + + def load_tasks + Rails.application.load_tasks + end + + def load_generators + Rails.application.load_generators + end + end + end + end +end diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb new file mode 100644 index 0000000000..1efcd69e63 --- /dev/null +++ b/railties/lib/rails/command/base.rb @@ -0,0 +1,135 @@ +require "thor" +require "erb" + +require "active_support/core_ext/string/filters" +require "active_support/core_ext/string/inflections" + +require "rails/command/actions" + +module Rails + module Command + class Base < Thor + class Error < Thor::Error # :nodoc: + end + + include Actions + + class << self + # Returns true when the app is a Rails engine. + def engine? + defined?(ENGINE_ROOT) + end + + # Tries to get the description from a USAGE file one folder above the command + # root. + def desc(usage = nil, description = nil) + if usage + super + else + @desc ||= ERB.new(File.read(usage_path)).result(binding) if usage_path + end + end + + # Convenience method to get the namespace from the class name. It's the + # same as Thor default except that the Command at the end of the class + # is removed. + def namespace(name = nil) + if name + super + else + @namespace ||= super.chomp("_command").sub(/:command:/, ":") + end + end + + # Convenience method to hide this command from the available ones when + # running rails command. + def hide_command! + Rails::Command.hidden_commands << self + end + + def inherited(base) #:nodoc: + super + + if base.name && base.name !~ /Base$/ + Rails::Command.subclasses << base + end + end + + def perform(command, args, config) # :nodoc: + command = nil if Thor::HELP_MAPPINGS.include?(args.first) + + dispatch(command, args.dup, nil, config) + end + + def printing_commands + namespace.sub(/^rails:/, "") + end + + def executable + "bin/rails #{command_name}" + end + + # Use Rails' default banner. + def banner(*) + "#{executable} #{arguments.map(&:usage).join(' ')} [options]".squish! + end + + # Sets the base_name taking into account the current class namespace. + # + # Rails::Command::TestCommand.base_name # => 'rails' + def base_name + @base_name ||= begin + if base = name.to_s.split("::").first + base.underscore + end + end + end + + # Return command name without namespaces. + # + # Rails::Command::TestCommand.command_name # => 'test' + def command_name + @command_name ||= begin + if command = name.to_s.split("::").last + command.chomp!("Command") + command.underscore + end + end + end + + # Path to lookup a USAGE description in a file. + def usage_path + if default_command_root + path = File.join(default_command_root, "USAGE") + path if File.exist?(path) + end + end + + # Default file root to place extra files a command might need, placed + # one folder above the command file. + # + # For a `Rails::Command::TestCommand` placed in `rails/command/test_command.rb` + # would return `rails/test`. + def default_command_root + path = File.expand_path(File.join("../commands", command_name), __dir__) + path if File.exist?(path) + end + + private + # Allow the command method to be called perform. + def create_command(meth) + if meth == "perform" + alias_method command_name, meth + else + # Prevent exception about command without usage. + # Some commands define their documentation differently. + @usage ||= "" + @desc ||= "" + + super + end + end + end + end + end +end diff --git a/railties/lib/rails/command/behavior.rb b/railties/lib/rails/command/behavior.rb new file mode 100644 index 0000000000..2e8517070c --- /dev/null +++ b/railties/lib/rails/command/behavior.rb @@ -0,0 +1,123 @@ +require "active_support" + +module Rails + module Command + module Behavior #:nodoc: + extend ActiveSupport::Concern + + class_methods do + # Remove the color from output. + def no_color! + Thor::Base.shell = Thor::Shell::Basic + end + + # Track all command subclasses. + def subclasses + @subclasses ||= [] + end + + protected + + # This code is based directly on the Text gem implementation. + # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher. + # + # Returns a value representing the "cost" of transforming str1 into str2. + def levenshtein_distance(str1, str2) + s = str1 + t = str2 + n = s.length + m = t.length + + return m if (0 == n) + return n if (0 == m) + + d = (0..m).to_a + x = nil + + # avoid duplicating an enumerable object in the loop + str2_codepoint_enumerable = str2.each_codepoint + + str1.each_codepoint.with_index do |char1, i| + e = i + 1 + + str2_codepoint_enumerable.with_index do |char2, j| + cost = (char1 == char2) ? 0 : 1 + x = [ + d[j + 1] + 1, # insertion + e + 1, # deletion + d[j] + cost # substitution + ].min + d[j] = e + e = x + end + + d[m] = x + end + + x + end + + # Prints a list of generators. + def print_list(base, namespaces) #:nodoc: + return if namespaces.empty? + puts "#{base.camelize}:" + + namespaces.each do |namespace| + puts(" #{namespace}") + end + + puts + end + + # Receives namespaces in an array and tries to find matching generators + # in the load path. + def lookup(namespaces) #:nodoc: + paths = namespaces_to_paths(namespaces) + + paths.each do |raw_path| + lookup_paths.each do |base| + path = "#{base}/#{raw_path}_#{command_type}" + + begin + require path + return + rescue LoadError => e + raise unless e.message =~ /#{Regexp.escape(path)}$/ + rescue Exception => e + warn "[WARNING] Could not load #{command_type} #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}" + end + end + end + end + + # This will try to load any command in the load path to show in help. + def lookup! #:nodoc: + $LOAD_PATH.each do |base| + Dir[File.join(base, *file_lookup_paths)].each do |path| + begin + path = path.sub("#{base}/", "") + require path + rescue Exception + # No problem + end + end + end + end + + # Convert namespaces to paths by replacing ":" for "/" and adding + # an extra lookup. For example, "rails:model" should be searched + # in both: "rails/model/model_generator" and "rails/model_generator". + def namespaces_to_paths(namespaces) #:nodoc: + paths = [] + namespaces.each do |namespace| + pieces = namespace.split(":") + paths << pieces.dup.push(pieces.last).join("/") + paths << pieces.join("/") + end + paths.uniq! + paths + end + end + end + end +end diff --git a/railties/lib/rails/command/environment_argument.rb b/railties/lib/rails/command/environment_argument.rb new file mode 100644 index 0000000000..05eac34155 --- /dev/null +++ b/railties/lib/rails/command/environment_argument.rb @@ -0,0 +1,34 @@ +require "active_support" + +module Rails + module Command + module EnvironmentArgument #:nodoc: + extend ActiveSupport::Concern + + included do + argument :environment, optional: true, banner: "environment" + end + + private + def extract_environment_option_from_argument + if environment + self.options = options.merge(environment: acceptable_environment(environment)) + elsif !options[:environment] + self.options = options.merge(environment: Rails::Command.environment) + end + end + + def acceptable_environment(env = nil) + if available_environments.include? env + env + else + %w( production development test ).detect { |e| e =~ /^#{env}/ } || env + end + end + + def available_environments + Dir["config/environments/*.rb"].map { |fname| File.basename(fname, ".*") } + end + end + end +end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index d64b355aec..fff0119c65 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -1,4 +1,4 @@ -ARGV << "--help" if ARGV.empty? +require "rails/command" aliases = { "g" => "generate", @@ -13,6 +13,4 @@ aliases = { command = ARGV.shift command = aliases[command] || command -require "rails/commands/commands_tasks" - -Rails::CommandsTasks.new(ARGV).run_command!(command) +Rails::Command.invoke command, ARGV diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application/application_command.rb index f6e7771cf3..7675d3b3d1 100644 --- a/railties/lib/rails/commands/application.rb +++ b/railties/lib/rails/commands/application/application_command.rb @@ -11,7 +11,19 @@ module Rails end end end -end -args = Rails::Generators::ARGVScrubber.new(ARGV).prepare! -Rails::Generators::AppGenerator.start args + module Command + class ApplicationCommand < Base # :nodoc: + hide_command! + + def help + perform # Punt help output to the generator. + end + + def perform(*args) + Rails::Generators::AppGenerator.start \ + Rails::Generators::ARGVScrubber.new(args).prepare! + end + end + end +end diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb deleted file mode 100644 index 43f9dd38f3..0000000000 --- a/railties/lib/rails/commands/commands_tasks.rb +++ /dev/null @@ -1,136 +0,0 @@ -require "rails/commands/rake_proxy" -require "rails/commands/common_commands_tasks" -require "active_support/core_ext/string/strip" - -module Rails - # This is a class which takes in a rails command and initiates the appropriate - # initiation sequence. - # - # Warning: This class mutates ARGV because some commands require manipulating - # it before they are run. - class CommandsTasks # :nodoc: - include Rails::RakeProxy - include Rails::CommonCommandsTasks - - attr_reader :argv - - ADDITIONAL_COMMANDS = [ - [ "destroy", 'Undo code generated with "generate" (short-cut alias: "d")' ], - [ "plugin new", "Generates skeleton for developing a Rails plugin" ], - [ "runner", - 'Run a piece of code in the application environment (short-cut alias: "r")' ] - ] - - def initialize(argv) - @argv = argv - end - - def plugin - require_command!("plugin") - end - - def console - require_command!("console") - options = Rails::Console.parse_arguments(argv) - - # RAILS_ENV needs to be set before config/application is required - ENV["RAILS_ENV"] = options[:environment] if options[:environment] - - # shift ARGV so IRB doesn't freak - shift_argv! - - require_application_and_environment! - Rails::Console.start(Rails.application, options) - end - - def server - set_application_directory! - require_command!("server") - - Rails::Server.new.tap do |server| - # We need to require application after the server sets environment, - # otherwise the --environment option given to the server won't propagate. - require APP_PATH - Dir.chdir(Rails.application.root) - server.start - end - end - - def dbconsole - require_command!("dbconsole") - Rails::DBConsole.start - end - - def runner - require_command!("runner") - end - - def new - if %w(-h --help).include?(argv.first) - require_command!("application") - else - exit_with_initialization_warning! - end - end - - private - - def exit_with_initialization_warning! - puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n" - puts "Type 'rails' for help." - exit(1) - end - - def shift_argv! - argv.shift if argv.first && argv.first[0] != "-" - end - - # Change to the application's path if there is no config.ru file in current directory. - # This allows us to run `rails server` from other directories, but still get - # the main config.ru and properly set the tmp directory. - def set_application_directory! - Dir.chdir(File.expand_path("../../", APP_PATH)) unless File.exist?(File.expand_path("config.ru")) - end - - def commands - ADDITIONAL_COMMANDS + formatted_rake_tasks - end - - def command_whitelist - %w(plugin generate destroy console server dbconsole runner new version help test) - end - - def help_message - <<-EOT.strip_heredoc - Usage: rails COMMAND [ARGS] - - The most common rails commands are: - generate Generate new code (short-cut alias: "g") - console Start the Rails console (short-cut alias: "c") - server Start the Rails server (short-cut alias: "s") - test Run tests (short-cut alias: "t") - dbconsole Start a console for the database specified in config/database.yml - (short-cut alias: "db") - new Create a new Rails application. "rails new my_app" creates a - new application called MyApp in "./my_app" - - All commands can be run with -h (or --help) for more information. - - In addition to those commands, there are: - EOT - end - - def require_application_and_environment! - require APP_PATH - Rails.application.require_environment! - end - - def load_tasks - Rails.application.load_tasks - end - - def load_generators - Rails.application.load_generators - end - end -end diff --git a/railties/lib/rails/commands/common_commands_tasks.rb b/railties/lib/rails/commands/common_commands_tasks.rb deleted file mode 100644 index c1484d7ae2..0000000000 --- a/railties/lib/rails/commands/common_commands_tasks.rb +++ /dev/null @@ -1,68 +0,0 @@ -module Rails - module CommonCommandsTasks # :nodoc: - def run_command!(command) - command = parse_command(command) - - if command_whitelist.include?(command) - send(command) - else - run_rake_task(command) - end - end - - def generate - generate_or_destroy(:generate) - end - - def destroy - generate_or_destroy(:destroy) - end - - def test - require_command!("test") - end - - def version - argv.unshift "--version" - require_command!("application") - end - - def help - write_help_message - write_commands(commands) - end - - private - - def generate_or_destroy(command) - require "rails/generators" - require_application_and_environment! - load_generators - require_command!(command) - end - - def require_command!(command) - require "rails/commands/#{command}" - end - - def write_help_message - puts help_message - end - - def write_commands(commands) - width = commands.map { |name, _| name.size }.max || 10 - commands.each { |command| printf(" %-#{width}s %s\n", *command) } - end - - def parse_command(command) - case command - when "--version", "-v" - "version" - when "--help", "-h" - "help" - else - command - end - end - end -end diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console/console_command.rb index e00887323e..62e3aa19df 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console/console_command.rb @@ -1,12 +1,10 @@ -require "optparse" require "irb" require "irb/completion" -require "rails/commands/console_helper" + +require "rails/command/environment_argument" module Rails class Console - include ConsoleHelper - module BacktraceCleaner def filter_backtrace(bt) if result = super @@ -15,26 +13,13 @@ module Rails end end - class << self - def parse_arguments(arguments) - options = {} - - OptionParser.new do |opt| - opt.banner = "Usage: rails console [environment] [options]" - opt.on("-s", "--sandbox", "Rollback database modifications on exit.") { |v| options[:sandbox] = v } - opt.on("-e", "--environment=name", String, - "Specifies the environment to run this console under (test/development/production).", - "Default: development") { |v| options[:environment] = v.strip } - opt.parse!(arguments) - end - - set_options_env(arguments, options) - end + def self.start(*args) + new(*args).start end attr_reader :options, :app, :console - def initialize(app, options={}) + def initialize(app, options = {}) @app = app @options = options @@ -53,7 +38,7 @@ module Rails end def environment - options[:environment] ||= super + options[:environment] end alias_method :environment?, :environment @@ -77,4 +62,28 @@ module Rails console.start end end + + module Command + class ConsoleCommand < Base # :nodoc: + include EnvironmentArgument + + class_option :sandbox, aliases: "-s", type: :boolean, default: false, + desc: "Rollback database modifications on exit." + + class_option :environment, aliases: "-e", type: :string, + desc: "Specifies the environment to run this console under (test/development/production)." + + def perform + extract_environment_option_from_argument + + # RAILS_ENV needs to be set before config/application is required. + ENV["RAILS_ENV"] = options[:environment] + + ARGV.clear # Clear ARGV so IRB doesn't freak. + + require_application_and_environment! + Rails::Console.start(Rails.application, options) + end + end + end end diff --git a/railties/lib/rails/commands/console_helper.rb b/railties/lib/rails/commands/console_helper.rb deleted file mode 100644 index 0b7f1c4249..0000000000 --- a/railties/lib/rails/commands/console_helper.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "active_support/concern" - -module Rails - module ConsoleHelper # :nodoc: - extend ActiveSupport::Concern - - module ClassMethods - def start(*args) - new(*args).start - end - - private - def set_options_env(arguments, options) - if arguments.first && arguments.first[0] != "-" - env = arguments.first - if available_environments.include? env - options[:environment] = env - else - options[:environment] = %w(production development test).detect { |e| e =~ /^#{env}/ } || env - end - end - options - end - - def available_environments - Dir["config/environments/*.rb"].map { |fname| File.basename(fname, ".*") } - end - end - - def environment - ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" - end - end -end diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb index 66b7a14f16..35e8673215 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb @@ -1,58 +1,17 @@ -require "erb" -require "yaml" -require "optparse" -require "rails/commands/console_helper" +require "rails/command/environment_argument" module Rails class DBConsole - include ConsoleHelper - - attr_reader :arguments - - class << self - def parse_arguments(arguments) - options = {} - - OptionParser.new do |opt| - opt.banner = "Usage: rails dbconsole [environment] [options]" - opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v| - options["include_password"] = true - end - - opt.on("--mode [MODE]", ["html", "list", "line", "column"], - "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode| - options["mode"] = mode - end - - opt.on("--header") do |h| - options["header"] = h - end - - opt.on("-h", "--help", "Show this help message.") do - puts opt - exit - end - - opt.on("-e", "--environment=name", String, - "Specifies the environment to run this console under (test/development/production).", - "Default: development" - ) { |v| options[:environment] = v.strip } - - opt.parse!(arguments) - abort opt.to_s unless (0..1).include?(arguments.size) - end - - set_options_env(arguments, options) - end + def self.start(*args) + new(*args).start end - def initialize(arguments = ARGV) - @arguments = arguments + def initialize(options = {}) + @options = options end def start - options = self.class.parse_arguments(arguments) - ENV["RAILS_ENV"] = options[:environment] || environment + ENV["RAILS_ENV"] = @options[:environment] || environment case config["adapter"] when /^(jdbc)?mysql/ @@ -69,7 +28,7 @@ module Rails "sslkey" => "--ssl-key" }.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact - if config["password"] && options["include_password"] + if config["password"] && @options["include_password"] args << "--password=#{config['password']}" elsif config["password"] && !config["password"].to_s.empty? args << "-p" @@ -83,14 +42,14 @@ module Rails ENV["PGUSER"] = config["username"] if config["username"] ENV["PGHOST"] = config["host"] if config["host"] ENV["PGPORT"] = config["port"].to_s if config["port"] - ENV["PGPASSWORD"] = config["password"].to_s if config["password"] && options["include_password"] + ENV["PGPASSWORD"] = config["password"].to_s if config["password"] && @options["include_password"] find_cmd_and_exec("psql", config["database"]) when "sqlite3" args = [] - args << "-#{options['mode']}" if options["mode"] - args << "-header" if options["header"] + args << "-#{@options['mode']}" if @options["mode"] + args << "-header" if @options["header"] args << File.expand_path(config["database"], Rails.respond_to?(:root) ? Rails.root : nil) find_cmd_and_exec("sqlite3", *args) @@ -100,7 +59,7 @@ module Rails if config["username"] logon = config["username"] - logon << "/#{config['password']}" if config["password"] && options["include_password"] + logon << "/#{config['password']}" if config["password"] && @options["include_password"] logon << "@#{config['database']}" if config["database"] end @@ -137,7 +96,7 @@ module Rails end def environment - Rails.respond_to?(:env) ? Rails.env : super + Rails.respond_to?(:env) ? Rails.env : Rails::Command.environment end protected @@ -170,4 +129,27 @@ module Rails end end end + + module Command + class DbconsoleCommand < Base # :nodoc: + include EnvironmentArgument + + class_option :include_password, aliases: "-p", type: :boolean, + desc: "Automatically provide the password from database.yml" + + class_option :mode, enum: %w( html list line column ), type: :string, + desc: "Automatically put the sqlite3 database in the specified mode (html, list, line, column)." + + class_option :header, type: :string + + class_option :environment, aliases: "-e", type: :string, + desc: "Specifies the environment to run this console under (test/development/production)." + + def perform + extract_environment_option_from_argument + + Rails::DBConsole.start(options) + end + end + end end diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb deleted file mode 100644 index 71c8c5e526..0000000000 --- a/railties/lib/rails/commands/destroy.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "rails/generators" - -#if no argument/-h/--help is passed to rails destroy command, then -#it generates the help associated. -if [nil, "-h", "--help"].include?(ARGV.first) - Rails::Generators.help "destroy" - exit -end - -name = ARGV.shift -Rails::Generators.invoke name, ARGV, behavior: :revoke, destination_root: Rails.root diff --git a/railties/lib/rails/commands/destroy/destroy_command.rb b/railties/lib/rails/commands/destroy/destroy_command.rb new file mode 100644 index 0000000000..5b552b2070 --- /dev/null +++ b/railties/lib/rails/commands/destroy/destroy_command.rb @@ -0,0 +1,21 @@ +require "rails/generators" + +module Rails + module Command + class DestroyCommand < Base # :nodoc: + def help + Rails::Generators.help self.class.command_name + end + + def perform(*) + generator = args.shift + return help unless generator + + require_application_and_environment! + Rails.application.load_generators + + Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails.root + end + end + end +end diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb deleted file mode 100644 index ba6f14073e..0000000000 --- a/railties/lib/rails/commands/generate.rb +++ /dev/null @@ -1,13 +0,0 @@ -require "rails/generators" - -#if no argument/-h/--help is passed to rails generate command, then -#it generates the help associated. -if [nil, "-h", "--help"].include?(ARGV.first) - Rails::Generators.help "generate" - exit -end - -name = ARGV.shift - -root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root -Rails::Generators.invoke name, ARGV, behavior: :invoke, destination_root: root diff --git a/railties/lib/rails/commands/generate/generate_command.rb b/railties/lib/rails/commands/generate/generate_command.rb new file mode 100644 index 0000000000..59b2febc43 --- /dev/null +++ b/railties/lib/rails/commands/generate/generate_command.rb @@ -0,0 +1,21 @@ +require "rails/generators" + +module Rails + module Command + class GenerateCommand < Base # :nodoc: + def help + Rails::Generators.help self.class.command_name + end + + def perform(*) + generator = args.shift + return help unless generator + + require_application_and_environment! + load_generators + + Rails::Generators.invoke generator, args, behavior: :invoke, destination_root: Rails::Command.root + end + end + end +end diff --git a/railties/lib/rails/commands/help/USAGE b/railties/lib/rails/commands/help/USAGE new file mode 100644 index 0000000000..348f41861f --- /dev/null +++ b/railties/lib/rails/commands/help/USAGE @@ -0,0 +1,27 @@ +Usage: bin/rails COMMAND [args] [options] +<% if engine? %> +The common Rails commands available for engines are: + generate Generate new code (short-cut alias: "g") + destroy Undo code generated with "generate" (short-cut alias: "d") + test Run tests (short-cut alias: "t") + +All commands can be run with -h for more information. + +If you want to run any commands that need to be run in context +of the application, like `bin/rails server` or `bin/rails console`, +you should do it from the application's directory (typically test/dummy). +<% else %> +The most common rails commands are: + generate Generate new code (short-cut alias: "g") + console Start the Rails console (short-cut alias: "c") + server Start the Rails server (short-cut alias: "s") + test Run tests (short-cut alias: "t") + dbconsole Start a console for the database specified in config/database.yml + (short-cut alias: "db") + new Create a new Rails application. "rails new my_app" creates a + new application called MyApp in "./my_app" + +All commands can be run with -h (or --help) for more information. +<% end %> +In addition to those commands, there are: + diff --git a/railties/lib/rails/commands/help/help_command.rb b/railties/lib/rails/commands/help/help_command.rb new file mode 100644 index 0000000000..90d37217fc --- /dev/null +++ b/railties/lib/rails/commands/help/help_command.rb @@ -0,0 +1,13 @@ +module Rails + module Command + class HelpCommand < Base # :nodoc: + hide_command! + + def help(*) + puts self.class.desc + + Rails::Command.print_commands + end + end + end +end diff --git a/railties/lib/rails/commands/new/new_command.rb b/railties/lib/rails/commands/new/new_command.rb new file mode 100644 index 0000000000..74d1fa5021 --- /dev/null +++ b/railties/lib/rails/commands/new/new_command.rb @@ -0,0 +1,15 @@ +module Rails + module Command + class NewCommand < Base # :nodoc: + def help + Rails::Command.invoke :application, [ "--help" ] + end + + def perform(*) + puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n" + puts "Type 'rails' for help." + exit 1 + end + end + end +end diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb deleted file mode 100644 index 60653a2cee..0000000000 --- a/railties/lib/rails/commands/plugin.rb +++ /dev/null @@ -1,24 +0,0 @@ -if ARGV.first != "new" - ARGV[0] = "--help" -else - ARGV.shift - unless ARGV.delete("--no-rc") - customrc = ARGV.index { |x| x.include?("--rc=") } - railsrc = if customrc - File.expand_path(ARGV.delete_at(customrc).gsub(/--rc=/, "")) - else - File.join(File.expand_path("~"), ".railsrc") - end - - if File.exist?(railsrc) - extra_args_string = File.read(railsrc) - extra_args = extra_args_string.split(/\n+/).flat_map(&:split) - puts "Using #{extra_args.join(" ")} from #{railsrc}" - ARGV.insert(1, *extra_args) - end - end -end - -require "rails/generators" -require "rails/generators/rails/plugin/plugin_generator" -Rails::Generators::PluginGenerator.start diff --git a/railties/lib/rails/commands/plugin/plugin_command.rb b/railties/lib/rails/commands/plugin/plugin_command.rb new file mode 100644 index 0000000000..16587ce067 --- /dev/null +++ b/railties/lib/rails/commands/plugin/plugin_command.rb @@ -0,0 +1,43 @@ +module Rails + module Command + class PluginCommand < Base # :nodoc: + hide_command! + + def help + run_plugin_generator %w( --help ) + end + + def self.banner(*) # :nodoc: + "#{executable} new [options]" + end + + class_option :rc, type: :boolean, default: File.join("~", ".railsrc"), + desc: "Initialize the plugin command with previous defaults. Uses .railsrc in your home directory by default." + + class_option :no_rc, desc: "Skip evaluating .railsrc." + + def perform(type = nil, *plugin_args) + plugin_args << "--help" unless type == "new" + + unless options.key?("no_rc") # Thor's not so indifferent access hash. + railsrc = File.expand_path(options[:rc]) + + if File.exist?(railsrc) + extra_args = File.read(railsrc).split(/\n+/).flat_map(&:split) + puts "Using #{extra_args.join(" ")} from #{railsrc}" + plugin_args.insert(1, *extra_args) + end + end + + run_plugin_generator plugin_args + end + + private + def run_plugin_generator(plugin_args) + require "rails/generators" + require "rails/generators/rails/plugin/plugin_generator" + Rails::Generators::PluginGenerator.start plugin_args + end + end + end +end diff --git a/railties/lib/rails/commands/rake/rake_command.rb b/railties/lib/rails/commands/rake/rake_command.rb new file mode 100644 index 0000000000..f03dc81117 --- /dev/null +++ b/railties/lib/rails/commands/rake/rake_command.rb @@ -0,0 +1,51 @@ +module Rails + module Command + class RakeCommand < Base # :nodoc: + extend Rails::Command::Actions + + namespace "rake" + + class << self + def printing_commands + formatted_rake_tasks.map(&:first) + end + + def perform(task, *) + require_rake + + ARGV.unshift(task) # Prepend the task, so Rake knows how to run it. + + Rake.application.standard_exception_handling do + Rake.application.init("rails") + Rake.application.load_rakefile + Rake.application.top_level + end + end + + private + def rake_tasks + require_rake + + return @rake_tasks if defined?(@rake_tasks) + + ActiveSupport::Deprecation.silence do + require_application_and_environment! + end + + Rake::TaskManager.record_task_metadata = true + Rake.application.instance_variable_set(:@name, "rails") + load_tasks + @rake_tasks = Rake.application.tasks.select(&:comment) + end + + def formatted_rake_tasks + rake_tasks.map { |t| [ t.name_with_args, t.comment ] } + end + + def require_rake + require "rake" # Defer booting Rake until we know it's needed. + end + end + end + end +end diff --git a/railties/lib/rails/commands/rake_proxy.rb b/railties/lib/rails/commands/rake_proxy.rb deleted file mode 100644 index f8da71831a..0000000000 --- a/railties/lib/rails/commands/rake_proxy.rb +++ /dev/null @@ -1,41 +0,0 @@ -require "active_support" - -module Rails - module RakeProxy #:nodoc: - private - def run_rake_task(command) - require_rake - - ARGV.unshift(command) # Prepend the command, so Rake knows how to run it. - - Rake.application.standard_exception_handling do - Rake.application.init("rails") - Rake.application.load_rakefile - Rake.application.top_level - end - end - - def rake_tasks - require_rake - - return @rake_tasks if defined?(@rake_tasks) - - ActiveSupport::Deprecation.silence do - require_application_and_environment! - end - - Rake::TaskManager.record_task_metadata = true - Rake.application.instance_variable_set(:@name, "rails") - load_tasks - @rake_tasks = Rake.application.tasks.select(&:comment) - end - - def formatted_rake_tasks - rake_tasks.map { |t| [ t.name_with_args, t.comment ] } - end - - def require_rake - require "rake" # Defer booting Rake until we know it's needed. - end - end -end diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb deleted file mode 100644 index b74addf587..0000000000 --- a/railties/lib/rails/commands/runner.rb +++ /dev/null @@ -1,71 +0,0 @@ -require "optparse" - -options = { environment: (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup } -code_or_file = nil -command = "bin/rails runner" - -if ARGV.first.nil? - ARGV.push "-h" -end - -ARGV.clone.options do |opts| - opts.banner = "Usage: rails runner [options] [<'Some.ruby(code)'> | <filename.rb>]" - - opts.separator "" - - opts.on("-e", "--environment=name", String, - "Specifies the environment for the runner to operate under (test/development/production).", - "Default: development") { |v| options[:environment] = v } - - opts.separator "" - - opts.on("-h", "--help", - "Show this help message.") { $stdout.puts opts; exit } - - opts.separator "" - opts.separator "Examples: " - - opts.separator " rails runner 'puts Rails.env'" - opts.separator " This runs the code `puts Rails.env` after loading the app" - opts.separator "" - opts.separator " rails runner path/to/filename.rb" - opts.separator " This runs the Ruby file located at `path/to/filename.rb` after loading the app" - - if RbConfig::CONFIG["host_os"] !~ /mswin|mingw/ - opts.separator "" - opts.separator "You can also use runner as a shebang line for your executables:" - opts.separator " -------------------------------------------------------------" - opts.separator " #!/usr/bin/env #{File.expand_path(command)}" - opts.separator "" - opts.separator " Product.all.each { |p| p.price *= 2 ; p.save! }" - opts.separator " -------------------------------------------------------------" - end - - opts.order! { |o| code_or_file ||= o } rescue retry -end - -ARGV.delete(code_or_file) - -ENV["RAILS_ENV"] = options[:environment] - -require APP_PATH -Rails.application.require_environment! -Rails.application.load_runner - -if code_or_file.nil? - $stderr.puts "Run '#{command} -h' for help." - exit 1 -elsif File.exist?(code_or_file) - $0 = code_or_file - Kernel.load code_or_file -else - begin - eval(code_or_file, binding, __FILE__, __LINE__) - rescue SyntaxError, NameError => e - $stderr.puts "Please specify a valid ruby command or the path of a script to run." - $stderr.puts "Run '#{command} -h' for help." - $stderr.puts - $stderr.puts e - exit 1 - end -end diff --git a/railties/lib/rails/commands/runner/USAGE b/railties/lib/rails/commands/runner/USAGE new file mode 100644 index 0000000000..b2a6e8493d --- /dev/null +++ b/railties/lib/rails/commands/runner/USAGE @@ -0,0 +1,17 @@ +Examples: + +Run `puts Rails.env` after loading the app: + + <%= executable %> 'puts Rails.env' + +Run the Ruby file located at `path/to/filename.rb` after loading the app: + + <%= executable %> path/to/filename.rb + +<% unless Gem.win_platform? %> +You can also use the runner command as a shebang line for your executables: + + #!/usr/bin/env <%= File.expand_path(executable) %> + + Product.all.each { |p| p.price *= 2 ; p.save! } +<% end %> diff --git a/railties/lib/rails/commands/runner/runner_command.rb b/railties/lib/rails/commands/runner/runner_command.rb new file mode 100644 index 0000000000..4989a7837d --- /dev/null +++ b/railties/lib/rails/commands/runner/runner_command.rb @@ -0,0 +1,46 @@ +module Rails + module Command + class RunnerCommand < Base # :nodoc: + class_option :environment, aliases: "-e", type: :string, + default: Rails::Command.environment.dup, + desc: "The environment for the runner to operate under (test/development/production)" + + def help + super + puts self.class.desc + end + + def self.banner(*) + "#{super} [<'Some.ruby(code)'> | <filename.rb>]" + end + + def perform(code_or_file = nil, *file_argv) + unless code_or_file + help + exit 1 + end + + ENV["RAILS_ENV"] = options[:environment] + + require_application_and_environment! + Rails.application.load_runner + + if File.exist?(code_or_file) + $0 = code_or_file + ARGV.replace(file_argv) + Kernel.load code_or_file + else + begin + eval(code_or_file, binding, __FILE__, __LINE__) + rescue SyntaxError, NameError => error + $stderr.puts "Please specify a valid ruby command or the path of a script to run." + $stderr.puts "Run '#{self.class.executable} -h' for help." + $stderr.puts + $stderr.puts error + exit 1 + end + end + end + end + end +end diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server/server_command.rb index 0339849bfe..e9538b804c 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -19,33 +19,35 @@ module Rails options end - private - - def option_parser(options) - OptionParser.new do |opts| - opts.banner = "Usage: rails server [mongrel, thin etc] [options]" - opts.on("-p", "--port=port", Integer, - "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v } - opts.on("-b", "--binding=IP", String, - "Binds Rails to the specified IP.", "Default: localhost") { |v| options[:Host] = v } - opts.on("-c", "--config=file", String, - "Uses a custom rackup configuration.") { |v| options[:config] = v } - opts.on("-d", "--daemon", "Runs server as a Daemon.") { options[:daemonize] = true } - opts.on("-e", "--environment=name", String, - "Specifies the environment to run this server under (test/development/production).", - "Default: development") { |v| options[:environment] = v } - opts.on("-P", "--pid=pid", String, - "Specifies the PID file.", - "Default: tmp/pids/server.pid") { |v| options[:pid] = v } - opts.on("-C", "--[no-]dev-caching", - "Specifies whether to perform caching in development.", - "true or false") { |v| options[:caching] = v } - - opts.separator "" - - opts.on("-h", "--help", "Shows this help message.") { puts opts; exit } - end + def option_parser(options) # :nodoc: + OptionParser.new do |opts| + opts.banner = "Usage: rails server [puma, thin etc] [options]" + + opts.separator "" + opts.separator "Options:" + + opts.on("-p", "--port=port", Integer, + "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v } + opts.on("-b", "--binding=IP", String, + "Binds Rails to the specified IP.", "Default: localhost") { |v| options[:Host] = v } + opts.on("-c", "--config=file", String, + "Uses a custom rackup configuration.") { |v| options[:config] = v } + opts.on("-d", "--daemon", "Runs server as a Daemon.") { options[:daemonize] = true } + opts.on("-e", "--environment=name", String, + "Specifies the environment to run this server under (test/development/production).", + "Default: development") { |v| options[:environment] = v } + opts.on("-P", "--pid=pid", String, + "Specifies the PID file.", + "Default: tmp/pids/server.pid") { |v| options[:pid] = v } + opts.on("-C", "--[no-]dev-caching", + "Specifies whether to perform caching in development.", + "true or false") { |v| options[:caching] = v } + + opts.separator "" + + opts.on("-h", "--help", "Shows this help message.") { puts opts; exit } end + end end def initialize(*) @@ -100,7 +102,6 @@ module Rails end private - def setup_dev_caching if options[:environment] == "development" Rails::DevCaching.enable_by_argument(options[:caching]) @@ -136,4 +137,24 @@ module Rails "bin/rails server #{ARGV.join(' ')}" end end + + module Command + class ServerCommand < Base # :nodoc: + def help + puts Rails::Server::Options.new.option_parser(Hash.new) + end + + def perform + set_application_directory! + + Rails::Server.new.tap do |server| + # Require application after server sets environment to propagate + # the --environment option. + require APP_PATH + Dir.chdir(Rails.application.root) + server.start + end + end + end + end end diff --git a/railties/lib/rails/commands/test.rb b/railties/lib/rails/commands/test.rb deleted file mode 100644 index 219c2fa4e0..0000000000 --- a/railties/lib/rails/commands/test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require "rails/test_unit/minitest_plugin" - -if defined?(ENGINE_ROOT) - $: << File.expand_path("test", ENGINE_ROOT) -else - $: << File.expand_path("../../test", APP_PATH) -end - -exit Minitest.run(ARGV) diff --git a/railties/lib/rails/commands/test/test_command.rb b/railties/lib/rails/commands/test/test_command.rb new file mode 100644 index 0000000000..7bf8f61137 --- /dev/null +++ b/railties/lib/rails/commands/test/test_command.rb @@ -0,0 +1,20 @@ +require "rails/command" +require "rails/test_unit/minitest_plugin" + +module Rails + module Command + class TestCommand < Base # :nodoc: + def help + perform # Hand over help printing to minitest. + end + + def perform(*) + $LOAD_PATH << Rails::Command.root.join("test") + + Minitest.run_via[:rails] = true + + require "active_support/testing/autorun" + end + end + end +end diff --git a/railties/lib/rails/commands/version/version_command.rb b/railties/lib/rails/commands/version/version_command.rb new file mode 100644 index 0000000000..ac745594ee --- /dev/null +++ b/railties/lib/rails/commands/version/version_command.rb @@ -0,0 +1,9 @@ +module Rails + module Command + class VersionCommand < Base # :nodoc: + def perform + Rails::Command.invoke :application, [ "--version" ] + end + end + end +end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 7dfab969e8..fc7d4909f6 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -91,8 +91,8 @@ module Rails attr_reader :hidden_namespaces def initialize - @aliases = Hash.new { |h,k| h[k] = {} } - @options = Hash.new { |h,k| h[k] = {} } + @aliases = Hash.new { |h, k| h[k] = {} } + @options = Hash.new { |h, k| h[k] = {} } @fallbacks = {} @templates = [] @colorize_logging = true diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb index 541d5e3dad..affadc8e09 100644 --- a/railties/lib/rails/console/app.rb +++ b/railties/lib/rails/console/app.rb @@ -5,7 +5,7 @@ module Rails module ConsoleMethods # reference the global "app" instance, created on demand. To recreate the # instance, pass a non-false value as the parameter. - def app(create=false) + def app(create = false) @app_integration_instance = nil if create @app_integration_instance ||= new_session do |sess| sess.host! "www.example.com" @@ -27,7 +27,7 @@ module Rails end # reloads the environment - def reload!(print=true) + def reload!(print = true) puts "Reloading..." if print Rails.application.reloader.reload! true diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 86d66afddb..e56f6159ad 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -337,7 +337,7 @@ module Rails # == Loading priority # # In order to change engine's priority you can use +config.railties_order+ in the main application. - # It will affect the priority of loading views, helpers, assets and all the other files + # It will affect the priority of loading views, helpers, assets, and all the other files # related to engine or application. # # # load Blog::Engine with highest priority, followed by application and other railties @@ -436,7 +436,7 @@ module Rails # Load console and invoke the registered hooks. # Check <tt>Rails::Railtie.console</tt> for more info. - def load_console(app=self) + def load_console(app = self) require "rails/console/app" require "rails/console/helpers" run_console_blocks(app) @@ -445,14 +445,14 @@ module Rails # Load Rails runner and invoke the registered hooks. # Check <tt>Rails::Railtie.runner</tt> for more info. - def load_runner(app=self) + def load_runner(app = self) run_runner_blocks(app) self end # Load Rake, railties tasks and invoke the registered hooks. # Check <tt>Rails::Railtie.rake_tasks</tt> for more info. - def load_tasks(app=self) + def load_tasks(app = self) require "rake" run_tasks_blocks(app) self @@ -460,7 +460,7 @@ module Rails # Load Rails generators and invoke the registered hooks. # Check <tt>Rails::Railtie.generators</tt> for more info. - def load_generators(app=self) + def load_generators(app = self) require "rails/generators" run_generators_blocks(app) Rails::Generators.configure!(app.config.generators) @@ -658,7 +658,7 @@ module Rails paths["db/migrate"].existent.any? end - def self.find_root_with_flag(flag, root_path, default=nil) #:nodoc: + def self.find_root_with_flag(flag, root_path, default = nil) #:nodoc: while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}") parent = File.dirname(root_path) diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb index dfbeea36b8..a23ae44b0b 100644 --- a/railties/lib/rails/engine/commands.rb +++ b/railties/lib/rails/engine/commands.rb @@ -1,6 +1,4 @@ -require "rails/engine/commands_tasks" - -ARGV << "--help" if ARGV.empty? +require "rails/command" aliases = { "g" => "generate", @@ -11,4 +9,4 @@ aliases = { command = ARGV.shift command = aliases[command] || command -Rails::Engine::CommandsTasks.new(ARGV).run_command!(command) +Rails::Command.invoke command, ARGV diff --git a/railties/lib/rails/engine/commands_tasks.rb b/railties/lib/rails/engine/commands_tasks.rb deleted file mode 100644 index d6effdb732..0000000000 --- a/railties/lib/rails/engine/commands_tasks.rb +++ /dev/null @@ -1,62 +0,0 @@ -require "rails/commands/rake_proxy" -require "rails/commands/common_commands_tasks" -require "active_support/core_ext/string/strip" - -module Rails - class Engine - class CommandsTasks # :nodoc: - include Rails::RakeProxy - include Rails::CommonCommandsTasks - - attr_reader :argv - - def initialize(argv) - @argv = argv - end - - private - - def commands - formatted_rake_tasks - end - - def command_whitelist - %w(generate destroy version help test) - end - - def help_message - <<-EOT.strip_heredoc - Usage: rails COMMAND [ARGS] - - The common Rails commands available for engines are: - generate Generate new code (short-cut alias: "g") - destroy Undo code generated with "generate" (short-cut alias: "d") - test Run tests (short-cut alias: "t") - - All commands can be run with -h for more information. - - If you want to run any commands that need to be run in context - of the application, like `rails server` or `rails console`, - you should do it from application's directory (typically test/dummy). - - In addition to those commands, there are: - EOT - end - - def require_application_and_environment! - require ENGINE_PATH - end - - def load_tasks - Rake.application.init("rails") - Rake.application.load_rakefile - end - - def load_generators - engine = ::Rails::Engine.find(ENGINE_ROOT) - Rails::Generators.namespace = engine.railtie_namespace - engine.load_generators - end - end - end -end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 147b904679..0c40173c38 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -7,7 +7,7 @@ module Rails attr_accessor :middleware attr_writer :eager_load_paths, :autoload_once_paths, :autoload_paths - def initialize(root=nil) + def initialize(root = nil) super() @root = root @generators = app_generators.dup diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 6a112fc710..67037106e5 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -2,6 +2,7 @@ activesupport_path = File.expand_path("../../../../activesupport/lib", __FILE__) $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) require "thor/group" +require "rails/command" require "active_support" require "active_support/core_ext/object/blank" @@ -13,6 +14,8 @@ require "active_support/core_ext/string/inflections" module Rails module Generators + include Rails::Command::Behavior + autoload :Actions, "rails/generators/actions" autoload :ActiveModel, "rails/generators/active_model" autoload :Base, "rails/generators/base" @@ -127,67 +130,6 @@ module Rails Thor::Base.shell = Thor::Shell::Basic end - # Track all generators subclasses. - def self.subclasses - @subclasses ||= [] - end - - # Rails finds namespaces similar to thor, it only adds one rule: - # - # Generators names must end with "_generator.rb". This is required because Rails - # looks in load paths and loads the generator just before it's going to be used. - # - # find_by_namespace :webrat, :rails, :integration - # - # Will search for the following generators: - # - # "rails:webrat", "webrat:integration", "webrat" - # - # Notice that "rails:generators:webrat" could be loaded as well, what - # Rails looks for is the first and last parts of the namespace. - def self.find_by_namespace(name, base=nil, context=nil) #:nodoc: - lookups = [] - lookups << "#{base}:#{name}" if base - lookups << "#{name}:#{context}" if context - - unless base || context - unless name.to_s.include?(?:) - lookups << "#{name}:#{name}" - lookups << "rails:#{name}" - end - lookups << "#{name}" - end - - lookup(lookups) - - namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }] - - lookups.each do |namespace| - klass = namespaces[namespace] - return klass if klass - end - - invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) - end - - # Receives a namespace, arguments and the behavior to invoke the generator. - # It's used as the default entry point for generate, destroy and update - # commands. - def self.invoke(namespace, args=ARGV, config={}) - names = namespace.to_s.split(":") - if klass = find_by_namespace(names.pop, names.any? && names.join(":")) - args << "--help" if args.empty? && klass.arguments.any?(&:required?) - klass.start(args, config) - else - options = sorted_groups.flat_map(&:last) - suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3) - msg = "Could not find generator '#{namespace}'. " - msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n" - msg << "Run `rails generate --help` for more options." - puts msg - end - end - # Returns an array of generator namespaces that are hidden. # Generator namespaces may be hidden for a variety of reasons. # Some are aliased such as "rails:migration" and can be @@ -260,11 +202,13 @@ module Rails def self.sorted_groups namespaces = public_namespaces namespaces.sort! - groups = Hash.new { |h,k| h[k] = [] } + + groups = Hash.new { |h, k| h[k] = [] } namespaces.each do |namespace| base = namespace.split(":").first groups[base] << namespace end + rails = groups.delete("rails") rails.map! { |n| n.sub(/^rails:/, "") } rails.delete("app") @@ -272,62 +216,69 @@ module Rails hidden_namespaces.each { |n| groups.delete(n.to_s) } - [["rails", rails]] + groups.sort.to_a + [[ "rails", rails ]] + groups.sort.to_a end - protected + # Rails finds namespaces similar to thor, it only adds one rule: + # + # Generators names must end with "_generator.rb". This is required because Rails + # looks in load paths and loads the generator just before it's going to be used. + # + # find_by_namespace :webrat, :rails, :integration + # + # Will search for the following generators: + # + # "rails:webrat", "webrat:integration", "webrat" + # + # Notice that "rails:generators:webrat" could be loaded as well, what + # Rails looks for is the first and last parts of the namespace. + def self.find_by_namespace(name, base = nil, context = nil) #:nodoc: + lookups = [] + lookups << "#{base}:#{name}" if base + lookups << "#{name}:#{context}" if context - # This code is based directly on the Text gem implementation - # Returns a value representing the "cost" of transforming str1 into str2 - def self.levenshtein_distance(str1, str2) - s = str1 - t = str2 - n = s.length - m = t.length - - return m if (0 == n) - return n if (0 == m) - - d = (0..m).to_a - x = nil - - # avoid duplicating an enumerable object in the loop - str2_codepoint_enumerable = str2.each_codepoint - - str1.each_codepoint.with_index do |char1, i| - e = i+1 - - str2_codepoint_enumerable.with_index do |char2, j| - cost = (char1 == char2) ? 0 : 1 - x = [ - d[j+1] + 1, # insertion - e + 1, # deletion - d[j] + cost # substitution - ].min - d[j] = e - e = x - end - - d[m] = x + unless base || context + unless name.to_s.include?(?:) + lookups << "#{name}:#{name}" + lookups << "rails:#{name}" end - - x + lookups << "#{name}" end - # Prints a list of generators. - def self.print_list(base, namespaces) #:nodoc: - namespaces = namespaces.reject do |n| - hidden_namespaces.include?(n) - end + lookup(lookups) - return if namespaces.empty? - puts "#{base.camelize}:" + namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }] + lookups.each do |namespace| - namespaces.each do |namespace| - puts(" #{namespace}") - end + klass = namespaces[namespace] + return klass if klass + end + + invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) + end + + # Receives a namespace, arguments and the behavior to invoke the generator. + # It's used as the default entry point for generate, destroy and update + # commands. + def self.invoke(namespace, args = ARGV, config = {}) + names = namespace.to_s.split(":") + if klass = find_by_namespace(names.pop, names.any? && names.join(":")) + args << "--help" if args.empty? && klass.arguments.any?(&:required?) + klass.start(args, config) + else + options = sorted_groups.flat_map(&:last) + suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3) + msg = "Could not find generator '#{namespace}'. " + msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n" + msg << "Run `rails generate --help` for more options." + puts msg + end + end - puts + protected + def self.print_list(base, namespaces) + namespaces = namespaces.reject { |n| hidden_namespaces.include?(n) } + super end # Try fallbacks for the given base. @@ -346,53 +297,16 @@ module Rails nil end - # Receives namespaces in an array and tries to find matching generators - # in the load path. - def self.lookup(namespaces) #:nodoc: - paths = namespaces_to_paths(namespaces) - - paths.each do |raw_path| - ["rails/generators", "generators"].each do |base| - path = "#{base}/#{raw_path}_generator" - - begin - require path - return - rescue LoadError => e - raise unless e.message =~ /#{Regexp.escape(path)}$/ - rescue Exception => e - warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}" - end - end - end + def self.command_type + @command_type ||= "generator" end - # This will try to load any generator in the load path to show in help. - def self.lookup! #:nodoc: - $LOAD_PATH.each do |base| - Dir[File.join(base, "{rails/generators,generators}", "**", "*_generator.rb")].each do |path| - begin - path = path.sub("#{base}/", "") - require path - rescue Exception - # No problem - end - end - end + def self.lookup_paths + @lookup_paths ||= %w( rails/generators generators ) end - # Convert namespaces to paths by replacing ":" for "/" and adding - # an extra lookup. For example, "rails:model" should be searched - # in both: "rails/model/model_generator" and "rails/model_generator". - def self.namespaces_to_paths(namespaces) #:nodoc: - paths = [] - namespaces.each do |namespace| - pieces = namespace.split(":") - paths << pieces.dup.push(pieces.last).join("/") - paths << pieces.join("/") - end - paths.uniq! - paths + def self.file_lookup_paths + @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_generator.rb" ] end end end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index ab9dc019e2..8a60560f86 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -68,7 +68,7 @@ module Rails # add_source "http://gems.github.com/" do # gem "rspec-rails" # end - def add_source(source, options={}, &block) + def add_source(source, options = {}, &block) log :source, source in_root do @@ -96,7 +96,7 @@ module Rails # environment(nil, env: "development") do # "config.action_controller.asset_host = 'localhost:3000'" # end - def environment(data=nil, options={}) + def environment(data = nil, options = {}) sentinel = /class [a-z_:]+ < Rails::Application/i env_file_sentinel = /Rails\.application\.configure do/ data = yield if !data && block_given? @@ -118,7 +118,7 @@ module Rails # git :init # git add: "this.file that.rb" # git add: "onefile.rb", rm: "badfile.cxx" - def git(commands={}) + def git(commands = {}) if commands.is_a?(Symbol) run "git #{commands}" else @@ -137,7 +137,7 @@ module Rails # end # # vendor("foreign.rb", "# Foreign code is fun") - def vendor(filename, data=nil, &block) + def vendor(filename, data = nil, &block) log :vendor, filename create_file("vendor/#{filename}", data, verbose: false, &block) end @@ -150,7 +150,7 @@ module Rails # end # # lib("foreign.rb", "# Foreign code is fun") - def lib(filename, data=nil, &block) + def lib(filename, data = nil, &block) log :lib, filename create_file("lib/#{filename}", data, verbose: false, &block) end @@ -170,7 +170,7 @@ module Rails # end # # rakefile('seed.rake', 'puts "Planting seeds"') - def rakefile(filename, data=nil, &block) + def rakefile(filename, data = nil, &block) log :rakefile, filename create_file("lib/tasks/#{filename}", data, verbose: false, &block) end @@ -188,7 +188,7 @@ module Rails # end # # initializer("api.rb", "API_KEY = '123456'") - def initializer(filename, data=nil, &block) + def initializer(filename, data = nil, &block) log :initializer, filename create_file("config/initializers/#{filename}", data, verbose: false, &block) end @@ -210,7 +210,7 @@ module Rails # rake("db:migrate") # rake("db:migrate", env: "production") # rake("gems:install", sudo: true) - def rake(command, options={}) + def rake(command, options = {}) execute_command :rake, command, options end @@ -219,7 +219,7 @@ module Rails # rails("db:migrate") # rails("db:migrate", env: "production") # rails("gems:install", sudo: true) - def rails_command(command, options={}) + def rails_command(command, options = {}) execute_command :rails, command, options end @@ -276,16 +276,16 @@ module Rails # Runs the supplied command using either "rake ..." or "rails ..." # based on the executor parameter provided. - def execute_command(executor, command, options={}) + def execute_command(executor, command, options = {}) log executor, command env = options[:env] || ENV["RAILS_ENV"] || "development" - sudo = options[:sudo] && RbConfig::CONFIG["host_os"] !~ /mswin|mingw/ ? "sudo " : "" + sudo = options[:sudo] && !Gem.win_platform? ? "sudo " : "" in_root { run("#{sudo}#{extify(executor)} #{command} RAILS_ENV=#{env}", verbose: false) } end # Add an extension to the given name based on the platform. def extify(name) - if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ + if Gem.win_platform? "#{name}.bat" else name diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb index 6183944bb0..2679d06fe4 100644 --- a/railties/lib/rails/generators/active_model.rb +++ b/railties/lib/rails/generators/active_model.rb @@ -39,13 +39,13 @@ module Rails # GET edit # PATCH/PUT update # DELETE destroy - def self.find(klass, params=nil) + def self.find(klass, params = nil) "#{klass}.find(#{params})" end # GET new # POST create - def self.build(klass, params=nil) + def self.build(klass, params = nil) if params "#{klass}.new(#{params})" else @@ -59,7 +59,7 @@ module Rails end # PATCH/PUT update - def update(params=nil) + def update(params = nil) "#{name}.update(#{params})" end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 7d061ed27c..aba68d6db2 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -30,9 +30,12 @@ module Rails class_option :database, type: :string, aliases: "-d", default: "sqlite3", desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})" - class_option :javascript, type: :string, aliases: "-j", default: "jquery", + class_option :javascript, type: :string, aliases: "-j", desc: "Preconfigure for selected JavaScript library" + class_option :skip_yarn, type: :boolean, default: false, + desc: "Don't use Yarn for managing JavaScript dependencies" + class_option :skip_gemfile, type: :boolean, default: false, desc: "Don't create a Gemfile" @@ -67,6 +70,9 @@ module Rails class_option :skip_listen, type: :boolean, default: false, desc: "Don't generate configuration that depends on the listen gem" + class_option :skip_coffee, type: :boolean, default: false, + desc: "Don't use CoffeeScript" + class_option :skip_javascript, type: :boolean, aliases: "-J", default: false, desc: "Skip JavaScript files" @@ -162,14 +168,15 @@ module Rails def set_default_accessors! self.destination_root = File.expand_path(app_path, destination_root) - self.rails_template = case options[:template] - when /^https?:\/\// - options[:template] - when String - File.expand_path(options[:template], Dir.pwd) + self.rails_template = \ + case options[:template] + when /^https?:\/\// + options[:template] + when String + File.expand_path(options[:template], Dir.pwd) else - options[:template] - end + options[:template] + end end def database_gemfile_entry @@ -242,7 +249,7 @@ module Rails ] + dev_edge_common elsif options.edge? [ - GemfileEntry.github("rails", "rails/rails", "5-0-stable") + GemfileEntry.github("rails", "rails/rails") ] + dev_edge_common else [GemfileEntry.version("rails", @@ -321,9 +328,13 @@ module Rails if options[:skip_javascript] || options[:skip_sprockets] [] else - gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry] - gems << GemfileEntry.version("#{options[:javascript]}-rails", nil, - "Use #{options[:javascript]} as the JavaScript library") + gems = [javascript_runtime_gemfile_entry] + gems << coffee_gemfile_entry unless options[:skip_coffee] + + if options[:javascript] + gems << GemfileEntry.version("#{options[:javascript]}-rails", nil, + "Use #{options[:javascript]} as the JavaScript library") + end unless options[:skip_turbolinks] gems << GemfileEntry.version("turbolinks", "~> 5", diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 1a0420c769..c707bbfcbf 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -20,14 +20,14 @@ module Rails strict_args_position! # Returns the source root for this generator using default_source_root as default. - def self.source_root(path=nil) + def self.source_root(path = nil) @_source_root = path if path @_source_root ||= default_source_root end # Tries to get the description from a USAGE file one folder above the source # root otherwise uses a default description. - def self.desc(description=nil) + def self.desc(description = nil) return super if description @desc ||= if usage_path @@ -40,7 +40,7 @@ module Rails # Convenience method to get the namespace from the class name. It's the # same as Thor default except that the Generator at the end of the class # is removed. - def self.namespace(name=nil) + def self.namespace(name = nil) return super if name @namespace ||= super.sub(/_generator$/, "").sub(/:generators:/, ":") end @@ -195,7 +195,7 @@ module Rails end # Make class option aware of Rails::Generators.options and Rails::Generators.aliases. - def self.class_option(name, options={}) #:nodoc: + def self.class_option(name, options = {}) #:nodoc: options[:desc] = "Indicates when to generate #{name.to_s.humanize.downcase}" unless options.key?(:desc) options[:aliases] = default_aliases_for_option(name, options) options[:default] = default_value_for_option(name, options) @@ -273,7 +273,7 @@ module Rails # Use Rails default banner. def self.banner - "rails generate #{namespace.sub(/^rails:/,'')} #{arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, " ") + "rails generate #{namespace.sub(/^rails:/, '')} #{arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, " ") end # Sets the base_name taking into account the current class namespace. diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 61181b7b97..baed7bf1e3 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -56,7 +56,7 @@ module Rails end end - def initialize(name, type=nil, index_type=false, attr_options={}) + def initialize(name, type = nil, index_type = false, attr_options = {}) @name = name @type = type || :string @has_index = INDEX_OPTIONS.include?(index_type) @@ -151,7 +151,7 @@ module Rails end def inject_options - "".tap { |s| options_for_migration.each { |k,v| s << ", #{k}: #{v.inspect}" } } + "".tap { |s| options_for_migration.each { |k, v| s << ", #{k}: #{v.inspect}" } } end def inject_index_options diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb index 7290e235a1..0d63b9a5c9 100644 --- a/railties/lib/rails/generators/migration.rb +++ b/railties/lib/rails/generators/migration.rb @@ -52,7 +52,7 @@ module Rails # # migration_template "migration.rb", "db/migrate/add_foo_to_bar.rb" def migration_template(source, destination, config = {}) - source = File.expand_path(find_in_source_paths(source.to_s)) + source = File.expand_path(find_in_source_paths(source.to_s)) set_migration_assigns!(destination) context = instance_eval("binding") diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index c39ea24935..45f2fba5b9 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -217,7 +217,7 @@ module Rails # If the generator is invoked with class name Admin, it will check for # the presence of "AdminDecorator". # - def self.check_class_collision(options={}) + def self.check_class_collision(options = {}) define_method :check_class_collision do name = if self.respond_to?(:controller_class_name) # for ScaffoldBase controller_class_name diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 9f9c50ca10..20bc45fee8 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -56,7 +56,7 @@ module Rails def app directory "app" - keep_file "app/assets/images" + keep_file "app/assets/images" empty_directory_with_keep_file "app/assets/javascripts/channels" unless options[:skip_action_cable] keep_file "app/controllers/concerns" @@ -151,19 +151,12 @@ module Rails end def vendor - vendor_javascripts - vendor_stylesheets - end + empty_directory_with_keep_file "vendor" - def vendor_javascripts - unless options[:skip_javascript] - empty_directory_with_keep_file "vendor/assets/javascripts" + unless options[:skip_yarn] + template "package.json", "vendor/package.json" end end - - def vendor_stylesheets - empty_directory_with_keep_file "vendor/assets/stylesheets" - end end module Generators @@ -205,8 +198,8 @@ module Rails build(:readme) build(:rakefile) build(:configru) - build(:gitignore) unless options[:skip_git] - build(:gemfile) unless options[:skip_gemfile] + build(:gitignore) unless options[:skip_git] + build(:gemfile) unless options[:skip_gemfile] end def create_app_files @@ -241,6 +234,7 @@ module Rails end def create_db_files + return if options[:skip_active_record] build(:db) end @@ -266,6 +260,10 @@ module Rails def create_vendor_files build(:vendor) + + if options[:skip_yarn] + remove_file "vendor/package.json" + end end def delete_app_assets_if_api_option @@ -273,7 +271,6 @@ module Rails remove_dir "app/assets" remove_dir "lib/assets" remove_dir "tmp/cache/assets" - remove_dir "vendor/assets" end end @@ -422,7 +419,7 @@ module Rails "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4 "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5 "/opt/lampp/var/mysql/mysql.sock" # xampp for linux - ].find { |f| File.exist?(f) } unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ + ].find { |f| File.exist?(f) } unless Gem.win_platform? end def get_builder_class diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 7af5fcd3c1..f1015b16d5 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -1,5 +1,10 @@ source 'https://rubygems.org' +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + <% gemfile_entries.each do |gem| -%> <% if gem.comment -%> @@ -39,7 +44,7 @@ group :development do <%- end -%> <%- end -%> <% if depend_on_listen? -%> - gem 'listen', '~> 3.0.5' + gem 'listen', '>= 3.0.5', '< 3.2' <% end -%> <% if spring_install? -%> # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt index c88426ec06..25870f19c8 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt @@ -1,8 +1,8 @@ // This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's +// vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. JavaScript code in this file should be added after the last require_* statement. @@ -11,9 +11,11 @@ // about supported directives. // <% unless options[:skip_javascript] -%> +<% if options[:javascript] -%> //= require <%= options[:javascript] %> -//= require <%= options[:javascript] %>_ujs -<% if gemfile_entries.any? { |m| m.name == "turbolinks" } -%> +<% end -%> +//= require rails-ujs +<% unless options[:skip_turbolinks] -%> //= require turbolinks <% end -%> <% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css index 0ebd7fe829..865300bef9 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css @@ -2,8 +2,8 @@ * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt index d51f79bd49..5460155b3e 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt @@ -7,7 +7,7 @@ <%- if options[:skip_javascript] -%> <%%= stylesheet_link_tag 'application', media: 'all' %> <%- else -%> - <%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%> + <%- unless options[:skip_turbolinks] -%> <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%- else -%> diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt index acae810c1a..e4ba83a129 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/setup +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt @@ -16,7 +16,11 @@ chdir APP_ROOT do puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') +<% unless options[:skip_yarn] %> + system('bin/yarn') # Ignore failure from yarn not being installed +<% end %> +<% unless options.skip_active_record -%> # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') # cp 'config/database.yml.sample', 'config/database.yml' @@ -24,6 +28,7 @@ chdir APP_ROOT do puts "\n== Preparing database ==" system! 'bin/rails db:setup' +<% end -%> puts "\n== Removing old logs and tempfiles ==" system! 'bin/rails log:clear tmp:clear' diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update b/railties/lib/rails/generators/rails/app/templates/bin/update.tt index 770a605fed..cc188315a8 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/update +++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt @@ -16,9 +16,14 @@ chdir APP_ROOT do puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') +<% unless options[:skip_yarn] %> + system('bin/yarn') # Ignore failure from yarn not being installed +<% end %> +<% unless options.skip_active_record -%> puts "\n== Updating database ==" system! 'bin/rails db:migrate' +<% end -%> puts "\n== Removing old logs and tempfiles ==" system! 'bin/rails log:clear tmp:clear' diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn b/railties/lib/rails/generators/rails/app/templates/bin/yarn new file mode 100644 index 0000000000..cf48e2e5b7 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/bin/yarn @@ -0,0 +1,2 @@ +VENDOR_PATH = File.expand_path('../vendor', __dir__) +Dir.chdir(VENDOR_PATH) { exec "yarnpkg #{ARGV.join(" ")}" } diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt index 2318cf59ff..f5d03fb117 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt @@ -3,8 +3,12 @@ # Version of your assets, change this if you want to expire all your assets. Rails.application.config.assets.version = '1.0' -# Add additional assets to the asset load path +# Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path +<%- unless options[:skip_yarn] -%> +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('vendor/node_modules') +<%- end -%> # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt index 5ad18cc5ad..3ad3eba98a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt @@ -32,7 +32,9 @@ ActiveSupport.halt_callback_chains_on_return_false = <%= options[:update] ? true # Configure SSL options to enable HSTS with subdomains. Previous versions had false. Rails.application.config.ssl_options = { hsts: { subdomains: true } } <%- end -%> +<%- unless options[:skip_sprockets] -%> # Unknown asset fallback will return the path passed in when the given # asset is not present in the asset pipeline. Rails.application.config.assets.unknown_asset_fallback = <%= options[:update] ? true : false %> +<%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb index 121ad7080e..1e19380dcb 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb @@ -1,13 +1,13 @@ # Puma can serve each request in a thread from an internal thread pool. -# The `threads` method setting takes two numbers a minimum and maximum. +# The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum -# and maximum, this matches the default thread size of Active Record. +# and maximum; this matches the default thread size of Active Record. # threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } threads threads_count, threads_count -# Specifies the `port` that Puma will listen on to receive requests, default is 3000. +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. # port ENV.fetch("PORT") { 3000 } @@ -32,16 +32,25 @@ environment ENV.fetch("RAILS_ENV") { "development" } # # preload_app! +# If you are preloading your application and using Active Record, it's +# recommended that you close any connections to the database before workers +# are forked to prevent connection leakage. +# +# before_fork do +# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) +# end + # The code in the `on_worker_boot` will be called if you are using # clustered mode by specifying a number of `workers`. After each worker -# process is booted this block will be run, if you are using `preload_app!` -# option you will want to use this block to reconnect to any threads -# or connections that may have been created at application boot, Ruby +# process is booted, this block will be run. If you are using the `preload_app!` +# option, you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, as Ruby # cannot share connections between processes. # # on_worker_boot do # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) # end +# # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore index 0e66cc4237..709b341387 100644 --- a/railties/lib/rails/generators/rails/app/templates/gitignore +++ b/railties/lib/rails/generators/rails/app/templates/gitignore @@ -21,5 +21,8 @@ !/tmp/.keep <% end -%> -# Ignore Byebug command history file. +<% unless options[:skip_yarn] -%> +/vendor/node_modules + +<% end -%> .byebug_history diff --git a/railties/lib/rails/generators/rails/app/templates/package.json b/railties/lib/rails/generators/rails/app/templates/package.json new file mode 100644 index 0000000000..46db57dcbe --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/package.json @@ -0,0 +1,5 @@ +{ + "name": "<%= app_name %>", + "private": true, + "dependencies": {} +} diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb index 87b8fe3516..2f92168eef 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb @@ -1,4 +1,3 @@ -ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb index 213de37cce..ced3c85c00 100644 --- a/railties/lib/rails/generators/rails/controller/controller_generator.rb +++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb @@ -3,6 +3,8 @@ module Rails class ControllerGenerator < NamedBase # :nodoc: argument :actions, type: :array, default: [], banner: "action action" class_option :skip_routes, type: :boolean, desc: "Don't add routes to config/routes.rb." + class_option :helper, type: :boolean + class_option :assets, type: :boolean check_class_collision suffix: "Controller" diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 9ffeab4fbe..2186fa4ded 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -81,7 +81,7 @@ task default: :test end PASSTHROUGH_OPTIONS = [ - :skip_active_record, :skip_action_mailer, :skip_javascript, :database, + :skip_active_record, :skip_action_mailer, :skip_javascript, :skip_sprockets, :database, :javascript, :quiet, :pretend, :force, :skip ] @@ -270,8 +270,8 @@ task default: :test @name ||= begin # same as ActiveSupport::Inflector#underscore except not replacing '-' underscored = original_name.dup - underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') - underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + underscored.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2') + underscored.gsub!(/([a-z\d])([A-Z])/, '\1_\2') underscored.downcase! underscored @@ -283,7 +283,7 @@ task default: :test end def namespaced_name - @namespaced_name ||= name.gsub("-", "/") + @namespaced_name ||= name.tr("-", "/") end protected diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt index 62b94618fd..c0fbb84a93 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt @@ -5,4 +5,6 @@ require 'rails/test_unit/minitest_plugin' Rails::TestUnitReporter.executable = 'bin/test' -exit Minitest.run(ARGV) +Minitest.run_via[:rails] = true + +require "active_support/testing/autorun" diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb index a5eebcb19f..e84e403018 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb @@ -1,6 +1,3 @@ -# Configure Rails Environment -ENV["RAILS_ENV"] = "test" - require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__) <% unless options[:skip_active_record] -%> ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)] diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index 6d80003271..a28977319a 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -73,7 +73,7 @@ module Rails end # Initialize ORM::Generators::ActiveModel to access instance methods. - def orm_instance(name=singular_table_name) + def orm_instance(name = singular_table_name) @orm_instance ||= orm_class.new(name) end end diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb index 81b1cd8110..a2615d5efd 100644 --- a/railties/lib/rails/initializable.rb +++ b/railties/lib/rails/initializable.rb @@ -53,7 +53,7 @@ module Rails end end - def run_initializers(group=:default, *args) + def run_initializers(group = :default, *args) return if instance_variable_defined?(:@ran) initializers.tsort_each do |initializer| initializer.run(*args) if initializer.belongs_to?(group) diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 88ec2ba85b..10925de8b2 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -30,7 +30,7 @@ module Rails # root["config/routes"].inspect # => ["config/routes.rb"] # # The +add+ method accepts the following options as arguments: - # eager_load, autoload, autoload_once and glob. + # eager_load, autoload, autoload_once, and glob. # # Finally, the +Path+ object also provides a few helpers: # @@ -45,7 +45,6 @@ module Rails attr_accessor :path def initialize(path) - @current = nil @path = path @root = {} end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index eb3f5d4ee9..696db61f01 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -132,27 +132,19 @@ module Rails end def rake_tasks(&blk) - @rake_tasks ||= [] - @rake_tasks << blk if blk - @rake_tasks + register_block_for(:rake_tasks, &blk) end def console(&blk) - @load_console ||= [] - @load_console << blk if blk - @load_console + register_block_for(:load_console, &blk) end def runner(&blk) - @load_runner ||= [] - @load_runner << blk if blk - @load_runner + register_block_for(:runner, &blk) end def generators(&blk) - @generators ||= [] - @generators << blk if blk - @generators + register_block_for(:generators, &blk) end def abstract_railtie? @@ -195,6 +187,17 @@ module Rails super end end + + private + # receives an instance variable identifier, set the variable value if is + # blank and append given block to value, which will be used later in + # `#each_registered_block(type, &block)` + def register_block_for(type, &blk) + var_name = "@#{type}" + blocks = instance_variable_defined?(var_name) ? instance_variable_get(var_name) : instance_variable_set(var_name, []) + blocks << blk if blk + blocks + end end delegate :railtie_name, to: :class @@ -241,6 +244,7 @@ module Rails private + # run `&block` in every registered block in `#register_block_for` def each_registered_block(type, &block) klass = self.class while klass.respond_to?(type) diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index c60e59cb88..3a48c4c496 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -44,7 +44,7 @@ class SourceAnnotationExtractor # # If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above. # Otherwise the string contains just line and text. - def to_s(options={}) + def to_s(options = {}) s = "[#{line.to_s.rjust(options[:indent])}] " s << "[#{tag}] " if options[:tag] s << text @@ -66,7 +66,7 @@ class SourceAnnotationExtractor # See <tt>#find_in</tt> for a list of file extensions that will be taken into account. # # This class method is the single entry point for the rake tasks. - def self.enumerate(tag, options={}) + def self.enumerate(tag, options = {}) extractor = new(tag) dirs = options.delete(:dirs) || Annotation.directories extractor.display(extractor.find(dirs), options) @@ -126,7 +126,7 @@ class SourceAnnotationExtractor # Prints the mapping from filenames to annotations in +results+ ordered by filename. # The +options+ hash is passed to each annotation's +to_s+. - def display(results, options={}) + def display(results, options = {}) options[:indent] = results.flat_map { |f, a| a.map(&:line) }.max.to_s.size results.keys.sort.each do |file| puts "#{file}:" diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index a6cdd1e99c..8265aef10b 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -8,7 +8,7 @@ STATS_DIRECTORIES = [ %w(Models app/models), %w(Mailers app/mailers), %w(Channels app/channels), - %w(Javascripts app/assets/javascripts), + %w(JavaScripts app/assets/javascripts), %w(Libraries lib/), %w(Tasks lib/tasks), %w(APIs app/apis), diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index e15c6b3a38..6e196a32ab 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -61,19 +61,30 @@ module Minitest # as the patterns would also contain the other Rake tasks. def self.rake_run(patterns) # :nodoc: @rake_patterns = patterns - passed = run(Shellwords.split(ENV["TESTOPTS"] || "")) - exit passed unless passed - passed + autorun end + module RunRespectingRakeTestopts + def run(args = []) + if defined?(@rake_patterns) + args = Shellwords.split(ENV["TESTOPTS"] || "") + end + + super + end + end + + singleton_class.prepend RunRespectingRakeTestopts + # Owes great inspiration to test runner trailblazers like RSpec, # minitest-reporters, maxitest and others. def self.plugin_rails_init(options) - self.run_with_rails_extension = true - ENV["RAILS_ENV"] = options[:environment] || "test" - ::Rails::TestRequirer.require_files(options[:patterns]) unless run_with_autorun + # If run via `ruby` we've been passed the files to run directly. + unless run_via[:ruby] + ::Rails::TestRequirer.require_files(options[:patterns]) + end unless options[:full_backtrace] || ENV["BACKTRACE"] # Plugin can run without Rails loaded, check before filtering. @@ -86,8 +97,7 @@ module Minitest reporter << ::Rails::TestUnitReporter.new(options[:io], options) end - mattr_accessor(:run_with_autorun) { false } - mattr_accessor(:run_with_rails_extension) { false } + mattr_accessor(:run_via) { Hash.new } end # Put Rails as the first plugin minitest initializes so other plugins diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index d0fc795515..746120e6a1 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -14,7 +14,9 @@ module Rails end initializer "test_unit.line_filtering" do - ActiveSupport::TestCase.extend Rails::LineFiltering + ActiveSupport.on_load(:active_support_test_case) { + ActiveSupport::TestCase.extend Rails::LineFiltering + } end rake_tasks do |