diff options
30 files changed, 541 insertions, 143 deletions
@@ -1,98 +1,98 @@ -source "http://rubygems.org" +source 'https://rubygems.org' gemspec if ENV['AREL'] - gem "arel", :path => ENV['AREL'] + gem 'arel', :path => ENV['AREL'] else - gem "arel", :git => "git://github.com/rails/arel" + gem 'arel', :git => 'git://github.com/rails/arel' end -gem "bcrypt-ruby", "~> 3.0.0" -gem "jquery-rails" +gem 'bcrypt-ruby', '~> 3.0.0' +gem 'jquery-rails' if ENV['JOURNEY'] - gem "journey", :path => ENV['JOURNEY'] + gem 'journey', :path => ENV['JOURNEY'] else - gem "journey", :git => "git://github.com/rails/journey" + gem 'journey', :git => 'git://github.com/rails/journey' end # This needs to be with require false to avoid # it being automatically loaded by sprockets -gem "uglifier", ">= 1.0.3", :require => false +gem 'uglifier', '>= 1.0.3', :require => false -gem "rake", ">= 0.8.7" -gem "mocha", ">= 0.9.8" +gem 'rake', '>= 0.8.7' +gem 'mocha', '>= 0.9.8' group :doc do - gem "rdoc", "~> 3.4" + gem 'rdoc', '~> 3.4' # The current sdoc cannot generate GitHub links due # to a bug, but the PR that fixes it has been there # for some weeks unapplied. As a temporary solution # this is our own fork with the fix. - gem "sdoc", :git => 'git://github.com/fxn/sdoc.git' - gem "RedCloth", "~> 4.2" if RUBY_VERSION < "1.9.3" - gem "w3c_validators" + gem 'sdoc', :git => 'git://github.com/fxn/sdoc.git' + gem 'RedCloth', '~> 4.2' if RUBY_VERSION < '1.9.3' + gem 'w3c_validators' end # AS -gem "memcache-client", ">= 1.8.5" +gem 'memcache-client', '>= 1.8.5' platforms :mri_18 do - gem "system_timer" - gem "json" + gem 'system_timer' + gem 'json' end # Add your own local bundler stuff -instance_eval File.read ".Gemfile" if File.exists? ".Gemfile" +instance_eval File.read '.Gemfile' if File.exists? '.Gemfile' platforms :mri do group :test do - gem "ruby-prof" if RUBY_VERSION < "1.9.3" + gem 'ruby-prof' if RUBY_VERSION < '1.9.3' end end platforms :ruby do - gem "json" - gem "yajl-ruby" - gem "nokogiri", ">= 1.4.5" + gem 'json' + gem 'yajl-ruby' + gem 'nokogiri', '>= 1.4.5' # AR - gem "sqlite3", "~> 1.3.5" + gem 'sqlite3', '~> 1.3.5' group :db do - gem "pg", ">= 0.11.0" - gem "mysql", ">= 2.8.1" - gem "mysql2", ">= 0.3.10" + gem 'pg', '>= 0.11.0' + gem 'mysql', '>= 2.8.1' + gem 'mysql2', '>= 0.3.10' end end platforms :jruby do - gem "json" - gem "activerecord-jdbcsqlite3-adapter", ">= 1.2.0" + gem 'json' + gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.0' # This is needed by now to let tests work on JRuby # TODO: When the JRuby guys merge jruby-openssl in # jruby this will be removed - gem "jruby-openssl" + gem 'jruby-openssl' group :db do - gem "activerecord-jdbcmysql-adapter", ">= 1.2.0" - gem "activerecord-jdbcpostgresql-adapter", ">= 1.2.0" + gem 'activerecord-jdbcmysql-adapter', '>= 1.2.0' + gem 'activerecord-jdbcpostgresql-adapter', '>= 1.2.0' end end # gems that are necessary for ActiveRecord tests with Oracle database if ENV['ORACLE_ENHANCED_PATH'] || ENV['ORACLE_ENHANCED'] platforms :ruby do - gem "ruby-oci8", ">= 2.0.4" + gem 'ruby-oci8', '>= 2.0.4' end if ENV['ORACLE_ENHANCED_PATH'] - gem "activerecord-oracle_enhanced-adapter", :path => ENV['ORACLE_ENHANCED_PATH'] + gem 'activerecord-oracle_enhanced-adapter', :path => ENV['ORACLE_ENHANCED_PATH'] else - gem "activerecord-oracle_enhanced-adapter", :git => "git://github.com/rsim/oracle-enhanced.git" + gem 'activerecord-oracle_enhanced-adapter', :git => 'git://github.com/rsim/oracle-enhanced.git' end end # A gem necessary for ActiveRecord tests with IBM DB -gem "ibm_db" if ENV['IBM_DB'] +gem 'ibm_db' if ENV['IBM_DB'] diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index 444754d0e9..5c03a29f0f 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -19,8 +19,9 @@ module ActionMailer options.stylesheets_dir ||= paths["public/stylesheets"].first # make sure readers methods get compiled - options.asset_path ||= app.config.asset_path - options.asset_host ||= app.config.asset_host + options.asset_path ||= app.config.asset_path + options.asset_host ||= app.config.asset_host + options.relative_url_root ||= app.config.relative_url_root ActiveSupport.on_load(:action_mailer) do include AbstractController::UrlFor diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb index c2a6809f58..dd5f9a1942 100644 --- a/actionpack/lib/abstract_controller/asset_paths.rb +++ b/actionpack/lib/abstract_controller/asset_paths.rb @@ -4,7 +4,7 @@ module AbstractController included do config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, - :stylesheets_dir, :default_asset_host_protocol + :stylesheets_dir, :default_asset_host_protocol, :relative_url_root end end end diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index de7b837ecc..cb2e2b17aa 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -31,6 +31,7 @@ module ActionController # make sure readers methods get compiled options.asset_path ||= app.config.asset_path options.asset_host ||= app.config.asset_host + options.relative_url_root ||= app.config.relative_url_root ActiveSupport.on_load(:action_controller) do include app.routes.mounted_helpers diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 29289a76b4..4f48f1c974 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -43,34 +43,58 @@ module ActionDispatch # Execute all prepare callbacks. def self.prepare! - new(nil).run_callbacks :prepare + new(nil).prepare! end # Execute all cleanup callbacks. def self.cleanup! - new(nil).run_callbacks :cleanup + new(nil).cleanup! end - def initialize(app) + def initialize(app, condition=nil) @app = app - end - - module CleanupOnClose - def close - super if defined?(super) - ensure - ActionDispatch::Reloader.cleanup! - end + @condition = condition || lambda { true } + @validated = true end def call(env) - run_callbacks :prepare + @validated = @condition.call + prepare! response = @app.call(env) - response[2].extend(CleanupOnClose) + response[2].extend(module_hook) response rescue Exception - run_callbacks :cleanup + cleanup! raise end + + def prepare! #:nodoc: + run_callbacks :prepare if validated? + end + + def cleanup! #:nodoc: + run_callbacks :cleanup if validated? + ensure + @validated = true + end + + private + + def validated? #:nodoc: + @validated + end + + def module_hook #:nodoc: + middleware = self + Module.new do + define_method :close do + begin + super() if defined?(super) + ensure + middleware.cleanup! + end + end + end + end end end diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb index eaabc1feb3..3411bd14ea 100644 --- a/actionpack/test/dispatch/reloader_test.rb +++ b/actionpack/test/dispatch/reloader_test.rb @@ -43,6 +43,19 @@ class ReloaderTest < Test::Unit::TestCase assert_respond_to body, :close end + def test_condition_specifies_when_to_reload + i, j = 0, 0, 0, 0 + Reloader.to_prepare { |*args| i += 1 } + Reloader.to_cleanup { |*args| j += 1 } + app = Reloader.new(lambda { |env| [200, {}, []] }, lambda { i < 3 }) + 5.times do + resp = app.call({}) + resp[2].close + end + assert_equal 3, i + assert_equal 3, j + end + def test_returned_body_object_behaves_like_underlying_object body = call_and_return_body do b = MyBody.new @@ -116,6 +129,15 @@ class ReloaderTest < Test::Unit::TestCase assert cleaned end + def test_prepend_prepare_callback + i = 10 + Reloader.to_prepare { i += 1 } + Reloader.to_prepare(:prepend => true) { i = 0 } + + Reloader.prepare! + assert_equal 1, i + end + def test_cleanup_callbacks_are_called_on_exceptions cleaned = false Reloader.to_cleanup { cleaned = true } diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 268799b4b3..cd8e50b33a 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -146,6 +146,10 @@ during :reject_if => :all_blank (fixes #2937) *Aaron Christy* + +* Add ActiveSupport::Cache::NullStore for use in development and testing. + + *Brian Durand* ## Rails 3.1.3 (unreleased) ## diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index c2e31579a4..71772529d2 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -94,6 +94,11 @@ module ActiveRecord end end + initializer "active_record.add_watchable_files" do |app| + files = ["#{app.root}/db/schema.rb", "#{app.root}/db/structure.sql"] + config.watchable_files.concat files.select { |f| File.exist?(f) } + end + config.after_initialize do ActiveSupport.on_load(:active_record) do instantiate_observers diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 07f5fcdeb3..9711ed6f73 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -16,6 +16,7 @@ module ActiveSupport autoload :FileStore, 'active_support/cache/file_store' autoload :MemoryStore, 'active_support/cache/memory_store' autoload :MemCacheStore, 'active_support/cache/mem_cache_store' + autoload :NullStore, 'active_support/cache/null_store' # These options mean something to all cache implementations. Individual cache # implementations may support additional options. diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb new file mode 100644 index 0000000000..4427eaafcd --- /dev/null +++ b/activesupport/lib/active_support/cache/null_store.rb @@ -0,0 +1,44 @@ +module ActiveSupport + module Cache + # A cache store implementation which doesn't actually store anything. Useful in + # development and test environments where you don't want caching turned on but + # need to go through the caching interface. + # + # This cache does implement the local cache strategy, so values will actually + # be cached inside blocks that utilize this strategy. See + # ActiveSupport::Cache::Strategy::LocalCache for more details. + class NullStore < Store + def initialize(options = nil) + super(options) + extend Strategy::LocalCache + end + + def clear(options = nil) + end + + def cleanup(options = nil) + end + + def increment(name, amount = 1, options = nil) + end + + def decrement(name, amount = 1, options = nil) + end + + def delete_matched(matcher, options = nil) + end + + protected + def read_entry(key, options) # :nodoc: + end + + def write_entry(key, entry, options) # :nodoc: + true + end + + def delete_entry(key, options) # :nodoc: + false + end + end + end +end diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index f76ddff038..4137bbf6a0 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -1,3 +1,6 @@ +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/array/extract_options" + module ActiveSupport # This class is responsible to track files and invoke the given block # whenever one of these files are changed. For example, this class @@ -15,22 +18,86 @@ module ActiveSupport class FileUpdateChecker attr_reader :paths, :last_update_at + # It accepts two parameters on initialization. The first is + # the *paths* and the second is *calculate*, a boolean. + # + # paths must be an array of file paths but can contain a hash as + # last argument. The hash must have directories as keys and the + # value is an array of extensions to be watched under that directory. + # + # If *calculate* is true, the latest updated at will calculated + # on initialization, therefore, the first call to execute_if_updated + # will only evaluate the block if something really changed. + # + # This method must also receive a block that will be the block called + # once a file changes. + # + # This particular implementation checks for added files and updated files, + # but not removed files. Directories lookup are compiled to a glob for + # performance. def initialize(paths, calculate=false, &block) @paths = paths + @glob = compile_glob(@paths.extract_options!) @block = block + @updated_at = nil @last_update_at = calculate ? updated_at : nil end - def updated_at - paths.map { |path| File.mtime(path) }.max + # Check if any of the entries were updated. If so, the updated_at + # value is cached until flush! is called. + def updated? + current_updated_at = updated_at + if @last_update_at != current_updated_at + @updated_at = updated_at + true + else + false + end + end + + # Flush the cache so updated? is calculated again + def flush! + @updated_at = nil end + # Execute the block given if updated. This call + # always flush the cache. def execute_if_updated - current_update_at = self.updated_at - if @last_update_at != current_update_at - @last_update_at = current_update_at + if updated? + @last_update_at = updated_at @block.call + true + else + false end + ensure + flush! + end + + private + + def updated_at #:nodoc: + @updated_at || begin + all = [] + all.concat @paths + all.concat Dir[@glob] if @glob + all.map { |path| File.mtime(path) }.max + end + end + + def compile_glob(hash) #:nodoc: + return if hash.empty? + globs = [] + hash.each do |key, value| + globs << "#{key}/**/*#{compile_ext(value)}" + end + "{#{globs.join(",")}}" + end + + def compile_ext(array) #:nodoc: + array = Array.wrap(array) + return if array.empty? + ".{#{array.join(",")}}" end end end diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index 4c59fe9ac9..a989ff8f57 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -17,7 +17,8 @@ module I18n # point, no path was added to the reloader, I18n.reload! is not triggered # on to_prepare callbacks. This will only happen on the config.after_initialize # callback below. - initializer "i18n.callbacks" do + initializer "i18n.callbacks" do |app| + app.reloaders << I18n::Railtie.reloader ActionDispatch::Reloader.to_prepare do I18n::Railtie.reloader.execute_if_updated end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 5d7464c623..ab29ddecc5 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -218,7 +218,7 @@ module CacheStoreBehavior @cache.write('fud', 'biz') assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu')) end - + def test_read_multi_with_expires @cache.write('foo', 'bar', :expires_in => 0.001) @cache.write('fu', 'baz') @@ -576,12 +576,12 @@ class FileStoreTest < ActiveSupport::TestCase key = @cache_with_pathname.send(:key_file_path, "views/index?id=1") assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key) end - + # Because file systems have a maximum filename size, filenames > max size should be split in to directories # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B def test_key_transformation_max_filename_size key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B" - path = @cache.send(:key_file_path, key) + path = @cache.send(:key_file_path, key) assert path.split('/').all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE} assert_equal 'B', File.basename(path) end @@ -689,14 +689,14 @@ uses_memcached 'memcached backed store' do cache.write("foo", 2) assert_equal "2", cache.read("foo") end - + def test_raw_values_with_marshal cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) cache.clear cache.write("foo", Marshal.dump([])) - assert_equal [], cache.read("foo") + assert_equal [], cache.read("foo") end - + def test_local_cache_raw_values cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) cache.clear @@ -717,6 +717,64 @@ uses_memcached 'memcached backed store' do end end +class NullStoreTest < ActiveSupport::TestCase + def setup + @cache = ActiveSupport::Cache.lookup_store(:null_store) + end + + def test_clear + @cache.clear + end + + def test_cleanup + @cache.cleanup + end + + def test_write + assert_equal true, @cache.write("name", "value") + end + + def test_read + @cache.write("name", "value") + assert_nil @cache.read("name") + end + + def test_delete + @cache.write("name", "value") + assert_equal false, @cache.delete("name") + end + + def test_increment + @cache.write("name", 1, :raw => true) + assert_nil @cache.increment("name") + end + + def test_decrement + @cache.write("name", 1, :raw => true) + assert_nil @cache.increment("name") + end + + def test_delete_matched + @cache.write("name", "value") + @cache.delete_matched(/name/) + end + + def test_local_store_strategy + @cache.with_local_cache do + @cache.write("name", "value") + assert_equal "value", @cache.read("name") + @cache.delete("name") + assert_nil @cache.read("name") + @cache.write("name", "value") + end + assert_nil @cache.read("name") + end + + def test_setting_nil_cache_store + assert ActiveSupport::Cache.lookup_store.class.name, ActiveSupport::Cache::NullStore.name + end +end + class CacheStoreLoggerTest < ActiveSupport::TestCase def setup @cache = ActiveSupport::Cache.lookup_store(:memory_store) diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb index b65bb1d024..52c1f3260d 100644 --- a/activesupport/test/file_update_checker_test.rb +++ b/activesupport/test/file_update_checker_test.rb @@ -4,14 +4,16 @@ require 'fileutils' MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__) -class FileUpdateCheckerTest < Test::Unit::TestCase +class FileUpdateCheckerWithEnumerableTest < Test::Unit::TestCase FILES = %w(1.txt 2.txt 3.txt) def setup + FileUtils.mkdir_p("tmp_watcher") FileUtils.touch(FILES) end def teardown + FileUtils.rm_rf("tmp_watcher") FileUtils.rm(FILES) end @@ -38,18 +40,50 @@ class FileUpdateCheckerTest < Test::Unit::TestCase def test_should_not_invoke_the_block_if_no_file_has_changed i = 0 - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - 5.times { checker.execute_if_updated } - assert_equal 1, i + checker = ActiveSupport::FileUpdateChecker.new(FILES, true){ i += 1 } + 5.times { assert !checker.execute_if_updated } + assert_equal 0, i end def test_should_invoke_the_block_if_a_file_has_changed i = 0 - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - checker.execute_if_updated + checker = ActiveSupport::FileUpdateChecker.new(FILES, true){ i += 1 } sleep(1) FileUtils.touch(FILES) - checker.execute_if_updated - assert_equal 2, i + assert checker.execute_if_updated + assert_equal 1, i + end + + def test_should_cache_updated_result_until_flushed + i = 0 + checker = ActiveSupport::FileUpdateChecker.new(FILES, true){ i += 1 } + assert !checker.updated? + + sleep(1) + FileUtils.touch(FILES) + + assert checker.updated? + assert checker.execute_if_updated + assert !checker.updated? + end + + def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob + i = 0 + checker = ActiveSupport::FileUpdateChecker.new([{"tmp_watcher" => [:txt]}], true){ i += 1 } + FileUtils.cd "tmp_watcher" do + FileUtils.touch(FILES) + end + assert checker.execute_if_updated + assert_equal 1, i + end + + def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob + i = 0 + checker = ActiveSupport::FileUpdateChecker.new([{"tmp_watcher" => :rb}], true){ i += 1 } + FileUtils.cd "tmp_watcher" do + FileUtils.touch(FILES) + end + assert !checker.execute_if_updated + assert_equal 0, i end -end +end
\ No newline at end of file diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 2841996b56..a88f443517 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,9 +1,8 @@ ## Rails 3.2.0 (unreleased) ## -* New applications get a flag - `config.active_record.auto_explain_threshold_in_seconds` in the environments - configuration files. With a value of 0.5 in development.rb, and commented - out in production.rb. No mention in test.rb. *fxn* +* Speed up development by only reloading classes if dependencies files changed. This can be turned off by setting `config.reload_classes_only_on_change` to false. *José Valim* + +* New applications get a flag `config.active_record.auto_explain_threshold_in_seconds` in the environments configuration files. With a value of 0.5 in development.rb, and commented out in production.rb. No mention in test.rb. *fxn* * Add DebugExceptions middleware which contains features extracted from ShowExceptions middleware *José Valim* diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index 0ef6f51190..ec9bfd4d40 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -332,6 +332,14 @@ caches_action :index, :expires_in => 60.seconds, :unless_exist => true For more information about Ehcache, see "http://ehcache.org/":http://ehcache.org/ . For more information about Ehcache for JRuby and Rails, see "http://ehcache.org/documentation/jruby.html":http://ehcache.org/documentation/jruby.html +h4. ActiveSupport::Cache::NullStore + +This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with +Rails.cache+, but caching may interfere with being able to see the results of code changes. With this cache store, all +fetch+ and +read+ operations will result in a miss. + +<ruby> +ActionController::Base.cache_store = :null +</ruby> + h4. Custom Cache Stores You can create your own custom cache store by simply extending +ActiveSupport::Cache::Store+ and implementing the appropriate methods. In this way, you can swap in any number of caching technologies into your Rails application. diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 8e65dbccbb..8cf88cf71f 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -98,6 +98,8 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is * +config.preload_frameworks+ enables or disables preloading all frameworks at startup. Enabled by +config.threadsafe!+. Defaults to +nil+, so is disabled. +* +config.reload_classes_only_on_change+ enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. + * +config.reload_plugins+ enables or disables plugin reloading. Defaults to false. * +config.secret_token+ used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get +config.secret_token+ initialized to a random key in +config/initializers/secret_token.rb+. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index f5f47acfbc..0b8eac8a8b 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -33,6 +33,25 @@ module Rails # # The Application is also responsible for building the middleware stack. # + # == Booting process + # + # The application is also responsible for setting up and executing the booting + # process. From the moment you require "config/application.rb" in your app, + # the booting process goes like this: + # + # 1) require "config/boot.rb" to setup load paths + # 2) require railties and engines + # 3) Define Rails.application as "class MyApp::Application < Rails::Application" + # 4) Run config.before_configuration callbacks + # 5) Load config/environments/ENV.rb + # 6) Run config.before_initialize callbacks + # 7) Run Railtie#initializer defined by railties, engines and application. + # One by one, each engine sets up its load paths, routes, locales and so on. + # 8) Runs all registered config/initializers/*, executing the engines one first + # 9) Build the middleware stack and run to_prepare callbacks + # 10) Run config.before_eager_load and eager_load if cache classes is true + # 11) Run config.after_initialize callbacks + # class Application < Engine autoload :Bootstrap, 'rails/application/bootstrap' autoload :Configuration, 'rails/application/configuration' @@ -52,12 +71,14 @@ module Rails attr_accessor :assets, :sandbox alias_method :sandbox?, :sandbox + attr_reader :reloaders delegate :default_url_options, :default_url_options=, :to => :routes def initialize super @initialized = false + @reloaders = [] end # This method is called just after an application inherits from Rails::Application, @@ -83,27 +104,83 @@ module Rails require environment if environment end + # Reload application routes regardless if they changed or not. def reload_routes! routes_reloader.reload! end - def routes_reloader + def routes_reloader #:nodoc: @routes_reloader ||= RoutesReloader.new end - def initialize!(group=:default) + # A routes reloader hook that is used to setup to_prepare callbacks. + # A plugin may override this if they desire to provide a more + # exquisite route reloading. + # :api: plugin + def set_routes_reloader_hook + reloader = routes_reloader + hook = lambda { reloader.execute_if_updated } + hook.call + self.reloaders << reloader + ActionDispatch::Reloader.to_prepare(&hook) + end + + # An app dependencies hook that is used to setup to_cleanup callbacks. + # A plugin may override this if they desire to provide a more exquisite app reloading. + # :api: plugin + def set_dependencies_hook + callback = lambda do + ActiveSupport::DescendantsTracker.clear + ActiveSupport::Dependencies.clear + end + + if config.reload_classes_only_on_change + reloader = ActiveSupport::FileUpdateChecker.new(watchable_args, true, &callback) + self.reloaders << reloader + # We need to set a to_prepare callback regardless of the reloader result, i.e. + # models should be reloaded if any of the reloaders (i18n, routes) were updated. + ActionDispatch::Reloader.to_prepare(:prepend => true, &callback) + else + ActionDispatch::Reloader.to_cleanup(&callback) + end + end + + # Returns an array of file paths appended with a hash of directories-extensions + # suitable for ActiveSupport::FileUpdateChecker API. + def watchable_args + files = [] + files.concat config.watchable_files + + dirs = {} + dirs.merge! config.watchable_dirs + ActiveSupport::Dependencies.autoload_paths.each do |path| + dirs[path.to_s] = [:rb] + end + + files << dirs + end + + # Initialize the application passing the given group. By default, the + # group is :default but sprockets precompilation passes group equals + # to assets if initialize_on_precompile is false to avoid booting the + # whole app. + def initialize!(group=:default) #:nodoc: raise "Application has been already initialized." if @initialized run_initializers(group, self) @initialized = true self end + # Load the application and its railties tasks and invoke the registered hooks. + # Check <tt>Rails::Railtie.rake_tasks</tt> for more info. def load_tasks(app=self) initialize_tasks super self end + # Load the application console and invoke the registered hooks. + # Check <tt>Rails::Railtie.console</tt> for more info. def load_console(app=self) initialize_console super @@ -129,7 +206,8 @@ module Rails }) end - def ordered_railties + # Returns the ordered railties for this application considering railties_order. + def ordered_railties #:nodoc: @ordered_railties ||= begin order = config.railties_order.map do |railtie| if railtie == :main_app @@ -151,13 +229,13 @@ module Rails end end - def initializers + def initializers #:nodoc: Bootstrap.initializers_for(self) + super + Finisher.initializers_for(self) end - def config + def config #:nodoc: @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) end @@ -165,7 +243,7 @@ module Rails self end - def helpers_paths + def helpers_paths #:nodoc: config.helpers_paths end @@ -173,6 +251,10 @@ module Rails alias :build_middleware_stack :app + def reload_dependencies? + config.reload_classes_only_on_change != true || reloaders.map(&:updated?).any? + end + def default_middleware_stack ActionDispatch::MiddlewareStack.new.tap do |middleware| if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache @@ -202,7 +284,11 @@ module Rails middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header end - middleware.use ::ActionDispatch::Reloader unless config.cache_classes + unless config.cache_classes + app = self + middleware.use ::ActionDispatch::Reloader, lambda { app.reload_dependencies? } + end + middleware.use ::ActionDispatch::Callbacks middleware.use ::ActionDispatch::Cookies @@ -222,7 +308,7 @@ module Rails end end - def initialize_tasks + def initialize_tasks #:nodoc: self.class.rake_tasks do require "rails/tasks" task :environment do @@ -232,7 +318,7 @@ module Rails end end - def initialize_console + def initialize_console #:nodoc: require "pp" require "rails/console/app" require "rails/console/helpers" diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index 1ae90056d9..e189009cc5 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -59,13 +59,6 @@ module Rails end end - initializer :set_clear_dependencies_hook, :group => :all do - ActionDispatch::Reloader.to_cleanup do - ActiveSupport::DescendantsTracker.clear - ActiveSupport::Dependencies.clear - end - end - # Sets the dependency loading mechanism. # TODO: Remove files from the $" and always use require. initializer :initialize_dependency_mechanism, :group => :all do diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index e95b0f5495..39d66ecc31 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -7,11 +7,11 @@ module Rails class Configuration < ::Rails::Engine::Configuration attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets, :cache_classes, :cache_store, :consider_all_requests_local, - :dependency_loading, :filter_parameters, - :force_ssl, :helpers_paths, :logger, :log_tags, :preload_frameworks, - :reload_plugins, :secret_token, :serve_static_assets, - :ssl_options, :static_cache_control, :session_options, - :time_zone, :whiny_nils, :railties_order + :dependency_loading, :filter_parameters, :force_ssl, :helpers_paths, + :initializers_paths, :logger, :log_tags, :preload_frameworks, + :railties_order, :relative_url_root, :reload_plugins, :secret_token, + :serve_static_assets, :ssl_options, :static_cache_control, :session_options, + :time_zone, :reload_classes_only_on_change, :whiny_nils attr_writer :log_level attr_reader :encoding @@ -19,23 +19,26 @@ module Rails def initialize(*) super self.encoding = "utf-8" - @allow_concurrency = false - @consider_all_requests_local = false - @filter_parameters = [] - @helpers_paths = [] - @dependency_loading = true - @serve_static_assets = true - @static_cache_control = nil - @force_ssl = false - @ssl_options = {} - @session_store = :cookie_store - @session_options = {} - @time_zone = "UTC" - @log_level = nil - @middleware = app_middleware - @generators = app_generators - @cache_store = [ :file_store, "#{root}/tmp/cache/" ] - @railties_order = [:all] + @allow_concurrency = false + @consider_all_requests_local = false + @filter_parameters = [] + @helpers_paths = [] + @dependency_loading = true + @serve_static_assets = true + @static_cache_control = nil + @force_ssl = false + @ssl_options = {} + @session_store = :cookie_store + @session_options = {} + @time_zone = "UTC" + @log_level = nil + @middleware = app_middleware + @generators = app_generators + @cache_store = [ :file_store, "#{root}/tmp/cache/" ] + @railties_order = [:all] + @initializers_paths = [] + @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] + @reload_classes_only_on_change = true @assets = ActiveSupport::OrderedOptions.new @assets.enabled = false diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index fc7d205a6f..e000f6ef3a 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -4,6 +4,10 @@ module Rails include Initializable $rails_rake_task = nil + initializer :load_config_initializers do + config.initializers_paths.each { |init| load(init) } + end + initializer :add_generator_templates do config.generators.templates.unshift(*paths["lib/templates"].existent) end @@ -20,12 +24,6 @@ module Rails end end - initializer :add_to_prepare_blocks do - config.to_prepare_blocks.each do |block| - ActionDispatch::Reloader.to_prepare(&block) - end - end - initializer :add_builtin_route do |app| if Rails.env.development? app.routes.append do @@ -38,14 +36,22 @@ module Rails build_middleware_stack end - initializer :run_prepare_callbacks do - ActionDispatch::Reloader.prepare! - end - initializer :define_main_app_helper do |app| app.routes.define_mounted_helper(:main_app) end + initializer :add_to_prepare_blocks do + config.to_prepare_blocks.each do |block| + ActionDispatch::Reloader.to_prepare(&block) + end + end + + # This needs to happen before eager load so it happens + # in exactly the same point regardless of config.cache_classes + initializer :run_prepare_callbacks do + ActionDispatch::Reloader.prepare! + end + initializer :eager_load! do if config.cache_classes && !$rails_rake_task ActiveSupport.run_load_hooks(:before_eager_load, self) @@ -53,17 +59,21 @@ module Rails end end + # All initialization is done, including eager loading in production initializer :finisher_hook do ActiveSupport.run_load_hooks(:after_initialize, self) end - # Force routes to be loaded just at the end and add it to to_prepare callbacks - # This needs to be after the finisher hook to ensure routes added in the hook - # are still loaded. - initializer :set_routes_reloader do |app| - reloader = lambda { app.routes_reloader.execute_if_updated } - reloader.call - ActionDispatch::Reloader.to_prepare(&reloader) + # Set app reload just after the finisher hook to ensure + # routes added in the hook are still loaded. + initializer :set_routes_reloader_hook do |app| + app.set_routes_reloader_hook + end + + # Set app reload just after the finisher hook to ensure + # paths added in the hook are still loaded. + initializer :set_dependencies_hook, :group => :all do |app| + app.set_dependencies_hook end # Disable dependency loading during request cycle diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb index 1d1f5e1b06..460b84dfd9 100644 --- a/railties/lib/rails/application/routes_reloader.rb +++ b/railties/lib/rails/application/routes_reloader.rb @@ -1,10 +1,14 @@ +require "active_support/core_ext/module/delegation" + module Rails class Application - class RoutesReloader < ::ActiveSupport::FileUpdateChecker + class RoutesReloader attr_reader :route_sets - def initialize - super([]) { reload! } + delegate :paths, :execute_if_updated, :updated?, :to => :@updater + + def initialize(updater=ActiveSupport::FileUpdateChecker) + @updater = updater.new([]) { reload! } @route_sets = [] end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 5c1af99fe2..86efc7332c 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -583,10 +583,8 @@ module Rails end end - initializer :load_config_initializers do - config.paths["config/initializers"].existent.sort.each do |initializer| - load(initializer) - end + initializer :append_config_initializers do |app| + app.config.initializers_paths.concat config.paths["config/initializers"].existent.sort end initializer :engines_blank_point do diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index e3d22e81fb..0bd39cb2cd 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -194,7 +194,7 @@ module Rails def assets_gemfile_entry return if options[:skip_sprockets] - <<-GEMFILE.strip_heredoc + <<-GEMFILE.strip_heredoc.gsub(/^[ \t]*$/, '') # Gems used only for assets and not required # in production environments by default. group :assets do diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index f888684117..cf9e4ad500 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -7,6 +7,18 @@ module Rails @@options ||= {} end + # Add files that should be watched for change. + def watchable_files + @@watchable_files ||= [] + end + + # Add directories that should be watched for change. + # The key of the hashes should be directories and the values should + # be an array of extensions to match in each directory. + def watchable_dirs + @@watchable_dirs ||= {} + end + # This allows you to modify the application's middlewares from Engines. # # All operations you run on the app_middleware will be replayed on the diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 392bef3fd1..a08ea77ff3 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -478,6 +478,15 @@ module ApplicationTests assert_match 'src="//example.com/assets/rails.png"', File.read("#{app_path}/public/assets/image_loader.js") end + test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do + ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri" + + app_file "app/assets/javascripts/app.js.erb", 'var src="<%= image_path("rails.png") %>";' + add_to_config "config.assets.precompile = %w{app.js}" + precompile! + + assert_match 'src="/sub/uri/assets/rails.png"', File.read("#{app_path}/public/assets/app.js") + end private diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 2073c780bf..6f9d8d57b1 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -61,7 +61,8 @@ class ConsoleTest < Test::Unit::TestCase load_environment assert User.new.respond_to?(:name) - assert !User.new.respond_to?(:age) + + sleep(1) app_file "app/models/user.rb", <<-MODEL class User diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index 47c6fd5c6e..c4908915dc 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -66,6 +66,7 @@ class LoadingTest < Test::Unit::TestCase def test_descendants_are_cleaned_on_each_request_without_cache_classes add_to_config <<-RUBY config.cache_classes = false + config.reload_classes_only_on_change = false RUBY app_file "app/models/post.rb", <<-MODEL diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb index 050a2161ae..790c5b2d53 100644 --- a/railties/test/application/middleware/cache_test.rb +++ b/railties/test/application/middleware/cache_test.rb @@ -54,9 +54,9 @@ module ApplicationTests def test_cache_keeps_if_modified_since simple_controller expected = "Wed, 30 May 1984 19:43:31 GMT" - + get "/expires/keeps_if_modified_since", {}, "HTTP_IF_MODIFIED_SINCE" => expected - + assert_equal 200, last_response.status assert_equal expected, last_response.body, "cache should have kept If-Modified-Since" end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index ef9f2b22a7..30fbe74e83 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -124,6 +124,16 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "hats/config/environment.rb", /Hats::Application\.initialize!/ end + def test_gemfile_has_no_whitespace_errors + run_generator + absolute = File.expand_path("Gemfile", destination_root) + File.open(absolute, 'r') do |f| + f.each_line do |line| + assert_no_match /^[ \t]+$/, line + end + end + end + def test_config_database_is_added_by_default run_generator assert_file "config/database.yml", /sqlite3/ |