diff options
Diffstat (limited to 'railties/lib/rails')
27 files changed, 525 insertions, 376 deletions
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 26ffbe5a58..6034aa8485 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -8,7 +8,7 @@ module Rails class << self attr_writer :config alias configure class_eval - delegate :initialize!, :load_tasks, :to => :instance + delegate :initialize!, :load_tasks, :root, :to => :instance private :new def instance @@ -83,17 +83,19 @@ module Rails end def initializers - initializers = super + initializers = Bootstrap.new(self).initializers plugins.each { |p| initializers += p.initializers } + initializers += super initializers end # TODO: Fix this method def plugins @plugins ||= begin - plugin_names = config.plugins || [:all] - Railtie.plugins.select { |p| plugin_names.include?(:all) || plugin_names.include?(p.plugin_name) }.map { |p| p.new } + - Plugin.all(config.plugins || [:all], config.paths.vendor.plugins) + plugin_names = (config.plugins || [:all]).map { |p| p.to_sym } + Railtie.plugins.select { |p| + plugin_names.include?(:all) || plugin_names.include?(p.plugin_name) + }.map { |p| p.new } + Plugin.all(plugin_names, config.paths.vendor.plugins) end end @@ -102,137 +104,6 @@ module Rails @app.call(env) end - initializer :load_all_active_support do - require "active_support/all" unless config.active_support.bare - end - - # Set the <tt>$LOAD_PATH</tt> based on the value of - # Configuration#load_paths. Duplicates are removed. - initializer :set_load_path do - config.paths.add_to_load_path - $LOAD_PATH.uniq! - end - - # Set the paths from which Rails will automatically load source files, and - # the load_once paths. - initializer :set_autoload_paths do - require 'active_support/dependencies' - ActiveSupport::Dependencies.load_paths = config.load_paths.uniq - ActiveSupport::Dependencies.load_once_paths = config.load_once_paths.uniq - - extra = ActiveSupport::Dependencies.load_once_paths - ActiveSupport::Dependencies.load_paths - unless extra.empty? - abort <<-end_error - load_once_paths must be a subset of the load_paths. - Extra items in load_once_paths: #{extra * ','} - end_error - end - - # Freeze the arrays so future modifications will fail rather than do nothing mysteriously - config.load_once_paths.freeze - end - - # Create tmp directories - initializer :ensure_tmp_directories_exist do - %w(cache pids sessions sockets).each do |dir_to_make| - FileUtils.mkdir_p(File.join(root, 'tmp', dir_to_make)) - end - end - - # Preload all frameworks specified by the Configuration#frameworks. - # Used by Passenger to ensure everything's loaded before forking and - # to avoid autoload race conditions in JRuby. - initializer :preload_frameworks do - ActiveSupport::Autoload.eager_autoload! if config.preload_frameworks - end - - initializer :initialize_cache do - unless defined?(RAILS_CACHE) - silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(config.cache_store) } - - if RAILS_CACHE.respond_to?(:middleware) - # Insert middleware to setup and teardown local cache for each request - config.middleware.insert_after(:"Rack::Lock", RAILS_CACHE.middleware) - end - end - end - - initializer :initialize_logger do - # if the environment has explicitly defined a logger, use it - next if Rails.logger - - unless logger = config.logger - begin - logger = ActiveSupport::BufferedLogger.new(config.log_path) - logger.level = ActiveSupport::BufferedLogger.const_get(config.log_level.to_s.upcase) - if RAILS_ENV == "production" - logger.auto_flushing = false - end - rescue StandardError => e - logger = ActiveSupport::BufferedLogger.new(STDERR) - logger.level = ActiveSupport::BufferedLogger::WARN - logger.warn( - "Rails Error: Unable to access log file. Please ensure that #{config.log_path} exists and is chmod 0666. " + - "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." - ) - end - end - - # TODO: Why are we silencing warning here? - silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } - end - - # Sets the logger for Active Record, Action Controller, and Action Mailer - # (but only for those frameworks that are to be loaded). If the framework's - # logger is already set, it is not changed, otherwise it is set to use - # RAILS_DEFAULT_LOGGER. - initializer :initialize_framework_logging do - ActiveSupport::Dependencies.logger ||= Rails.logger - Rails.cache.logger ||= Rails.logger - end - - # Sets the dependency loading mechanism based on the value of - # Configuration#cache_classes. - initializer :initialize_dependency_mechanism do - # TODO: Remove files from the $" and always use require - ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load - end - - # Loads support for "whiny nil" (noisy warnings when methods are invoked - # on +nil+ values) if Configuration#whiny_nils is true. - initializer :initialize_whiny_nils do - require('active_support/whiny_nil') if config.whiny_nils - end - - # Sets the default value for Time.zone - # If assigned value cannot be matched to a TimeZone, an exception will be raised. - initializer :initialize_time_zone do - if config.time_zone - require 'active_support/core_ext/time/zones' - zone_default = Time.__send__(:get_zone, config.time_zone) - - unless zone_default - raise \ - 'Value assigned to config.time_zone not recognized.' + - 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.' - end - - Time.zone_default = zone_default - end - end - - # Set the i18n configuration from config.i18n but special-case for the load_path which should be - # appended to what's already set instead of overwritten. - initializer :initialize_i18n do - config.i18n.each do |setting, value| - if setting == :load_path - I18n.load_path += value - else - I18n.send("#{setting}=", value) - end - end - end - # # bail out if gems are missing - note that check_gem_dependencies will have # # already called abort() unless $gems_rake_task is set # return unless gems_dependencies_loaded diff --git a/railties/lib/rails/bootstrap.rb b/railties/lib/rails/bootstrap.rb new file mode 100644 index 0000000000..15d5b2b59d --- /dev/null +++ b/railties/lib/rails/bootstrap.rb @@ -0,0 +1,140 @@ +module Rails + class Bootstrap #< Railtie + include Initializable + + def initialize(application) + @application = application + end + + delegate :config, :root, :to => :'@application' + + initializer :load_all_active_support do + require "active_support/all" unless config.active_support.bare + end + + # Set the <tt>$LOAD_PATH</tt> based on the value of + # Configuration#load_paths. Duplicates are removed. + initializer :set_load_path do + config.paths.add_to_load_path + $LOAD_PATH.uniq! + end + + # Set the paths from which Rails will automatically load source files, and + # the load_once paths. + initializer :set_autoload_paths do + require 'active_support/dependencies' + ActiveSupport::Dependencies.load_paths = config.load_paths.uniq + ActiveSupport::Dependencies.load_once_paths = config.load_once_paths.uniq + + extra = ActiveSupport::Dependencies.load_once_paths - ActiveSupport::Dependencies.load_paths + unless extra.empty? + abort <<-end_error + load_once_paths must be a subset of the load_paths. + Extra items in load_once_paths: #{extra * ','} + end_error + end + + # Freeze the arrays so future modifications will fail rather than do nothing mysteriously + config.load_once_paths.freeze + end + + # Create tmp directories + initializer :ensure_tmp_directories_exist do + %w(cache pids sessions sockets).each do |dir_to_make| + FileUtils.mkdir_p(File.join(root, 'tmp', dir_to_make)) + end + end + + # Preload all frameworks specified by the Configuration#frameworks. + # Used by Passenger to ensure everything's loaded before forking and + # to avoid autoload race conditions in JRuby. + initializer :preload_frameworks do + ActiveSupport::Autoload.eager_autoload! if config.preload_frameworks + end + + initializer :initialize_cache do + unless defined?(RAILS_CACHE) + silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(config.cache_store) } + + if RAILS_CACHE.respond_to?(:middleware) + # Insert middleware to setup and teardown local cache for each request + config.middleware.insert_after(:"Rack::Lock", RAILS_CACHE.middleware) + end + end + end + + initializer :initialize_logger do + # if the environment has explicitly defined a logger, use it + next if Rails.logger + + unless logger = config.logger + begin + logger = ActiveSupport::BufferedLogger.new(config.log_path) + logger.level = ActiveSupport::BufferedLogger.const_get(config.log_level.to_s.upcase) + if RAILS_ENV == "production" + logger.auto_flushing = false + end + rescue StandardError => e + logger = ActiveSupport::BufferedLogger.new(STDERR) + logger.level = ActiveSupport::BufferedLogger::WARN + logger.warn( + "Rails Error: Unable to access log file. Please ensure that #{config.log_path} exists and is chmod 0666. " + + "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." + ) + end + end + + # TODO: Why are we silencing warning here? + silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } + end + + # Sets the logger for Active Record, Action Controller, and Action Mailer + # (but only for those frameworks that are to be loaded). If the framework's + # logger is already set, it is not changed, otherwise it is set to use + # RAILS_DEFAULT_LOGGER. + initializer :initialize_framework_logging do + ActiveSupport::Dependencies.logger ||= Rails.logger + Rails.cache.logger ||= Rails.logger + end + + # Sets the dependency loading mechanism based on the value of + # Configuration#cache_classes. + initializer :initialize_dependency_mechanism do + # TODO: Remove files from the $" and always use require + ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load + end + + # Loads support for "whiny nil" (noisy warnings when methods are invoked + # on +nil+ values) if Configuration#whiny_nils is true. + initializer :initialize_whiny_nils do + require('active_support/whiny_nil') if config.whiny_nils + end + + # Sets the default value for Time.zone + # If assigned value cannot be matched to a TimeZone, an exception will be raised. + initializer :initialize_time_zone do + require 'active_support/core_ext/time/zones' + zone_default = Time.__send__(:get_zone, config.time_zone) + + unless zone_default + raise \ + 'Value assigned to config.time_zone not recognized.' + + 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.' + end + + Time.zone_default = zone_default + end + + # Set the i18n configuration from config.i18n but special-case for the load_path which should be + # appended to what's already set instead of overwritten. + initializer :initialize_i18n do + config.i18n.each do |setting, value| + if setting == :load_path + I18n.load_path += value + else + I18n.send("#{setting}=", value) + end + end + end + end +end diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb index f85c17bb94..a2eff377ce 100644 --- a/railties/lib/rails/commands/destroy.rb +++ b/railties/lib/rails/commands/destroy.rb @@ -7,4 +7,4 @@ if ARGV.size == 0 end name = ARGV.shift -Rails::Generators.invoke name, ARGV, :behavior => :revoke +Rails::Generators.invoke name, ARGV, :behavior => :revoke, :destination_root => Rails.root diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb index c5e3ae3529..c1120aad74 100755 --- a/railties/lib/rails/commands/generate.rb +++ b/railties/lib/rails/commands/generate.rb @@ -7,4 +7,4 @@ if ARGV.size == 0 end name = ARGV.shift -Rails::Generators.invoke name, ARGV, :behavior => :invoke +Rails::Generators.invoke name, ARGV, :behavior => :invoke, :destination_root => Rails.root diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index f0a0d5e55e..c180031c61 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -4,11 +4,24 @@ module Rails # Temporarily separate the plugin configuration class from the main # configuration class while this bit is being cleaned up. class Railtie::Configuration - def self.default @default ||= new end + def self.default_middleware_stack + ActionDispatch::MiddlewareStack.new.tap do |middleware| + middleware.use('ActionDispatch::Static', lambda { Rails.public_path }, :if => lambda { Rails.application.config.serve_static_assets }) + middleware.use('::Rack::Lock', :if => lambda { !ActionController::Base.allow_concurrency }) + middleware.use('::Rack::Runtime') + middleware.use('ActionDispatch::ShowExceptions', lambda { ActionController::Base.consider_all_requests_local }) + middleware.use('ActionDispatch::Callbacks', lambda { ActionController::Dispatcher.prepare_each_request }) + middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options }) + middleware.use('ActionDispatch::ParamsParser') + middleware.use('::Rack::MethodOverride') + middleware.use('::Rack::Head') + end + end + attr_reader :middleware def initialize(base = nil) @@ -17,7 +30,7 @@ module Rails @middleware = base.middleware.dup else @options = Hash.new { |h,k| h[k] = ActiveSupport::OrderedOptions.new } - @middleware = ActionDispatch::MiddlewareStack.new + @middleware = self.class.default_middleware_stack end end @@ -120,8 +133,8 @@ module Rails end def frameworks(*args) - raise "config.frameworks in no longer supported. See the generated" \ - "config/boot.rb for steps on how to limit the frameworks that" \ + raise "config.frameworks in no longer supported. See the generated " \ + "config/boot.rb for steps on how to limit the frameworks that " \ "will be loaded" end alias frameworks= frameworks @@ -231,6 +244,10 @@ module Rails @log_level ||= RAILS_ENV == 'production' ? :info : :debug end + def time_zone + @time_zone ||= "UTC" + end + def i18n @i18n ||= begin i18n = ActiveSupport::OrderedOptions.new diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb index 3c5b8bdec7..155166a113 100644 --- a/railties/lib/rails/deprecation.rb +++ b/railties/lib/rails/deprecation.rb @@ -15,11 +15,3 @@ RAILS_ROOT = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do ActiveSupport::Deprecation.warn(msg, callstack) end end).new - -module Rails - class Configuration - def gem(*args) - ActiveSupport::Deprecation.warn("config.gem has been deprecated in favor of the Gemfile.") - end - end -end
\ No newline at end of file diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 6419961415..3713a38b33 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -117,11 +117,15 @@ module Rails end # Remove the color from output. - # def self.no_color! Thor::Base.shell = Thor::Shell::Basic end + # Track all generators subclasses. + def self.subclasses + @subclasses ||= [] + end + # Generators load paths used on lookup. The lookup happens as: # # 1) lib generators @@ -147,18 +151,10 @@ module Rails end load_paths # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths. - # Rails finds namespaces exactly as thor, with three conveniences: - # - # 1) If your generator name ends with generator, as WebratGenerator, it sets - # its namespace to "webrat", so it can be invoked as "webrat" and not - # "webrat_generator"; + # Rails finds namespaces similar to thor, it only adds one rule: # - # 2) If your generator has a generators namespace, as Rails::Generators::WebratGenerator, - # the namespace is set to "rails:generators:webrat", but Rails allows it - # to be invoked simply as "rails:webrat". The "generators" is added - # automatically when doing the lookup; - # - # 3) Rails looks in load paths and loads the generator just before it's going to be used. + # 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. # # ==== Examples # @@ -166,113 +162,81 @@ module Rails # # Will search for the following generators: # - # "rails:generators:webrat", "webrat:generators:integration", "webrat" - # - # On the other hand, if "rails:webrat" is given, it will search for: + # "rails:webrat", "webrat:integration", "webrat" # - # "rails:generators:webrat", "rails:webrat" - # - # Notice that the "generators" namespace is handled automatically by Rails, - # so you don't need to type it when you want to invoke a generator in specific. + # 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: - name, attempts = name.to_s, [ ] - - case name.count(':') - when 1 - base, name = name.split(':') - return find_by_namespace(name, base) - when 0 - attempts += generator_names(base, name) if base - attempts += generator_names(name, context) if context - end - - attempts << name - attempts += generator_names(name, name) unless name.include?(?:) - attempts.uniq! - - unloaded = attempts - namespaces - lookup(unloaded) + # Mount regexps to lookup + regexps = [] + regexps << /^#{base}:[\w:]*#{name}$/ if base + regexps << /^#{name}:[\w:]*#{context}$/ if context + regexps << /^[(#{name}):]+$/ + regexps.uniq! + + # Check if generator happens to be loaded + checked = subclasses.dup + klass = find_by_regexps(regexps, checked) + return klass if klass + + # Try to require other generators by looking in load_paths + lookup(name, context) + unchecked = subclasses - checked + klass = find_by_regexps(regexps, unchecked) + return klass if klass + + # Invoke fallbacks + invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) + end - attempts.each do |namespace| - klass = Thor::Util.find_by_namespace(namespace) - return klass if klass + # Tries to find a generator which the namespace match the regexp. + def self.find_by_regexps(regexps, klasses) + klasses.find do |klass| + namespace = klass.namespace + regexps.find { |r| namespace =~ r } 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={}) - if klass = find_by_namespace(namespace, "rails") + names = namespace.to_s.split(':') + + if klass = find_by_namespace(names.pop, names.shift || "rails") args << "--help" if klass.arguments.any? { |a| a.required? } && args.empty? - klass.start args, config + klass.start(args, config) else puts "Could not find generator #{namespace}." end end # Show help message with available generators. - # def self.help - rails = Rails::Generators.builtin.map do |group, name| - name if group == "rails" - end - rails.compact! - rails.sort! - - puts "Please select a generator." - puts "Builtin: #{rails.join(', ')}." - - # Load paths and remove builtin - paths, others = load_paths.dup, [] - paths.pop - - paths.each do |path| - tail = [ "*", "*", "*_generator.rb" ] - - until tail.empty? - others += Dir[File.join(path, *tail)].collect do |file| - name = file.split('/')[-tail.size, 2] - name.last.sub!(/_generator\.rb$/, '') - name.uniq! - name.join(':') - end - tail.shift - end - end + builtin = Rails::Generators.builtin.each { |n| n.sub!(/^rails:/, '') } + builtin.sort! + lookup("*") + others = subclasses.map{ |k| k.namespace.gsub(':generators:', ':') } + others -= Rails::Generators.builtin others.sort! + + puts "Please select a generator." + puts "Builtin: #{builtin.join(', ')}." puts "Others: #{others.join(', ')}." unless others.empty? end protected - # Return all defined namespaces. - # - def self.namespaces #:nodoc: - Thor::Base.subclasses.map { |klass| klass.namespace } - end - - # Keep builtin generators in an Array[Array[group, name]]. - # + # Keep builtin generators in an Array. def self.builtin #:nodoc: Dir[File.dirname(__FILE__) + '/generators/*/*'].collect do |file| - file.split('/')[-2, 2] + file.split('/')[-2, 2].join(':') end end - # By default, Rails strips the generator namespace to make invocations - # easier. This method generaters the both possibilities names. - def self.generator_names(first, second) #:nodoc: - [ "#{first}:generators:#{second}", "#{first}:#{second}" ] - end - - # Try callbacks for the given base. - # + # Try fallbacks for the given base. def self.invoke_fallbacks_for(name, base) #:nodoc: return nil unless base && fallbacks[base.to_sym] invoked_fallbacks = [] @@ -290,10 +254,10 @@ module Rails # Receives namespaces in an array and tries to find matching generators # in the load path. - # - def self.lookup(attempts) #:nodoc: - attempts = attempts.map { |a| "#{a.split(":").last}_generator" }.uniq - attempts = "{#{attempts.join(',')}}.rb" + def self.lookup(*attempts) #:nodoc: + attempts.compact! + attempts.uniq! + attempts = "{#{attempts.join(',')}}_generator.rb" self.load_paths.each do |path| Dir[File.join(path, '**', attempts)].each do |file| diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 226ae63963..26abb46644 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -12,16 +12,6 @@ module Rails add_runtime_options! - # Always move to rails source root. - # - def initialize(*args) #:nodoc: - if !invoked?(args) && defined?(Rails.root) && Rails.root - self.destination_root = Rails.root - FileUtils.cd(destination_root) - end - super - end - # Automatically sets the source root based on the class name. # def self.source_root @@ -76,17 +66,18 @@ module Rails # # The controller generator will then try to invoke the following generators: # - # "rails:generators:test_unit", "test_unit:generators:controller", "test_unit" + # "rails:test_unit", "test_unit:controller", "test_unit" # - # In this case, the "test_unit:generators:controller" is available and is - # invoked. This allows any test framework to hook into Rails as long as it - # provides any of the hooks above. + # Notice that "rails:generators:test_unit" could be loaded as well, what + # Rails looks for is the first and last parts of the namespace. This is what + # allows any test framework to hook into Rails as long as it provides any + # of the hooks above. # # ==== Options # - # This lookup can be customized with two options: :base and :as. The first - # is the root module value and in the example above defaults to "rails". - # The later defaults to the generator name, without the "Generator" ending. + # The first and last part used to find the generator to be invoked are + # guessed based on class invokes hook_for, as noticed in the example above. + # This can be customized with two options: :base and :as. # # Let's suppose you are creating a generator that needs to invoke the # controller generator from test unit. Your first attempt is: @@ -97,7 +88,7 @@ module Rails # # The lookup in this case for test_unit as input is: # - # "test_unit:generators:awesome", "test_unit" + # "test_unit:awesome", "test_unit" # # Which is not the desired the lookup. You can change it by providing the # :as option: @@ -108,18 +99,18 @@ module Rails # # And now it will lookup at: # - # "test_unit:generators:awesome", "test_unit" + # "test_unit:controller", "test_unit" # # Similarly, if you want it to also lookup in the rails namespace, you just # need to provide the :base value: # # class AwesomeGenerator < Rails::Generators::Base - # hook_for :test_framework, :base => :rails, :as => :controller + # hook_for :test_framework, :in => :rails, :as => :controller # end # # And the lookup is exactly the same as previously: # - # "rails:generators:test_unit", "test_unit:generators:controller", "test_unit" + # "rails:test_unit", "test_unit:controller", "test_unit" # # ==== Switches # @@ -151,11 +142,11 @@ module Rails # ==== Custom invocations # # You can also supply a block to hook_for to customize how the hook is - # going to be invoked. The block receives two parameters, an instance + # going to be invoked. The block receives two arguments, an instance # of the current class and the klass to be invoked. # # For example, in the resource generator, the controller should be invoked - # with a pluralized class name. By default, it is invoked with the same + # with a pluralized class name. But by default it is invoked with the same # name as the resource generator, which is singular. To change this, we # can give a block to customize how the controller can be invoked. # @@ -178,11 +169,11 @@ module Rails end unless class_options.key?(name) - class_option name, defaults.merge!(options) + class_option(name, defaults.merge!(options)) end hooks[name] = [ in_base, as_hook ] - invoke_from_option name, options, &block + invoke_from_option(name, options, &block) end end @@ -193,7 +184,7 @@ module Rails # remove_hook_for :orm # def self.remove_hook_for(*names) - remove_invocation *names + remove_invocation(*names) names.each do |name| hooks.delete(name) @@ -219,12 +210,16 @@ module Rails # and can point to wrong directions when inside an specified directory. base.source_root - if base.name && base.name !~ /Base$/ && base.base_name && base.generator_name && defined?(Rails.root) && Rails.root - path = File.expand_path(File.join(Rails.root, 'lib', 'templates')) - if base.name.include?('::') - base.source_paths << File.join(path, base.base_name, base.generator_name) - else - base.source_paths << File.join(path, base.generator_name) + if base.name && base.name !~ /Base$/ + Rails::Generators.subclasses << base + + if defined?(Rails.root) && Rails.root + path = File.expand_path(File.join(Rails.root, 'lib', 'templates')) + if base.name.include?('::') + base.source_paths << File.join(path, base.base_name, base.generator_name) + else + base.source_paths << File.join(path, base.generator_name) + end end end end @@ -263,13 +258,6 @@ module Rails end end - # Check if this generator was invoked from another one by inspecting - # parameters. - # - def invoked?(args) - args.last.is_a?(Hash) && args.last.key?(:invocations) - end - # Use Rails default banner. # def self.banner @@ -290,12 +278,10 @@ module Rails # Rails::Generators::MetalGenerator will return "metal" as generator name. # def self.generator_name - if name - @generator_name ||= begin - if klass_name = name.to_s.split('::').last - klass_name.sub!(/Generator$/, '') - klass_name.underscore - end + @generator_name ||= begin + if generator = name.to_s.split('::').last + generator.sub!(/Generator$/, '') + generator.underscore end end end @@ -339,6 +325,7 @@ module Rails # def self.prepare_for_invocation(name, value) #:nodoc: if value && constants = self.hooks[name] + value = name if TrueClass === value Rails::Generators.find_by_namespace(value, *constants) else super diff --git a/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb index 51c4ad0e2e..7aa049fe80 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb @@ -1,7 +1,6 @@ <!DOCTYPE html> <html> <head> - <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <title><%= controller_class_name %>: <%%= controller.action_name %></title> <%%= stylesheet_link_tag 'scaffold' %> </head> diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index ee401b1fde..ba3eb67fe0 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -46,15 +46,16 @@ module Rails::Generators def initialize(*args) super - if !options[:no_activerecord] && !DATABASES.include?(options[:database]) + if !options[:skip_activerecord] && !DATABASES.include?(options[:database]) raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}." end end def create_root self.destination_root = File.expand_path(app_path, destination_root) - empty_directory '.' + valid_app_const? + empty_directory '.' set_default_accessors! FileUtils.cd(destination_root) end @@ -192,8 +193,19 @@ module Rails::Generators @app_name ||= File.basename(destination_root) end + def app_const_base + @app_const_base ||= app_name.gsub(/\W/, '_').squeeze('_').camelize + end + def app_const - @app_const ||= "#{app_name.classify}::Application" + @app_const ||= "#{app_const_base}::Application" + end + + def valid_app_const? + case app_const + when /^\d/ + raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers." + end end def app_secret diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 59c6d333e2..7b5c89c3e2 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -5,7 +5,8 @@ gem "rails", "<%= Rails::VERSION::STRING %>" ## Bundle edge rails: <%- if options.dev? -%> -gem "rails", :path => "<%= Rails::Generators::RAILS_DEV_PATH %>" +directory "<%= Rails::Generators::RAILS_DEV_PATH %>", :glob => "{*/,}*.gemspec" +gem "rails", "<%= Rails::VERSION::STRING %>" <%- else -%> <%= "# " unless options.edge? %>gem "rails", :git => "git://github.com/rails/rails.git" <%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb index 9889b52893..2cdf4eae54 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb @@ -1,8 +1,4 @@ -# Filters added to this controller apply to all controllers in the application. -# Likewise, all the methods added will be available for all controllers. - class ApplicationController < ActionController::Base - helper :all protect_from_forgery filter_parameter_logging :password end diff --git a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb index 22a7940eb2..de6be7945c 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb @@ -1,3 +1,2 @@ -# Methods added to this helper will be available to all templates in the application. module ApplicationHelper end diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru index acb8435446..2ab821e38d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config.ru +++ b/railties/lib/rails/generators/rails/app/templates/config.ru @@ -1,5 +1,4 @@ -# Require your environment file to bootstrap Rails -require ::File.expand_path('../config/environment', __FILE__) +# This file is used by Rack-based servers to start the application. -# Dispatch the request +require ::File.expand_path('../config/environment', __FILE__) run <%= app_const %>.instance diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index ec0729db04..334820826f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -1,13 +1,13 @@ require File.expand_path('../boot', __FILE__) -module <%= app_name.classify %> +module <%= app_const_base %> class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Add additional load paths for your own custom dirs - # config.load_paths += %W( #{root}/extras ) + # config.load_paths += %W( #{config.root}/extras ) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named @@ -17,15 +17,14 @@ module <%= app_name.classify %> # config.active_record.observers = :cacher, :garbage_collector, :forum_observer # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. - config.time_zone = 'UTC' + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] # config.i18n.default_locale = :de - # Configure generators values. Many other options are available, be sure to - # check the documentation. + # Configure generators values. Many other options are available, be sure to check the documentation. # config.generators do |g| # g.orm :active_record # g.template_engine :erb diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt index 9f05cd5a31..451dbe1d1c 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt @@ -4,4 +4,4 @@ # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. -ActionController::Base.cookie_verifier_secret = '<%= app_secret %>'; +ActionController::Base.cookie_verifier_secret = '<%= app_secret %>' diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_rails_defaults.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_rails_defaults.rb deleted file mode 100644 index 8ec3186c84..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_rails_defaults.rb +++ /dev/null @@ -1,19 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# These settings change the behavior of Rails 2 apps and will be defaults -# for Rails 3. You can remove this initializer when Rails 3 is released. - -if defined?(ActiveRecord) - # Include Active Record class name as root for JSON serialized output. - ActiveRecord::Base.include_root_in_json = true - - # Store the full class name (including module namespace) in STI type column. - ActiveRecord::Base.store_full_sti_class = true -end - -# Use ISO 8601 format for JSON serialized times and dates. -ActiveSupport.use_standard_json_time_format = true - -# Don't escape HTML entities in JSON, leave that for the #json_escape helper. -# if you're including raw json in an HTML page. -ActiveSupport.escape_html_entities_in_json = false
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt index 4499ab84b6..baff704d3e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt @@ -5,8 +5,8 @@ # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. ActionController::Base.session = { - :key => '_<%= app_name %>_session', - :secret => '<%= app_secret %>' + :key => '_<%= app_name %>_session', + :secret => '<%= app_secret %>' } # Use the database for sessions instead of the cookie-based default, diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html index 88ee108e90..9a48320a5f 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/404.html +++ b/railties/lib/rails/generators/rails/app/templates/public/404.html @@ -1,7 +1,6 @@ <!DOCTYPE html> <html> <head> - <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <title>The page you were looking for doesn't exist (404)</title> <style type="text/css"> body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html index 9c3c96670b..83660ab187 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/422.html +++ b/railties/lib/rails/generators/rails/app/templates/public/422.html @@ -1,7 +1,6 @@ <!DOCTYPE html> <html> <head> - <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <title>The change you wanted was rejected (422)</title> <style type="text/css"> body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html index f71c86e652..b80307fc16 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/500.html +++ b/railties/lib/rails/generators/rails/app/templates/public/500.html @@ -1,7 +1,6 @@ <!DOCTYPE html> <html> <head> - <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <title>We're sorry, but something went wrong (500)</title> <style type="text/css"> body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/rails/generators/rails/app/templates/public/index.html index ff2dfd3193..b153ae392f 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/index.html +++ b/railties/lib/rails/generators/rails/app/templates/public/index.html @@ -1,7 +1,6 @@ <!DOCTYPE html> <html> <head> - <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <title>Ruby on Rails: Welcome aboard</title> <style type="text/css" media="screen"> body { 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 a16f587d8b..45b551fc7d 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 @@ -3,31 +3,6 @@ require File.expand_path(File.dirname(__FILE__) + "/../config/environment") require 'rails/test_help' class ActiveSupport::TestCase - # Transactional fixtures accelerate your tests by wrapping each test method - # in a transaction that's rolled back on completion. This ensures that the - # test database remains unchanged so your fixtures don't have to be reloaded - # between every test method. Fewer database queries means faster tests. - # - # Read Mike Clark's excellent walkthrough at - # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting - # - # Every Active Record database supports transactions except MyISAM tables - # in MySQL. Turn off transactional fixtures in this case; however, if you - # don't care one way or the other, switching from MyISAM to InnoDB tables - # is recommended. - # - # The only drawback to using transactional fixtures is when you actually - # need to test transactions. Since your test is bracketed by a transaction, - # any transactions started in your code will be automatically rolled back. - self.use_transactional_fixtures = true - - # Instantiated fixtures are slow, but give you @david where otherwise you - # would need people(:david). If you don't want to migrate your existing - # test cases which use the @david style and don't mind the speed hit (each - # instantiated fixtures translates to a database query per test method), - # then set this back to true. - self.use_instantiated_fixtures = false - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb new file mode 100644 index 0000000000..38a3cbb035 --- /dev/null +++ b/railties/lib/rails/generators/test_case.rb @@ -0,0 +1,238 @@ +require 'active_support/core_ext/class/inheritable_attributes' +require 'active_support/core_ext/hash/reverse_merge' +require 'rails/generators' +require 'fileutils' + +module Rails + module Generators + # Disable color in output. Easier to debug. + no_color! + + # This class provides a TestCase for testing generators. To setup, you need + # just to configure the destination and set which generator is being tested: + # + # class AppGeneratorTest < Rails::Generators::TestCase + # tests AppGenerator + # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # end + # + # If you want to ensure your destination root is clean before running each test, + # you can set a setup callback: + # + # class AppGeneratorTest < Rails::Generators::TestCase + # tests AppGenerator + # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # setup :prepare_destination + # end + # + class TestCase < ActiveSupport::TestCase + include FileUtils + + extlib_inheritable_accessor :destination_root, :current_path, :generator_class, + :default_arguments, :instance_writer => false + + # Generators frequently change the current path using +FileUtils.cd+. + # So we need to store the path at file load and revert back to it after each test. + self.current_path = File.expand_path(Dir.pwd) + self.default_arguments = [] + + setup :destination_root_is_set?, :ensure_current_path + teardown :ensure_current_path + + # Sets which generator should be tested: + # + # tests AppGenerator + # + def self.tests(klass) + self.generator_class = klass + end + + # Sets default arguments on generator invocation. This can be overwritten when + # invoking it. + # + # arguments %w(app_name --skip-activerecord) + # + def self.arguments(array) + self.default_arguments = array + end + + # Sets the destination of generator files: + # + # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # + def self.destination(path) + self.destination_root = path + end + + # Captures the given stream and returns it: + # + # stream = capture(:stdout){ puts "Cool" } + # stream #=> "Cool\n" + # + def capture(stream) + begin + stream = stream.to_s + eval "$#{stream} = StringIO.new" + yield + result = eval("$#{stream}").string + ensure + eval("$#{stream} = #{stream.upcase}") + end + + result + end + alias :silence :capture + + # Asserts a given file exists. You need to supply an absolute path or a path relative + # to the configured destination: + # + # assert_file "config/environment.rb" + # + # You can also give extra arguments. If the argument is a regexp, it will check if the + # regular expression matches the given file content. If it's a string, it compares the + # file with the given string: + # + # assert_file "config/environment.rb", /initialize/ + # + # Finally, when a block is given, it yields the file content: + # + # assert_file "app/controller/products_controller.rb" do |controller| + # assert_instance_method :index, content do |index| + # assert_match /Product\.all/, index + # end + # end + # + def assert_file(relative, *contents) + absolute = File.expand_path(relative, destination_root) + assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not" + + read = File.read(absolute) if block_given? || !contents.empty? + yield read if block_given? + + contents.each do |content| + case content + when String + assert_equal content, read + when Regexp + assert_match content, read + end + end + end + alias :assert_directory :assert_file + + # Asserts a given file does not exist. You need to supply an absolute path or a + # path relative to the configured destination: + # + # assert_no_file "config/random.rb" + # + def assert_no_file(relative) + absolute = File.expand_path(relative, destination_root) + assert !File.exists?(absolute), "Expected file #{relative.inspect} to not exist, but does" + end + alias :assert_no_directory :assert_no_file + + # Asserts a given file does not exist. You need to supply an absolute path or a + # path relative to the configured destination: + # + # assert_migration "db/migrate/create_products.rb" + # + # This method manipulates the given path and tries to find any migration which + # matches the migration name. For example, the call above is converted to: + # + # assert_file "db/migrate/003_create_products.rb" + # + # Consequently, assert_migration accepts the same arguments has assert_file. + # + def assert_migration(relative, *contents, &block) + file_name = migration_file_name(relative) + assert file_name, "Expected migration #{relative} to exist, but was not found" + assert_file file_name, *contents, &block + end + + # Asserts a given migration does not exist. You need to supply an absolute path or a + # path relative to the configured destination: + # + # assert_no_file "config/random.rb" + # + def assert_no_migration(relative) + file_name = migration_file_name(relative) + assert_nil file_name, "Expected migration #{relative} to not exist, but found #{file_name}" + end + + # Asserts the given class method exists in the given content. This method does not detect + # class methods inside (class << self), only class methods which starts with "self.". + # When a block is given, it yields the content of the method. + # + # assert_migration "db/migrate/create_products.rb" do |migration| + # assert_class_method :up, migration do |up| + # assert_match /create_table/, up + # end + # end + # + def assert_class_method(method, content, &block) + assert_instance_method "self.#{method}", content, &block + end + + # Asserts the given method exists in the given content. When a block is given, + # it yields the content of the method. + # + # assert_file "app/controller/products_controller.rb" do |controller| + # assert_instance_method :index, content do |index| + # assert_match /Product\.all/, index + # end + # end + # + def assert_instance_method(method, content) + assert content =~ /def #{method}(\(.+\))?(.*?)\n end/m, "Expected to have method #{method}" + yield $2.strip if block_given? + end + alias :assert_method :assert_instance_method + + # Runs the generator configured for this class. The first argument is an array like + # command line arguments: + # + # class AppGeneratorTest < Rails::Generators::TestCase + # tests AppGenerator + # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # teardown :cleanup_destination_root + # + # test "database.yml is not created when skipping activerecord" do + # run_generator %w(myapp --skip-activerecord) + # assert_no_file "config/database.yml" + # end + # end + # + # You can provide a configuration hash as second argument. This method returns the output + # printed by the generator. + def run_generator(args=self.default_arguments, config={}) + capture(:stdout) { self.generator_class.start(args, config.reverse_merge(:destination_root => destination_root)) } + end + + # Instantiate the generator. + def generator(args=self.default_arguments, options={}, config={}) + @generator ||= self.generator_class.new(args, options, config.reverse_merge(:destination_root => destination_root)) + end + + protected + + def destination_root_is_set? #:nodoc: + raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root + end + + def ensure_current_path #:nodoc: + cd current_path + end + + def prepare_destination + rm_rf(destination_root) + mkdir_p(destination_root) + end + + def migration_file_name(relative) #:nodoc: + absolute = File.expand_path(relative, destination_root) + dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '') + Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first + end + end + end +end diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb index 348ec33582..2ca36a1e44 100644 --- a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb +++ b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb @@ -1,5 +1,3 @@ require 'rubygems' require 'test/unit' require 'active_support' -require 'active_support/test_case' - diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb index e4bf4035da..9380aa49b6 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb @@ -16,7 +16,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase test "should create <%= file_name %>" do assert_difference('<%= class_name %>.count') do - post :create, :<%= file_name %> => { } + post :create, :<%= file_name %> => <%= table_name %>(:one).attributes end assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>)) @@ -33,7 +33,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase end test "should update <%= file_name %>" do - put :update, :id => <%= table_name %>(:one).to_param, :<%= file_name %> => { } + put :update, :id => <%= table_name %>(:one).to_param, :<%= file_name %> => <%= table_name %>(:one).attributes assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>)) end diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 2601765065..e76c476bfb 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -2,28 +2,13 @@ # so fixtures are loaded to the right database silence_warnings { RAILS_ENV = "test" } -require 'rack' -require 'rack/test' - require 'test/unit' require 'active_support/core_ext/kernel/requires' -# AP is always present -require 'action_controller/test_case' -require 'action_view/test_case' - -require 'action_mailer/test_case' if defined?(ActionMailer) -require 'active_model/test_case' if defined?(ActiveModel) - if defined?(ActiveRecord) - require 'active_record/test_case' - require 'active_record/fixtures' - class ActiveSupport::TestCase include ActiveRecord::TestFixtures self.fixture_path = "#{Rails.root}/test/fixtures/" - self.use_instantiated_fixtures = false - self.use_transactional_fixtures = true end ActionController::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path |