diff options
89 files changed, 628 insertions, 471 deletions
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 32e6183ff6..cfbe2f1cbd 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -35,7 +35,10 @@ require 'active_support/lazy_load_hooks' module ActionMailer extend ::ActiveSupport::Autoload - autoload :Collector + eager_autoload do + autoload :Collector + end + autoload :Base autoload :DeliveryMethods autoload :MailHelper diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index 5c03a29f0f..8679096735 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -5,6 +5,7 @@ require "abstract_controller/railties/routes_helpers" module ActionMailer class Railtie < Rails::Railtie config.action_mailer = ActiveSupport::OrderedOptions.new + config.eager_load_namespaces << ActionMailer initializer "action_mailer.logger" do ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger } diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 95fc79b791..1445cf43f6 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,10 @@ ## Rails 4.0.0 (unreleased) ## +* Fix select_tag when option_tags is nil. + Fixes #7404. + + *Sandeep Ravichandran* + * Add Request#formats=(extensions) that lets you set multiple formats directly in a prioritized order *DHH* Example of using this for custom iphone views with an HTML fallback: @@ -51,8 +56,9 @@ *Richard Schneeman* -* Add 'X-Frame-Options' => 'SAMEORIGIN' and - 'X-XSS-Protection' => '1; mode=block' +* Add 'X-Frame-Options' => 'SAMEORIGIN' + 'X-XSS-Protection' => '1; mode=block' and + 'X-Content-Type-Options' => 'nosniff' as default headers. *Egor Homakov* diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index ceb90f8cee..31df9d605c 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -48,6 +48,12 @@ module ActionController eager_autoload do autoload :RecordIdentifier end + + def self.eager_load! + super + ActionController::Caching.eager_load! + HTML.eager_load! + end end # All of these simply register additional autoloads diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 851a2c4aee..3ecc105e22 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -9,6 +9,8 @@ module ActionController class Railtie < Rails::Railtie #:nodoc: config.action_controller = ActiveSupport::OrderedOptions.new + config.eager_load_namespaces << ActionController + initializer "action_controller.assets_config", :group => :all do |app| app.config.action_controller.assets_dir ||= app.config.paths["public"].first end diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index b382997052..57b4678add 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -38,9 +38,11 @@ module ActionDispatch class IllegalStateError < StandardError end - autoload_under 'http' do - autoload :Request - autoload :Response + eager_autoload do + autoload_under 'http' do + autoload :Request + autoload :Response + end end autoload_under 'middleware' do diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 0dcf1fc4fe..ccc0435a39 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -21,9 +21,12 @@ module ActionDispatch config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', - 'X-XSS-Protection' => '1; mode=block' + 'X-XSS-Protection' => '1; mode=block', + 'X-Content-Type-Options' => 'nosniff' } + config.eager_load_namespaces << ActionDispatch + initializer "action_dispatch.configure" do |app| ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index ea5028a7c0..f64cff8394 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -444,9 +444,10 @@ module ActionDispatch raise "A rack application must be specified" unless path - options[:as] ||= app_name(app) + options[:as] ||= app_name(app) + options[:via] ||= :all - match(path, options.merge(:to => app, :anchor => false, :format => false, :via => :all)) + match(path, options.merge(:to => app, :anchor => false, :format => false)) define_generate_prefix(app, options[:as]) self diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 4bd72c5520..9d11c284f5 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -38,7 +38,6 @@ module ActionView autoload :PathSet autoload :Template - autoload_under "renderer" do autoload :Renderer autoload :AbstractRenderer @@ -77,6 +76,11 @@ module ActionView autoload :TestCase ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' + + def self.eager_load! + super + ActionView::Template.eager_load! + end end require 'active_support/core_ext/string/output_safety' diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 68b0195700..ceb56824fa 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -209,18 +209,18 @@ module ActionView # * <tt>:title</tt> - Specify the title of the link, defaults to the +type+ # # ==== Examples - # auto_discovery_link_tag # => - # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" /> - # auto_discovery_link_tag(:atom) # => - # <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" /> - # auto_discovery_link_tag(:rss, {:action => "feed"}) # => - # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" /> - # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # => - # <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" /> - # auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"}) # => - # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" /> - # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) # => - # <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" /> + # auto_discovery_link_tag + # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" /> + # auto_discovery_link_tag(:atom) + # # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" /> + # auto_discovery_link_tag(:rss, {:action => "feed"}) + # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" /> + # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) + # # => <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" /> + # auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"}) + # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" /> + # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) + # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" /> def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) tag( "link", @@ -360,18 +360,18 @@ module ActionView # width="30" and height="45". <tt>:size</tt> will be ignored if the # value is not in the correct format. # - # image_tag("icon") # => - # <img src="/assets/icon" alt="Icon" /> - # image_tag("icon.png") # => - # <img src="/assets/icon.png" alt="Icon" /> - # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # => - # <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" /> - # image_tag("/icons/icon.gif", :size => "16x16") # => - # <img src="/icons/icon.gif" width="16" height="16" alt="Icon" /> - # image_tag("/icons/icon.gif", :height => '32', :width => '32') # => - # <img alt="Icon" height="32" src="/icons/icon.gif" width="32" /> - # image_tag("/icons/icon.gif", :class => "menu_icon") # => - # <img alt="Icon" class="menu_icon" src="/icons/icon.gif" /> + # image_tag("icon") + # # => <img src="/assets/icon" alt="Icon" /> + # image_tag("icon.png") + # # => <img src="/assets/icon.png" alt="Icon" /> + # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") + # # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" /> + # image_tag("/icons/icon.gif", :size => "16x16") + # # => <img src="/icons/icon.gif" width="16" height="16" alt="Icon" /> + # image_tag("/icons/icon.gif", :height => '32', :width => '32') + # # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" /> + # image_tag("/icons/icon.gif", :class => "menu_icon") + # # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" /> def image_tag(source, options={}) options = options.symbolize_keys @@ -408,24 +408,24 @@ module ActionView # width="30" and height="45". <tt>:size</tt> will be ignored if the # value is not in the correct format. # - # video_tag("trailer") # => - # <video src="/videos/trailer" /> - # video_tag("trailer.ogg") # => - # <video src="/videos/trailer.ogg" /> - # video_tag("trailer.ogg", :controls => true, :autobuffer => true) # => - # <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" /> - # video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") # => - # <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" /> - # video_tag("/trailers/hd.avi", :size => "16x16") # => - # <video src="/trailers/hd.avi" width="16" height="16" /> - # video_tag("/trailers/hd.avi", :height => '32', :width => '32') # => - # <video height="32" src="/trailers/hd.avi" width="32" /> - # video_tag("trailer.ogg", "trailer.flv") # => - # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> - # video_tag(["trailer.ogg", "trailer.flv"]) # => - # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> - # video_tag(["trailer.ogg", "trailer.flv"], :size => "160x120") # => - # <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> + # video_tag("trailer") + # # => <video src="/videos/trailer" /> + # video_tag("trailer.ogg") + # # => <video src="/videos/trailer.ogg" /> + # video_tag("trailer.ogg", :controls => true, :autobuffer => true) + # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" /> + # video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") + # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" /> + # video_tag("/trailers/hd.avi", :size => "16x16") + # # => <video src="/trailers/hd.avi" width="16" height="16" /> + # video_tag("/trailers/hd.avi", :height => '32', :width => '32') + # # => <video height="32" src="/trailers/hd.avi" width="32" /> + # video_tag("trailer.ogg", "trailer.flv") + # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> + # video_tag(["trailer.ogg", "trailer.flv"]) + # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> + # video_tag(["trailer.ogg", "trailer.flv"], :size => "160x120") + # # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> def video_tag(*sources) multiple_sources_tag('video', sources) do |options| options[:poster] = path_to_image(options[:poster]) if options[:poster] diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 5dc5bb8a98..b79577bcd3 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -720,15 +720,15 @@ module ActionView # label(:post, :title) # # => <label for="post_title">Title</label> # - # You can localize your labels based on model and attribute names. - # For example you can define the following in your locale (e.g. en.yml) + # You can localize your labels based on model and attribute names. + # For example you can define the following in your locale (e.g. en.yml) # # helpers: # label: # post: # body: "Write your entire text here" # - # Which then will result in + # Which then will result in # # label(:post, :body) # # => <label for="post_body">Write your entire text here</label> diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index e4f4ebc7ff..2bb526a539 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -501,14 +501,14 @@ module ActionView # # Possible output: # <optgroup label="---------"> + # <option value="US">United States</option> + # <option value="Canada">Canada</option> + # </optgroup> + # <optgroup label="---------"> # <option value="Denmark">Denmark</option> # <option value="Germany">Germany</option> # <option value="France">France</option> # </optgroup> - # <optgroup label="---------"> - # <option value="US">United States</option> - # <option value="Canada">Canada</option> - # </optgroup> # # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to # wrap the output in an appropriate <tt><select></tt> tag. diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index a9b91d1db3..ace457df2e 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -118,6 +118,7 @@ module ActionView # # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option> # # <option>Paris</option><option>Rome</option></select> def select_tag(name, option_tags = nil, options = {}) + option_tags ||= "" html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name if options.delete(:include_blank) diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index 9f5e3be454..2d36deaa78 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -9,6 +9,8 @@ module ActionView config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) } config.action_view.embed_authenticity_token_in_remote_forms = false + config.eager_load_namespaces << ActionView + initializer "action_view.embed_authenticity_token_in_remote_forms" do |app| ActiveSupport.on_load(:action_view) do ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 6cc1370105..57ab325683 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -197,7 +197,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end def test_regexp_precidence - @rs.draw do + rs.draw do get '/whois/:domain', :constraints => { :domain => /\w+\.[\w\.]+/ }, :to => lambda { |env| [200, {}, %w{regexp}] } @@ -216,7 +216,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end } - @rs.draw do + rs.draw do get '/', :constraints => subdomain.new, :to => lambda { |env| [200, {}, %w{default}] } get '/', :constraints => { :subdomain => 'clients' }, @@ -228,7 +228,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end def test_lambda_constraints - @rs.draw do + rs.draw do get '/', :constraints => lambda { |req| req.subdomain.present? and req.subdomain != "clients" }, :to => lambda { |env| [200, {}, %w{default}] } @@ -266,22 +266,22 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_draw_with_block_arity_one_raises assert_raise(RuntimeError) do - @rs.draw { |map| map.match '/:controller(/:action(/:id))' } + rs.draw { |map| map.match '/:controller(/:action(/:id))' } end end def test_specific_controller_action_failure - @rs.draw do + rs.draw do mount lambda {} => "/foo" end assert_raises(ActionController::RoutingError) do - url_for(@rs, :controller => "omg", :action => "lol") + url_for(rs, :controller => "omg", :action => "lol") end end def test_default_setup - @rs.draw { get '/:controller(/:action(/:id))' } + rs.draw { get '/:controller(/:action(/:id))' } assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content")) assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list")) assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10")) @@ -298,8 +298,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end def test_ignores_leading_slash - @rs.clear! - @rs.draw { get '/:controller(/:action(/:id))'} + rs.clear! + rs.draw { get '/:controller(/:action(/:id))'} test_default_setup end @@ -470,7 +470,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end def test_changing_controller - @rs.draw { get ':controller/:action/:id' } + rs.draw { get ':controller/:action/:id' } assert_equal '/admin/stuff/show/10', url_for(rs, {:controller => 'stuff', :action => 'show', :id => 10}, @@ -583,7 +583,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end def test_action_expiry - @rs.draw { get ':controller(/:action(/:id))' } + rs.draw { get ':controller(/:action(/:id))' } assert_equal '/content', url_for(rs, { :controller => 'content' }, { :controller => 'content', :action => 'show' }) end diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb index 536e35ab2e..3b008fdff0 100644 --- a/actionpack/test/dispatch/mount_test.rb +++ b/actionpack/test/dispatch/mount_test.rb @@ -22,6 +22,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest mount SprocketsApp => "/shorthand" mount FakeEngine, :at => "/fakeengine" + mount FakeEngine, :at => "/getfake", :via => :get scope "/its_a" do mount SprocketsApp, :at => "/sprocket" @@ -52,6 +53,14 @@ class TestRoutingMount < ActionDispatch::IntegrationTest assert_equal "/shorthand -- /omg", response.body end + def test_mounting_works_with_via + get "/getfake" + assert_equal "OK", response.body + + post "/getfake" + assert_response :not_found + end + def test_with_fake_engine_does_not_call_invalid_method get "/fakeengine" assert_equal "OK", response.body diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 71609d7340..4d699bd739 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -177,9 +177,10 @@ class ResponseTest < ActiveSupport::TestCase end end - test "read x_frame_options and x_xss_protection" do + test "read x_frame_options, x_content_type_options and x_xss_protection" do ActionDispatch::Response.default_headers = { 'X-Frame-Options' => 'DENY', + 'X-Content-Type-Options' => 'nosniff', 'X-XSS-Protection' => '1;' } resp = ActionDispatch::Response.new.tap { |response| @@ -188,6 +189,7 @@ class ResponseTest < ActiveSupport::TestCase resp.to_a assert_equal('DENY', resp.headers['X-Frame-Options']) + assert_equal('nosniff', resp.headers['X-Content-Type-Options']) assert_equal('1;', resp.headers['X-XSS-Protection']) end diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 81ba92f2e6..3c66a29754 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -225,6 +225,18 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_select_tag_with_nil_option_tags_and_include_blank + actual = select_tag "places", nil, :include_blank => true + expected = %(<select id="places" name="places"><option value=""></option></select>) + assert_dom_equal expected, actual + end + + def test_select_tag_with_nil_option_tags_and_prompt + actual = select_tag "places", nil, :prompt => "string" + expected = %(<select id="places" name="places"><option value="">string</option></select>) + assert_dom_equal expected, actual + end + def test_text_area_tag_size_string actual = text_area_tag "body", "hello world", "size" => "20x40" expected = %(<textarea cols="20" id="body" name="body" rows="40">\nhello world</textarea>) diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index ec2d734647..d1cc19ec6b 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -34,7 +34,6 @@ module ActiveModel autoload :Conversion autoload :Dirty autoload :EachValidator, 'active_model/validator' - autoload :Errors autoload :Lint autoload :MassAssignmentSecurity autoload :Model @@ -49,11 +48,22 @@ module ActiveModel autoload :Validations autoload :Validator + eager_autoload do + autoload :Errors + end + module Serializers extend ActiveSupport::Autoload - autoload :JSON - autoload :Xml + eager_autoload do + autoload :JSON + autoload :Xml + end + end + + def eager_load! + super + ActiveModel::Serializer.eager_load! end end diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb index 63ffe5db63..f239758b35 100644 --- a/activemodel/lib/active_model/railtie.rb +++ b/activemodel/lib/active_model/railtie.rb @@ -1,2 +1,8 @@ require "active_model" -require "rails"
\ No newline at end of file +require "rails" + +module ActiveModel + class Railtie < Rails::Railtie + config.eager_load_namespaces << ActiveModel + end +end
\ No newline at end of file diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 597a1aa974..c5ef39b9d2 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,24 @@ ## Rails 4.0.0 (unreleased) ## +* Fix `reset_counters` when there are multiple `belongs_to` association with the + same foreign key and one of them have a counter cache. + Fixes #5200. + + *Dave Desrochers* + +* `serialized_attributes` and `_attr_readonly` become class method only. Instance reader methods are deprecated. + + *kennyj* + +* Round usec when comparing timestamp attributes in the dirty tracking. + Fixes #6975. + + *kennyj* + +* Use inversed parent for first and last child of has_many association. + + *Ravil Bayramgalin* + * Fix Column.microseconds and Column.fast_string_to_date to avoid converting timestamp seconds to a float, since it occasionally results in inaccuracies with microsecond-precision times. Fixes #7352. diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 39427bc296..fa94f6a941 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -160,6 +160,15 @@ module ActiveRecord autoload :TestCase autoload :TestFixtures, 'active_record/fixtures' + + def self.eager_load! + super + ActiveRecord::Locking.eager_load! + ActiveRecord::Scoping.eager_load! + ActiveRecord::Associations.eager_load! + ActiveRecord::AttributeMethods.eager_load! + ActiveRecord::ConnectionAdapters.eager_load! + end end ActiveSupport.on_load(:active_record) do diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index a84eda1d3b..b15df4f308 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -574,7 +574,7 @@ module ActiveRecord args.shift if args.first.is_a?(Hash) && args.first.empty? collection = fetch_first_or_last_using_find?(args) ? scope : load_target - collection.send(type, *args) + collection.send(type, *args).tap {|it| set_inverse_instance it } end end end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 33d7cc7f34..bdda5bc009 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -6,7 +6,7 @@ module ActiveRecord included do # Returns a hash of all the attributes that have been specified for serialization as # keys and their class restriction as values. - class_attribute :serialized_attributes, instance_writer: false + class_attribute :serialized_attributes, instance_accessor: false self.serialized_attributes = {} end @@ -41,6 +41,11 @@ module ActiveRecord end end + def serialized_attributes + ActiveSupport::Deprecation.warn("Instance level serialized_attributes method is deprecated, please use class level method.") + defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes + end + class Type # :nodoc: def initialize(column) @column = column @@ -114,7 +119,7 @@ module ActiveRecord end def read_attribute_before_type_cast(attr_name) - if serialized_attributes.include?(attr_name) + if self.class.serialized_attributes.include?(attr_name) super.unserialized_value else super diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index fa5b2ef336..d1e9d2de0e 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -59,11 +59,14 @@ module ActiveRecord unless time.acts_like?(:time) time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time end - time = time.in_time_zone rescue nil if time - changed = read_attribute(:#{attr_name}) != time - write_attribute(:#{attr_name}, original_time) - #{attr_name}_will_change! if changed - @attributes_cache["#{attr_name}"] = time + zoned_time = time && time.in_time_zone rescue nil + rounded_time = round_usec(zoned_time) + rounded_value = round_usec(read_attribute("#{attr_name}")) + if (rounded_value != rounded_time) || (!rounded_value && original_time) + write_attribute("#{attr_name}", original_time) + #{attr_name}_will_change! + @attributes_cache["#{attr_name}"] = zoned_time + end end EOV generated_attribute_methods.module_eval(method_body, __FILE__, line) @@ -79,6 +82,12 @@ module ActiveRecord [:datetime, :timestamp].include?(column.type) end end + + private + def round_usec(value) + return unless value + value.change(:usec => 0) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index b0b51f540c..02459763f7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -1,6 +1,12 @@ module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseStatements + def initialize + super + @_current_transaction_records = [] + @transaction_joinable = nil + end + # Converts an arel AST to SQL def to_sql(arel, binds = []) if arel.respond_to?(:ast) @@ -167,7 +173,7 @@ module ActiveRecord def transaction(options = {}) options.assert_valid_keys :requires_new, :joinable - last_transaction_joinable = defined?(@transaction_joinable) ? @transaction_joinable : nil + last_transaction_joinable = @transaction_joinable if options.has_key?(:joinable) @transaction_joinable = options[:joinable] else @@ -176,22 +182,19 @@ module ActiveRecord requires_new = options[:requires_new] || !last_transaction_joinable transaction_open = false - @_current_transaction_records ||= [] begin - if block_given? - if requires_new || open_transactions == 0 - if open_transactions == 0 - begin_db_transaction - elsif requires_new - create_savepoint - end - increment_open_transactions - transaction_open = true - @_current_transaction_records.push([]) + if requires_new || open_transactions == 0 + if open_transactions == 0 + begin_db_transaction + elsif requires_new + create_savepoint end - yield + increment_open_transactions + transaction_open = true + @_current_transaction_records.push([]) end + yield rescue Exception => database_transaction_rollback if transaction_open && !outside_transaction? transaction_open = false @@ -225,7 +228,7 @@ module ActiveRecord @_current_transaction_records.last.concat(save_point_records) end end - rescue Exception => database_transaction_rollback + rescue Exception if open_transactions == 0 rollback_db_transaction rollback_transaction_records(true) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 3b0353358a..6bf7af081f 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -298,13 +298,6 @@ module ActiveRecord @connection.insert_id end - class Result < ActiveRecord::Result - def initialize(columns, rows, column_types) - super(columns, rows) - @column_types = column_types - end - end - module Fields class Type def type; end @@ -437,7 +430,7 @@ module ActiveRecord } end } - result_set = Result.new(types.keys, result.to_a, types) + result_set = ActiveRecord::Result.new(types.keys, result.to_a, types) result.free else result_set = ActiveRecord::Result.new([], []) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 8e9ce80697..40cd65cce9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -803,13 +803,6 @@ module ActiveRecord Arel::Nodes::BindParam.new "$#{index + 1}" end - class Result < ActiveRecord::Result - def initialize(columns, rows, column_types) - super(columns, rows) - @column_types = column_types - end - end - def exec_query(sql, name = 'SQL', binds = []) log(sql, name, binds) do result = binds.empty? ? exec_no_cache(sql, binds) : @@ -825,7 +818,7 @@ module ActiveRecord } end - ret = Result.new(result.fields, result.values, types) + ret = ActiveRecord::Result.new(result.fields, result.values, types) result.clear return ret end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index cde3325919..aad21b8e37 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -393,6 +393,7 @@ module ActiveRecord @marked_for_destruction = false @new_record = true @mass_assignment_options = nil + @_start_transaction_state = {} end end end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index b27a19f89a..c877079b25 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -25,7 +25,7 @@ module ActiveRecord foreign_key = has_many_association.foreign_key.to_s child_class = has_many_association.klass belongs_to = child_class.reflect_on_all_associations(:belongs_to) - reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key } + reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? } counter_name = reflection.counter_cache_column stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({ diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index ff7c996648..99de16cd33 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -225,7 +225,7 @@ module ActiveRecord def decorate_columns(columns_hash) # :nodoc: return if columns_hash.empty? - serialized_attributes.keys.each do |key| + serialized_attributes.each_key do |key| columns_hash[key] = AttributeMethods::Serialization::Type.new(columns_hash[key]) end @@ -259,13 +259,12 @@ module ActiveRecord # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute # is available. def column_methods_hash #:nodoc: - @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr| + @dynamic_methods_hash ||= column_names.each_with_object(Hash.new(false)) do |attr, methods| attr_name = attr.to_s methods[attr.to_sym] = attr_name methods["#{attr}=".to_sym] = attr_name methods["#{attr}?".to_sym] = attr_name methods["#{attr}_before_type_cast".to_sym] = attr_name - methods end end @@ -324,8 +323,7 @@ module ActiveRecord # Guesses the table name, but does not decorate it with prefix and suffix information. def undecorated_table_name(class_name = base_class.name) table_name = class_name.to_s.demodulize.underscore - table_name = table_name.pluralize if pluralize_table_names - table_name + pluralize_table_names ? table_name.pluralize : table_name end # Computes and returns a table name according to default conventions. diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 593fed5a85..6b4b9bd103 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -218,7 +218,7 @@ module ActiveRecord raise ActiveRecordError, "can not update on a new record object" unless persisted? attributes.each_key do |key| - raise ActiveRecordError, "#{key.to_s} is marked as readonly" if self.class.readonly_attributes.include?(key.to_s) + raise ActiveRecordError, "#{key} is marked as readonly" if self.class.readonly_attributes.include?(key.to_s) end attributes.each do |k,v| @@ -391,9 +391,5 @@ module ActiveRecord @new_record = false id end - - def verify_readonly_attribute(name) - raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name) - end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 672d9a4246..ecf8547e67 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -29,8 +29,11 @@ module ActiveRecord 'ActiveRecord::RecordNotSaved' => :unprocessable_entity ) + config.active_record.use_schema_cache_dump = true + config.eager_load_namespaces << ActiveRecord + rake_tasks do require "active_record/base" load "active_record/railties/databases.rake" diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb index 1d8c566e40..b3c20c4aff 100644 --- a/activerecord/lib/active_record/readonly_attributes.rb +++ b/activerecord/lib/active_record/readonly_attributes.rb @@ -4,7 +4,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :_attr_readonly, instance_writer: false + class_attribute :_attr_readonly, instance_accessor: false self._attr_readonly = [] end @@ -20,5 +20,10 @@ module ActiveRecord self._attr_readonly end end + + def _attr_readonly + ActiveSupport::Deprecation.warn("Instance level _attr_readonly method is deprecated, please use class level method.") + defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly + end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 1abbc58314..2d0457636e 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -18,6 +18,7 @@ module ActiveRecord attr_reader :table, :klass, :loaded attr_accessor :default_scoped + alias :model :klass alias :loaded? :loaded alias :default_scoped? :default_scoped diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 7531f22494..e5b50673da 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -97,18 +97,13 @@ module ActiveRecord merged_wheres = relation.where_values + values[:where] unless relation.where_values.empty? - # Remove duplicate ARel attributes. Last one wins. - seen = Hash.new { |h,table| h[table] = {} } + # Remove equalities with duplicated left-hand. Last one wins. + seen = {} merged_wheres = merged_wheres.reverse.reject { |w| nuke = false - # We might have non-attributes on the left side of equality nodes, - # so we need to make sure they quack like an attribute. - if w.respond_to?(:operator) && w.operator == :== && - w.left.respond_to?(:relation) - name = w.left.name - table = w.left.relation.name - nuke = seen[table][name] - seen[table][name] = true + if w.respond_to?(:operator) && w.operator == :== + nuke = seen[w.left] + seen[w.left] = true end nuke }.reverse diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index fd276ccf5d..2414a4bbd7 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -10,11 +10,11 @@ module ActiveRecord attr_reader :columns, :rows, :column_types - def initialize(columns, rows) + def initialize(columns, rows, column_types = {}) @columns = columns @rows = rows @hash_rows = nil - @column_types = {} + @column_types = column_types end def each diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 5151f349b7..b4013ecc1e 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -41,7 +41,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :stored_attributes, instance_writer: false + class_attribute :stored_attributes, instance_accessor: false self.stored_attributes = {} end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 5c3399e2aa..9cec791faf 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -293,12 +293,12 @@ module ActiveRecord begin status = yield rescue ActiveRecord::Rollback - if defined?(@_start_transaction_state) + if defined?(@_start_transaction_state) @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 end status = nil end - + raise ActiveRecord::Rollback unless status end status @@ -308,7 +308,6 @@ module ActiveRecord # Save the new record state and id of a record so it can be restored later if a transaction fails. def remember_transaction_record_state #:nodoc: - @_start_transaction_state ||= {} @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key) @_start_transaction_state[:new_record] = @new_record @_start_transaction_state[:destroyed] = @destroyed @@ -317,18 +316,16 @@ module ActiveRecord # Clear the new record state and id of a record. def clear_transaction_record_state #:nodoc: - if defined?(@_start_transaction_state) - @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 - remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1 - end + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 + @_start_transaction_state.clear if @_start_transaction_state[:level] < 1 end # Restore the new record state and id of a record that was previously saved by a call to save_record_state. def restore_transaction_record_state(force = false) #:nodoc: - if defined?(@_start_transaction_state) + unless @_start_transaction_state.empty? @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 if @_start_transaction_state[:level] < 1 || force - restore_state = remove_instance_variable(:@_start_transaction_state) + restore_state = @_start_transaction_state was_frozen = @attributes.frozen? @attributes = @attributes.dup if was_frozen @new_record = restore_state[:new_record] @@ -340,13 +337,14 @@ module ActiveRecord @attributes_cache.delete(self.class.primary_key) end @attributes.freeze if was_frozen + @_start_transaction_state.clear end end end # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed. def transaction_record_state(state) #:nodoc: - @_start_transaction_state[state] if defined?(@_start_transaction_state) + @_start_transaction_state[state] end # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index c3f82bc63d..4bccd2cc59 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -128,11 +128,12 @@ class MysqlConnectionTest < ActiveRecord::TestCase assert_equal [["STRICT_ALL_TABLES"]], result.rows end - def test_mysql_strict_mode_disabled + def test_mysql_strict_mode_disabled_dont_override_global_sql_mode run_without_connection do |orig_connection| ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false})) - result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal [['']], result.rows + global_sql_mode = ActiveRecord::Model.connection.exec_query "SELECT @@GLOBAL.sql_mode" + session_sql_mode = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode" + assert_equal global_sql_mode.rows, session_sql_mode.rows end end diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 276c499276..c63e4fe5b6 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -44,11 +44,12 @@ class MysqlConnectionTest < ActiveRecord::TestCase assert_equal [["STRICT_ALL_TABLES"]], result.rows end - def test_mysql_strict_mode_disabled + def test_mysql_strict_mode_disabled_dont_override_global_sql_mode run_without_connection do |orig_connection| ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false})) - result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal [['']], result.rows + global_sql_mode = ActiveRecord::Model.connection.exec_query "SELECT @@GLOBAL.sql_mode" + session_sql_mode = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode" + assert_equal global_sql_mode.rows, session_sql_mode.rows end end diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 8cb8a5a861..aad48e7ce9 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -259,6 +259,12 @@ class InverseHasManyTests < ActiveRecord::TestCase assert_equal m.name, i.man.name, "Name of man should be the same after changes to replaced-child-owned instance" end + def test_parent_instance_should_be_shared_with_first_and_last_child + man = Man.first + assert man.interests.first.man.equal? man + assert man.interests.last.man.equal? man + end + def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests } end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 04b1d75e3e..63981a68a9 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -604,6 +604,12 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "changed", post.body end + def test_attr_readonly_is_class_level_setting + post = ReadonlyTitlePost.new + assert_raise(NoMethodError) { post._attr_readonly = [:title] } + assert_deprecated { post._attr_readonly } + end + def test_non_valid_identifier_column_name weird = Weird.create('a$b' => 'value') weird.reload diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index cd3d19e783..ee443741ca 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -8,9 +8,11 @@ require 'models/category' require 'models/categorization' require 'models/dog' require 'models/dog_lover' +require 'models/person' +require 'models/friendship' class CounterCacheTest < ActiveRecord::TestCase - fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers + fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships class ::SpecialTopic < ::Topic has_many :special_replies, :foreign_key => 'parent_id' @@ -109,4 +111,11 @@ class CounterCacheTest < ActiveRecord::TestCase Topic.update_counters([t1.id, t2.id], :replies_count => 2) end end + + test "reset the right counter if two have the same foreign key" do + michael = people(:michael) + assert_nothing_raised(ActiveRecord::StatementInvalid) do + Person.reset_counters(michael.id, :followers) + end + end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 248f4efe3e..92677b9926 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -525,6 +525,21 @@ class DirtyTest < ActiveRecord::TestCase end end + def test_setting_time_attributes_with_time_zone_field_to_same_time_should_not_be_marked_as_a_change + in_time_zone 'Paris' do + target = Class.new(ActiveRecord::Base) + target.table_name = 'pirates' + + created_on = Time.now + + pirate = target.create(:created_on => created_on) + pirate.reload # Here mysql truncate the usec value to 0 + + pirate.created_on = created_on + assert !pirate.created_on_changed? + end + end + private def with_partial_updates(klass, on = true) old = klass.partial_updates? diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index afb0bd6fd9..2392516395 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -3,6 +3,7 @@ require "cases/helper" require 'models/person' require 'models/job' require 'models/reader' +require 'models/ship' require 'models/legacy_thing' require 'models/reference' require 'models/string_key_object' @@ -18,8 +19,8 @@ class LockWithCustomColumnWithoutDefault < ActiveRecord::Base self.locking_column = :custom_lock_version end -class ReadonlyFirstNamePerson < Person - attr_readonly :first_name +class ReadonlyNameShip < Ship + attr_readonly :name end class OptimisticLockingTest < ActiveRecord::TestCase @@ -200,15 +201,15 @@ class OptimisticLockingTest < ActiveRecord::TestCase end def test_readonly_attributes - assert_equal Set.new([ 'first_name' ]), ReadonlyFirstNamePerson.readonly_attributes + assert_equal Set.new([ 'name' ]), ReadonlyNameShip.readonly_attributes - p = ReadonlyFirstNamePerson.create(:first_name => "unchangeable name") - p.reload - assert_equal "unchangeable name", p.first_name + s = ReadonlyNameShip.create(:name => "unchangeable name") + s.reload + assert_equal "unchangeable name", s.name - p.update_attributes(:first_name => "changed name") - p.reload - assert_equal "unchangeable name", p.first_name + s.update_attributes(:name => "changed name") + s.reload + assert_equal "unchangeable name", s.name end def test_quote_table_name diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 5fb54b1ca1..6399111be6 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -19,6 +19,11 @@ module ActiveRecord assert !relation.loaded, 'relation is not loaded' end + def test_responds_to_model_and_returns_klass + relation = Relation.new :a, :b + assert_equal :a, relation.model + end + def test_initialize_single_values relation = Relation.new :a, :b (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method| diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 4ef11590a9..684538940a 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -675,11 +675,14 @@ class RelationTest < ActiveRecord::TestCase assert_equal [developers(:poor_jamis)], devs.to_a end - def test_relation_merging_with_arel_equalities_with_a_non_attribute_left_hand_ignores_non_attributes_when_discarding_equalities + def test_relation_merging_with_arel_equalities_keeps_last_equality_with_non_attribute_left_hand salary_attr = Developer.arel_table[:salary] - devs = Developer.where(salary_attr.eq(80000)).merge( - Developer.where(salary_attr.eq(9000)). - where(Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(9000)) + devs = Developer.where( + Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(80000) + ).merge( + Developer.where( + Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(9000) + ) ) assert_equal [developers(:poor_jamis)], devs.to_a end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index ce167509c1..10d8ccc711 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -53,9 +53,8 @@ class SerializationTest < ActiveRecord::TestCase end def test_serialized_attributes_are_class_level_settings - assert_raise NoMethodError do - topic = Topic.new - topic.serialized_attributes = [] - end + topic = Topic.new + assert_raise(NoMethodError) { topic.serialized_attributes = [] } + assert_deprecated { topic.serialized_attributes } end end diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 3e60b62fd5..fb0d116c08 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -122,9 +122,8 @@ class StoreTest < ActiveRecord::TestCase end test "stores_attributes are class level settings" do - assert_raise NoMethodError do - @john.stored_attributes = {} - end + assert_raise(NoMethodError) { @john.stored_attributes = Hash.new } + assert_raise(NoMethodError) { @john.stored_attributes } end end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index d5597a68ad..0d0de455b3 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -91,18 +91,14 @@ class TransactionTest < ActiveRecord::TestCase end def test_raising_exception_in_callback_rollbacks_in_save - add_exception_raising_after_save_callback_to_topic - - begin - @first.approved = true - @first.save - flunk - rescue => e - assert_equal "Make the transaction rollback", e.message - assert !Topic.find(1).approved? - ensure - remove_exception_raising_after_save_callback_to_topic + def @first.after_save_for_transaction + raise 'Make the transaction rollback' end + + @first.approved = true + e = assert_raises(RuntimeError) { @first.save } + assert_equal "Make the transaction rollback", e.message + assert !Topic.find(1).approved? end def test_update_attributes_should_rollback_on_failure @@ -125,100 +121,83 @@ class TransactionTest < ActiveRecord::TestCase end def test_cancellation_from_before_destroy_rollbacks_in_destroy - add_cancelling_before_destroy_with_db_side_effect_to_topic - begin - nbooks_before_destroy = Book.count - status = @first.destroy - assert !status - assert_nothing_raised(ActiveRecord::RecordNotFound) { @first.reload } - assert_equal nbooks_before_destroy, Book.count - ensure - remove_cancelling_before_destroy_with_db_side_effect_to_topic - end + add_cancelling_before_destroy_with_db_side_effect_to_topic @first + nbooks_before_destroy = Book.count + status = @first.destroy + assert !status + @first.reload + assert_equal nbooks_before_destroy, Book.count end - def test_cancellation_from_before_filters_rollbacks_in_save - %w(validation save).each do |filter| - send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") - begin - nbooks_before_save = Book.count - original_author_name = @first.author_name - @first.author_name += '_this_should_not_end_up_in_the_db' - status = @first.save - assert !status - assert_equal original_author_name, @first.reload.author_name - assert_equal nbooks_before_save, Book.count - ensure - send("remove_cancelling_before_#{filter}_with_db_side_effect_to_topic") - end + %w(validation save).each do |filter| + define_method("test_cancellation_from_before_filters_rollbacks_in_#{filter}") do + send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic", @first) + nbooks_before_save = Book.count + original_author_name = @first.author_name + @first.author_name += '_this_should_not_end_up_in_the_db' + status = @first.save + assert !status + assert_equal original_author_name, @first.reload.author_name + assert_equal nbooks_before_save, Book.count end - end - def test_cancellation_from_before_filters_rollbacks_in_save! - %w(validation save).each do |filter| - send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") + define_method("test_cancellation_from_before_filters_rollbacks_in_#{filter}!") do + send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic", @first) + nbooks_before_save = Book.count + original_author_name = @first.author_name + @first.author_name += '_this_should_not_end_up_in_the_db' + begin - nbooks_before_save = Book.count - original_author_name = @first.author_name - @first.author_name += '_this_should_not_end_up_in_the_db' @first.save! - flunk - rescue - assert_equal original_author_name, @first.reload.author_name - assert_equal nbooks_before_save, Book.count - ensure - send("remove_cancelling_before_#{filter}_with_db_side_effect_to_topic") + rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved end + + assert_equal original_author_name, @first.reload.author_name + assert_equal nbooks_before_save, Book.count end end def test_callback_rollback_in_create - new_topic = Topic.new( - :title => "A new topic", - :author_name => "Ben", - :author_email_address => "ben@example.com", - :written_on => "2003-07-16t15:28:11.2233+01:00", - :last_read => "2004-04-15", - :bonus_time => "2005-01-30t15:28:00.00+01:00", - :content => "Have a nice day", - :approved => false) + topic = Class.new(Topic) { + def after_create_for_transaction + raise 'Make the transaction rollback' + end + } + + new_topic = topic.new(:title => "A new topic", + :author_name => "Ben", + :author_email_address => "ben@example.com", + :written_on => "2003-07-16t15:28:11.2233+01:00", + :last_read => "2004-04-15", + :bonus_time => "2005-01-30t15:28:00.00+01:00", + :content => "Have a nice day", + :approved => false) + new_record_snapshot = !new_topic.persisted? id_present = new_topic.has_attribute?(Topic.primary_key) id_snapshot = new_topic.id # Make sure the second save gets the after_create callback called. 2.times do - begin - add_exception_raising_after_create_callback_to_topic - new_topic.approved = true - new_topic.save - flunk - rescue => e - assert_equal "Make the transaction rollback", e.message - assert_equal new_record_snapshot, !new_topic.persisted?, "The topic should have its old persisted value" - assert_equal id_snapshot, new_topic.id, "The topic should have its old id" - assert_equal id_present, new_topic.has_attribute?(Topic.primary_key) - ensure - remove_exception_raising_after_create_callback_to_topic - end + new_topic.approved = true + e = assert_raises(RuntimeError) { new_topic.save } + assert_equal "Make the transaction rollback", e.message + assert_equal new_record_snapshot, !new_topic.persisted?, "The topic should have its old persisted value" + assert_equal id_snapshot, new_topic.id, "The topic should have its old id" + assert_equal id_present, new_topic.has_attribute?(Topic.primary_key) end end def test_callback_rollback_in_create_with_record_invalid_exception - begin - Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 - remove_method(:after_create_for_transaction) - def after_create_for_transaction - raise ActiveRecord::RecordInvalid.new(Author.new) - end - eoruby + topic = Class.new(Topic) { + def after_create_for_transaction + raise ActiveRecord::RecordInvalid.new(Author.new) + end + } - new_topic = Topic.create(:title => "A new topic") - assert !new_topic.persisted?, "The topic should not be persisted" - assert_nil new_topic.id, "The topic should not have an ID" - ensure - remove_exception_raising_after_create_callback_to_topic - end + new_topic = topic.create(:title => "A new topic") + assert !new_topic.persisted?, "The topic should not be persisted" + assert_nil new_topic.id, "The topic should not have an ID" end def test_nested_explicit_transactions @@ -478,62 +457,16 @@ class TransactionTest < ActiveRecord::TestCase end private - def define_callback_method(callback_method) - define_method(callback_method) do - self.history << [callback_method, :method] - end - end - def add_exception_raising_after_save_callback_to_topic - Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 - remove_method(:after_save_for_transaction) - def after_save_for_transaction - raise 'Make the transaction rollback' - end - eoruby - end - - def remove_exception_raising_after_save_callback_to_topic - Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 - remove_method :after_save_for_transaction - def after_save_for_transaction; end - eoruby - end - - def add_exception_raising_after_create_callback_to_topic - Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 - remove_method(:after_create_for_transaction) - def after_create_for_transaction - raise 'Make the transaction rollback' - end - eoruby - end - - def remove_exception_raising_after_create_callback_to_topic - Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 - remove_method :after_create_for_transaction - def after_create_for_transaction; end - eoruby - end - - %w(validation save destroy).each do |filter| - define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do - Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 - remove_method :before_#{filter}_for_transaction - def before_#{filter}_for_transaction - Book.create - false - end - eoruby - end - - define_method("remove_cancelling_before_#{filter}_with_db_side_effect_to_topic") do - Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 - remove_method :before_#{filter}_for_transaction - def before_#{filter}_for_transaction; end - eoruby + %w(validation save destroy).each do |filter| + define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do |topic| + meta = class << topic; self; end + meta.send("define_method", "before_#{filter}_for_transaction") do + Book.create + false end end + end end class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase diff --git a/activerecord/test/fixtures/friendships.yml b/activerecord/test/fixtures/friendships.yml new file mode 100644 index 0000000000..1ee09175bf --- /dev/null +++ b/activerecord/test/fixtures/friendships.yml @@ -0,0 +1,4 @@ +Connection 1: + id: 1 + person_id: 1 + friend_id: 2
\ No newline at end of file diff --git a/activerecord/test/fixtures/people.yml b/activerecord/test/fixtures/people.yml index 123673a2af..e640a38f1f 100644 --- a/activerecord/test/fixtures/people.yml +++ b/activerecord/test/fixtures/people.yml @@ -4,15 +4,18 @@ michael: primary_contact_id: 2 number1_fan_id: 3 gender: M + followers_count: 1 david: id: 2 first_name: David primary_contact_id: 3 number1_fan_id: 1 gender: M + followers_count: 1 susan: id: 3 first_name: Susan primary_contact_id: 2 number1_fan_id: 1 gender: F + followers_count: 1 diff --git a/activerecord/test/models/friendship.rb b/activerecord/test/models/friendship.rb new file mode 100644 index 0000000000..6b4f7acc38 --- /dev/null +++ b/activerecord/test/models/friendship.rb @@ -0,0 +1,4 @@ +class Friendship < ActiveRecord::Base + belongs_to :friend, class_name: 'Person' + belongs_to :follower, foreign_key: 'friend_id', class_name: 'Person', counter_cache: :followers_count +end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index e204508986..6e6ff29f77 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -8,6 +8,8 @@ class Person < ActiveRecord::Base has_many :posts_with_no_comments, -> { includes(:comments).where('comments.id is null').references(:comments) }, :through => :readers, :source => :post + has_many :followers, foreign_key: 'friend_id', class_name: 'Friendship' + has_many :references has_many :bad_references has_many :fixed_bad_references, -> { where :favourite => true }, :class_name => 'BadReference' diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 6c919a2b02..7c45ca27c0 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -270,6 +270,11 @@ ActiveRecord::Schema.define do t.string :name end + create_table :friendships, :force => true do |t| + t.integer :friend_id + t.integer :person_id + end + create_table :goofy_string_id, :force => true, :id => false do |t| t.string :id, :null => false t.string :info @@ -476,6 +481,7 @@ ActiveRecord::Schema.define do t.references :number1_fan t.integer :lock_version, :null => false, :default => 0 t.string :comments + t.integer :followers_count, :default => 0 t.references :best_friend t.references :best_friend_of t.timestamps diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 5f4bf70a08..4f57efb35d 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,11 @@ ## Rails 4.0.0 (unreleased) ## +* Remove `j` alias for `ERB::Util#json_escape`. + The `j` alias is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript` + and both modules are included in the view context that would confuse the developers. + + *Akira Matsuda* + * Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore *Guillermo Iguaran* diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 56d6676961..41d77ab6c1 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -37,7 +37,6 @@ module ActiveSupport autoload :LogSubscriber autoload :Notifications - # TODO: Narrow this list down eager_autoload do autoload :BacktraceCleaner autoload :BasicObject @@ -55,12 +54,12 @@ module ActiveSupport autoload :OptionMerger autoload :OrderedHash autoload :OrderedOptions - autoload :Rescuable autoload :StringInquirer autoload :TaggedLogging autoload :XmlMini end + autoload :Rescuable autoload :SafeBuffer, "active_support/core_ext/string/output_safety" autoload :TestCase end diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index c17d695967..dad4b29d46 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -60,18 +60,11 @@ class ERB # json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}') # # => {name:john,created_at:2010-04-28T01:39:31Z,id:1} # - # This method is also aliased as +j+, and available as a helper - # in Rails templates: - # - # <%=j @person.to_json %> - # def json_escape(s) result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] } s.html_safe? ? result.html_safe : result end - alias j json_escape - module_function :j module_function :json_escape end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index c9071a73d8..77cab6f08d 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -168,16 +168,30 @@ module ActiveSupport #:nodoc: end end - def const_missing(const_name, nesting = nil) + def const_missing(const_name) klass_name = name.presence || "Object" - unless nesting - # We'll assume that the nesting of Foo::Bar is ["Foo::Bar", "Foo"] - # even though it might not be, such as in the case of - # class Foo::Bar; Baz; end - nesting = [] - klass_name.to_s.scan(/::|$/) { nesting.unshift $` } - end + # Since Ruby does not pass the nesting at the point the unknown + # constant triggered the callback we cannot fully emulate constant + # name lookup and need to make a trade-off: we are going to assume + # that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even + # though it might not be. Counterexamples are + # + # class Foo::Bar + # Module.nesting # => [Foo::Bar] + # end + # + # or + # + # module M::N + # module S::T + # Module.nesting # => [S::T, M::N] + # end + # end + # + # for example. + nesting = [] + klass_name.to_s.scan(/::|$/) { nesting.unshift $` } # If there are multiple levels of nesting to search under, the top # level is the one we want to report as the lookup fail. @@ -287,13 +301,11 @@ module ActiveSupport #:nodoc: Object.class_eval { include Loadable } Module.class_eval { include ModuleConstMissing } Exception.class_eval { include Blamable } - true end def unhook! ModuleConstMissing.exclude_from(Module) Loadable.exclude_from(Object) - true end def load? diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb index 4045db3232..df490ae298 100644 --- a/activesupport/lib/active_support/dependencies/autoload.rb +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -1,52 +1,78 @@ require "active_support/inflector/methods" module ActiveSupport + # Autoload and eager load conveniences for your library. + # + # This module allows you to define autoloads based on + # Rails conventions (i.e. no need to define the path + # it is automatically guessed based on the filename) + # and also define a set of constants that needs to be + # eager loaded: + # + # module MyLib + # extend ActiveSupport::Autoload + # + # autoload :Model + # + # eager_autoload do + # autoload :Cache + # end + # end + # + # Then your library can be eager loaded by simply calling: + # + # MyLib.eager_load! + # module Autoload - @@autoloads = {} - @@under_path = nil - @@at_path = nil - @@eager_autoload = false + def self.extended(base) + base.class_eval do + @_autoloads = {} + @_under_path = nil + @_at_path = nil + @_eager_autoload = false + end + end - def autoload(const_name, path = @@at_path) + def autoload(const_name, path = @_at_path) unless path - full = [name, @@under_path, const_name.to_s, path].compact.join("::") + full = [name, @_under_path, const_name.to_s, path].compact.join("::") path = Inflector.underscore(full) end - if @@eager_autoload - @@autoloads[const_name] = path + if @_eager_autoload + @_autoloads[const_name] = path end super const_name, path end def autoload_under(path) - @@under_path, old_path = path, @@under_path + @_under_path, old_path = path, @_under_path yield ensure - @@under_path = old_path + @_under_path = old_path end def autoload_at(path) - @@at_path, old_path = path, @@at_path + @_at_path, old_path = path, @_at_path yield ensure - @@at_path = old_path + @_at_path = old_path end def eager_autoload - old_eager, @@eager_autoload = @@eager_autoload, true + old_eager, @_eager_autoload = @_eager_autoload, true yield ensure - @@eager_autoload = old_eager + @_eager_autoload = old_eager end - def self.eager_autoload! - @@autoloads.values.each { |file| require file } + def eager_load! + @_autoloads.values.each { |file| require file } end def autoloads - @@autoloads + @_autoloads end end end diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 30ac881090..aa8d408da9 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -5,6 +5,8 @@ module ActiveSupport class Railtie < Rails::Railtie config.active_support = ActiveSupport::OrderedOptions.new + config.eager_load_namespaces << ActiveSupport + initializer "active_support.deprecation_behavior" do |app| if deprecation = app.config.active_support.deprecation ActiveSupport::Deprecation.behavior = deprecation diff --git a/activesupport/test/autoload.rb b/activesupport/test/autoload_test.rb index 5d8026a9ca..7d02d835a8 100644 --- a/activesupport/test/autoload.rb +++ b/activesupport/test/autoload_test.rb @@ -28,15 +28,6 @@ class TestAutoloadModule < ActiveSupport::TestCase assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end - test ":eager constants can be triggered via ActiveSupport::Autoload.eager_autoload!" do - module ::Fixtures::Autoload - autoload :SomeClass, "fixtures/autoload/some_class" - end - ActiveSupport::Autoload.eager_autoload! - assert $LOADED_FEATURES.include?("fixtures/autoload/some_class.rb") - assert_nothing_raised { ::Fixtures::Autoload::SomeClass } - end - test "the location of autoloaded constants defaults to :name.underscore" do module ::Fixtures::Autoload autoload :SomeClass @@ -51,8 +42,7 @@ class TestAutoloadModule < ActiveSupport::TestCase autoload :SomeClass end - ActiveSupport::Autoload.eager_autoload! - assert $LOADED_FEATURES.include?("fixtures/autoload/some_class.rb") + ::Fixtures::Autoload.eager_load! assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile index 5ed3ad4a6b..27eaf1cbc5 100644 --- a/guides/source/configuring.textile +++ b/guides/source/configuring.textile @@ -50,8 +50,6 @@ config.after_initialize do end </ruby> -* +config.allow_concurrency+ should be true to allow concurrent (threadsafe) action processing. False by default. You probably don't want to call this one directly, though, because a series of other adjustments need to be made for threadsafe mode to work properly. Can also be enabled with +threadsafe!+. - * +config.asset_host+ sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints builtin in browsers using different domain aliases. Shorter version of +config.action_controller.asset_host+. * +config.asset_path+ lets you decorate asset paths. This can be a callable, a string, or be +nil+ which is the default. For example, the normal path for +blog.js+ would be +/javascripts/blog.js+, let that absolute path be +path+. If +config.asset_path+ is a callable, Rails calls it when generating asset paths passing +path+ as argument. If +config.asset_path+ is a string, it is expected to be a +sprintf+ format string with a +%s+ where +path+ will get inserted. In either case, Rails outputs the decorated path. Shorter version of +config.action_controller.asset_path+. @@ -89,6 +87,10 @@ end * +config.dependency_loading+ is a flag that allows you to disable constant autoloading setting it to false. It only has effect if +config.cache_classes+ is true, which it is by default in production mode. This flag is set to false by +config.threadsafe!+. +* +config.eager_load+ when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace. + +* +config.eager_load_namespaces+ registers namespaces that are eager loaded when +config.eager_load+ is true. All namespaces in the list must respond to the +eager_load!+ method. + * +config.eager_load_paths+ accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the +app+ directory of the application. * +config.encoding+ sets up the application-wide encoding. Defaults to UTF-8. @@ -109,8 +111,6 @@ end * +config.middleware+ allows you to configure the application's middleware. This is covered in depth in the "Configuring Middleware":#configuring-middleware section below. -* +config.preload_frameworks+ enables or disables preloading all frameworks at startup. Enabled by +config.threadsafe!+. Defaults to +nil+, so is disabled. - * +config.queue+ configures a different queue implementation for the application. Defaults to +Rails::Queueing::Queue+. Note that, if the default queue is changed, the default +queue_consumer+ is not going to be initialized, it is up to the new queue implementation to handle starting and shutting down its own consumer(s). * +config.queue_consumer+ configures a different consumer implementation for the default queue. Defaults to +Rails::Queueing::ThreadedConsumer+. @@ -129,10 +129,6 @@ config.session_store :my_custom_store This custom store must be defined as +ActionDispatch::Session::MyCustomStore+. In addition to symbols, they can also be objects implementing a certain API, like +ActiveRecord::SessionStore+, in which case no special namespace is required. -* +config.threadsafe!+ enables +allow_concurrency+, +cache_classes+, +dependency_loading+ and +preload_frameworks+ to make the application threadsafe. - -WARNING: Threadsafe operation is incompatible with the normal workings of development mode Rails. In particular, automatic dependency loading and class reloading are automatically disabled when you call +config.threadsafe!+. - * +config.time_zone+ sets the default time zone for the application and enables time zone awareness for Active Record. * +config.whiny_nils+ enables or disables warnings when a certain set of methods are invoked on +nil+ and it does not respond to them. Defaults to true in development and test environments. @@ -203,7 +199,7 @@ Every Rails application comes with a standard set of middleware which it uses in * +ActionDispatch::SSL+ forces every request to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to +true+. Options passed to this can be configured by using +config.ssl_options+. * +ActionDispatch::Static+ is used to serve static assets. Disabled if +config.serve_static_assets+ is +true+. -* +Rack::Lock+ wraps the app in mutex so it can only be called by a single thread at a time. Only enabled if +config.action_controller.allow_concurrency+ is set to +false+, which it is by default. +* +Rack::Lock+ wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when +config.cache_classes_+ is +false+. * +ActiveSupport::Cache::Strategy::LocalCache+ serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * +Rack::Runtime+ sets an +X-Runtime+ header, containing the time (in seconds) taken to execute the request. * +Rails::Rack::Logger+ notifies the logs that the request has began. After request is complete, flushes all the logs. @@ -341,7 +337,7 @@ h4. Configuring Action Dispatch * +config.action_dispatch.default_headers+ is a hash with HTTP headers that are set by default in each response. By default, this is defined as: <ruby> -config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block' } +config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block', 'X-Content-Type-Options' => 'nosniff' } </ruby> * +config.action_dispatch.tld_length+ sets the TLD (top-level domain) length for the application. Defaults to +1+. @@ -656,8 +652,6 @@ Serves as a placeholder so that +:load_environment_config+ can be defined to run *+load_active_support+* Requires +active_support/dependencies+ which sets up the basis for Active Support. Optionally requires +active_support/all+ if +config.active_support.bare+ is un-truthful, which is the default. -*+preload_frameworks+* Loads all autoload dependencies of Rails automatically if +config.preload_frameworks+ is +true+ or "truthful". By default this configuration option is disabled. In Rails, when internal classes are referenced for the first time they are autoloaded. +:preload_frameworks+ loads all of this at once on initialization. - *+initialize_logger+* Initializes the logger (an +ActiveSupport::BufferedLogger+ object) for the application and makes it accessible at +Rails.logger+, provided that no initializer inserted before this point has defined +Rails.logger+. *+initialize_cache+* If +Rails.cache+ isn't set yet, initializes the cache by referencing the value in +config.cache_store+ and stores the outcome as +Rails.cache+. If this object responds to the +middleware+ method, its middleware is inserted before +Rack::Runtime+ in the middleware stack. @@ -752,13 +746,13 @@ The error occurred while evaluating nil.each *+build_middleware_stack+* Builds the middleware stack for the application, returning an object which has a +call+ method which takes a Rack environment object for the request. -*+eager_load!+* If +config.cache_classes+ is true, runs the +config.before_eager_load+ hooks and then calls +eager_load!+ which will load all the Ruby files from +config.eager_load_paths+. +*+eager_load!+* If +config.eager_load+ is true, runs the +config.before_eager_load+ hooks and then calls +eager_load!+ which will load all +config.eager_load_namespaces+. *+finisher_hook+* Provides a hook for after the initialization of process of the application is complete, as well as running all the +config.after_initialize+ blocks for the application, railties and engines. *+set_routes_reloader+* Configures Action Dispatch to reload the routes file using +ActionDispatch::Callbacks.to_prepare+. -*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.cache_classes+ is set to true and +config.dependency_loading+ is set to false. +*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.eager_load+ is set to true. h3. Database pooling diff --git a/guides/source/migrations.textile b/guides/source/migrations.textile index 06e85e5914..a7de21b754 100644 --- a/guides/source/migrations.textile +++ b/guides/source/migrations.textile @@ -14,7 +14,7 @@ Migrations also allow you to describe these transformations using Ruby. The great thing about this is that (like most of Active Record's functionality) it is database independent: you don't need to worry about the precise syntax of +CREATE TABLE+ any more than you worry about variations on +SELECT *+ (you can -drop down to raw SQL for database specific features). For example you could use +drop down to raw SQL for database specific features). For example, you could use SQLite3 in development, but MySQL in production. In this guide, you'll learn all about migrations including: @@ -123,10 +123,10 @@ database independent way (you'll read about them in detail later): * +rename_column+ * +remove_reference+ -If you need to perform tasks specific to your database (for example create a +If you need to perform tasks specific to your database (e.g., create a "foreign key":#active-record-and-referential-integrity constraint) then the +execute+ method allows you to execute arbitrary SQL. A migration is just a -regular Ruby class so you're not limited to these functions. For example after +regular Ruby class so you're not limited to these functions. For example, after adding a column you could write code to set the value of that column for existing records (if necessary using your models). @@ -165,7 +165,7 @@ config.active_record.timestamped_migrations = false The combination of timestamps and recording which migrations have been run allows Rails to handle common situations that occur with multiple developers. -For example Alice adds migrations +20080906120000+ and +20080906123000+ and Bob +For example, Alice adds migrations +20080906120000+ and +20080906123000+ and Bob adds +20080906124500+ and runs it. Alice finishes her changes and checks in her migrations and Bob pulls down the latest changes. When Bob runs +rake db:migrate+, Rails knows that it has not run Alice's two migrations so it executes the +up+ method for each migration. @@ -182,7 +182,7 @@ migration again: Rails thinks it has already run the migration and so will do nothing when you run +rake db:migrate+. You must rollback the migration (for example with +rake db:rollback+), edit your migration and then run +rake db:migrate+ to run the corrected version. -In general editing existing migrations is not a good idea: you will be creating +In general, editing existing migrations is not a good idea. You will be creating extra work for yourself and your co-workers and cause major headaches if the existing version of the migration has already been run on production machines. Instead, you should write a new migration that performs the changes you require. @@ -209,8 +209,7 @@ Active Record supports the following database column types: These will be mapped onto an appropriate underlying database type. For example, with MySQL the type +:string+ is mapped to +VARCHAR(255)+. You can create -columns of types not supported by Active Record when using the non-sexy syntax, -for example +columns of types not supported by Active Record when using the non-sexy syntax such as <ruby> create_table :products do |t| @@ -255,7 +254,7 @@ by Active Record). h4. Creating a Standalone Migration -If you are creating migrations for other purposes (for example to add a column +If you are creating migrations for other purposes (e.g., to add a column to an existing table) then you can also use the migration generator: <shell> @@ -309,7 +308,7 @@ class RemovePartNumberFromProducts < ActiveRecord::Migration end </ruby> -You are not limited to one magically generated column, for example +You are not limited to one magically generated column. For example <shell> $ rails generate migration AddDetailsToProducts part_number:string price:decimal @@ -334,7 +333,7 @@ NOTE: The generated migration file for destructive migrations will still be old-style using the +up+ and +down+ methods. This is because Rails needs to know the original data types defined when you made the original changes. -Also the generator accepts column type as +references+(also available as +belongs_to+), for instance +Also, the generator accepts column type as +references+(also available as +belongs_to+). For instance <shell> $ rails generate migration AddUserRefToProducts user:references @@ -362,7 +361,7 @@ following modifiers: * +scale+ Defines the scale for the +decimal+ fields * +polymorphic+ Adds a +type+ column for +belongs_to+ associations -For instance running +For instance, running <shell> $ rails generate migration AddDetailsToProducts price:decimal{5,2} supplier:references{polymorphic} @@ -541,8 +540,8 @@ support":#active-record-and-referential-integrity. If the helpers provided by Active Record aren't enough you can use the +execute+ method to execute arbitrary SQL. -For more details and examples of individual methods, check the API documentation, -in particular the documentation for +For more details and examples of individual methods, check the API documentation. +In particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html @@ -644,7 +643,7 @@ down to, but not including, 20080906120000. h4. Rolling Back -A common task is to rollback the last migration, for example if you made a +A common task is to rollback the last migration. For example, if you made a mistake in it and wish to correct it. Rather than tracking down the version number associated with the previous migration you can run @@ -839,7 +838,7 @@ An error has occurred, this and all later migrations canceled: undefined method `fuzz' for #<Product:0x000001049b14a0> </plain> -A fix for this is to create a local model within the migration. This keeps rails +A fix for this is to create a local model within the migration. This keeps Rails from running the validations, so that the migrations run to completion. When using a faux model, it's a good idea to call diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile index 5024bc4c37..cdf7306264 100644 --- a/guides/source/upgrading_ruby_on_rails.textile +++ b/guides/source/upgrading_ruby_on_rails.textile @@ -44,6 +44,8 @@ The <tt>delete</tt> method in collection associations can now receive <tt>Fixnum Rails 4.0 has changed how orders get stacked in +ActiveRecord::Relation+. In previous versions of rails new order was applied after previous defined order. But this is no long true. Check "ActiveRecord Query guide":active_record_querying.html#ordering for more information. +Rails 4.0 has changed <tt>serialized_attributes</tt> and <tt>_attr_readonly</tt> to class methods only. Now you shouldn't use instance methods, it's deprecated. You must change them, e.g. <tt>self.serialized_attributes</tt> to <tt>self.class.serialized_attributes</tt>. + h4(#active_model4_0). Active Model Rails 4.0 has changed how errors attach with the <tt>ActiveModel::Validations::ConfirmationValidator</tt>. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>. @@ -54,6 +56,10 @@ Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, e.g. <tt>get Rack::Utils.escape('こんにちは'), :controller => 'welcome', :action => 'index'</tt> to <tt>get 'こんにちは', :controller => 'welcome', :action => 'index'</tt>. +h4(#active_support4_0). Active Support + +Rails 4.0 Removed the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`. + h4(#helpers_order). Helpers Loading Order The loading order of helpers from more than one directory has changed in Rails 4.0. Previously, helpers from all directories were gathered and then sorted alphabetically. After upgrade to Rails 4.0 helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use <tt>helpers_path</tt> parameter, this change will only impact the way of loading helpers from engines. If you rely on the fact that particular helper from engine loads before or after another helper from application or another engine, you should check if correct methods are available after upgrade. If you would like to change order in which engines are loaded, you can use <tt>config.railties_order=</tt> method. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 851f41249a..fea18b5f47 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -5,6 +5,8 @@ default_url_options[:script_name] to set proper application's mount point by yourself. *Piotr Sarnacki* +* `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded *José Valim* + * The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance rails g migration AddReferencesToProducts user:references supplier:references{polymorphic} diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 3531728421..ae872f2bb0 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -16,7 +16,7 @@ module Rails # # Besides providing the same configuration as Rails::Engine and Rails::Railtie, # the application object has several specific configurations, for example - # "allow_concurrency", "cache_classes", "consider_all_requests_local", "filter_parameters", + # "cache_classes", "consider_all_requests_local", "filter_parameters", # "logger" and so forth. # # Check Rails::Application::Configuration to see them all. @@ -216,8 +216,9 @@ module Rails railties.each { |r| r.run_tasks_blocks(app) } super require "rails/tasks" + config = self.config task :environment do - $rails_rake_task = true + config.eager_load = false require_environment! end end @@ -296,7 +297,7 @@ module Rails middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control end - middleware.use ::Rack::Lock unless config.allow_concurrency + middleware.use ::Rack::Lock unless config.cache_classes middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride middleware.use ::ActionDispatch::RequestId diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index e567df7162..a1bc95550b 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -13,11 +13,18 @@ module Rails require "active_support/all" unless config.active_support.bare 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, :group => :all do - ActiveSupport::Autoload.eager_autoload! if config.preload_frameworks + initializer :set_eager_load, :group => :all do + if config.eager_load.nil? + warn <<-INFO +config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly: + + * development - set it to false + * test - set it to false (unless you use a tool that preloads your test environment) + * production - set it to true + +INFO + config.eager_load = config.cache_classes + end end # Initialize the logger early in the stack in case we need to log some deprecation. @@ -60,7 +67,6 @@ module Rails end # Sets the dependency loading mechanism. - # TODO: Remove files from the $" and always use require. initializer :initialize_dependency_mechanism, :group => :all do ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 2b36cc9d0d..7f05b2e7e1 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -5,11 +5,11 @@ require 'rails/engine/configuration' module Rails class Application class Configuration < ::Rails::Engine::Configuration - attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets, :autoflush_log, + attr_accessor :asset_host, :asset_path, :assets, :autoflush_log, :cache_classes, :cache_store, :consider_all_requests_local, :console, - :dependency_loading, :exceptions_app, :file_watcher, :filter_parameters, + :eager_load, :exceptions_app, :file_watcher, :filter_parameters, :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, - :preload_frameworks, :railties_order, :relative_url_root, :secret_token, + :railties_order, :relative_url_root, :secret_token, :serve_static_assets, :ssl_options, :static_cache_control, :session_options, :time_zone, :reload_classes_only_on_change, :queue, :queue_consumer @@ -20,11 +20,9 @@ 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 @@ -45,6 +43,7 @@ module Rails @log_formatter = ActiveSupport::Logger::SimpleFormatter.new @queue = Rails::Queueing::Queue @queue_consumer = Rails::Queueing::ThreadedConsumer + @eager_load = nil @assets = ActiveSupport::OrderedOptions.new @assets.enabled = false @@ -91,15 +90,12 @@ module Rails end end - # Enable threaded mode. Allows concurrent requests to controller actions and - # multiple database connections. Also disables automatic dependency loading - # after boot, and disables reloading code on every request, as these are - # fundamentally incompatible with thread safety. def threadsafe! - @preload_frameworks = true + ActiveSupport::Deprecation.warn "config.threadsafe! is deprecated. Rails applications " \ + "behave by default as thread safe in production as long as config.cache_classes and " \ + "config.eager_load are set to true" @cache_classes = true - @dependency_loading = false - @allow_concurrency = true + @eager_load = true self end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 60aa40b92f..1952a0fc3a 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -50,9 +50,9 @@ module Rails end initializer :eager_load! do - if config.cache_classes && !(defined?($rails_rake_task) && $rails_rake_task) + if config.eager_load ActiveSupport.run_load_hooks(:before_eager_load, self) - eager_load! + config.eager_load_namespaces.each(&:eager_load!) end end @@ -91,7 +91,7 @@ module Rails # Disable dependency loading during request cycle initializer :disable_dependency_loading do - if config.cache_classes && !config.dependency_loading + if config.eager_load ActiveSupport::Dependencies.unhook! end end diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 9ef64da3ef..a684129353 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -87,6 +87,15 @@ module Rails middlewares = [] middlewares << [Rails::Rack::Debugger] if options[:debugger] middlewares << [::Rack::ContentLength] + + # FIXME: add Rack::Lock in the case people are using webrick. + # This is to remain backwards compatible for those who are + # running webrick in production. We should consider removing this + # in development. + if server.name == 'Rack::Handler::WEBrick' + middlewares << [::Rack::Lock] + end + Hash.new(middlewares) end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 40f35ae5a6..3a5caf9f62 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -339,11 +339,16 @@ module Rails class << self attr_accessor :called_from, :isolated + alias :isolated? :isolated alias :engine_name :railtie_name + delegate :eager_load!, to: :instance + def inherited(base) unless base.abstract_railtie? + Rails::Railtie::Configuration.eager_load_namespaces << base + base.called_from = begin # Remove the line number from backtraces making sure we don't leave anything behind call_stack = caller.map { |p| p.sub(/:\d+.*/, '') } 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 b2b760ee7b..1ac0248bcf 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -60,7 +60,7 @@ module <%= app_const_base %> config.assets.version = '1.0' <% end -%> - # Enable app-wide asynchronous ActionMailer + # Enable app-wide asynchronous ActionMailer. # config.action_mailer.async = true end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb index 1684986a59..e080ebd74e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb @@ -1,5 +1,5 @@ -# Load the rails application +# Load the rails application. require File.expand_path('../application', __FILE__) -# Initialize the rails application +# Initialize the rails application. <%= app_const %>.initialize! diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index fee9eb9456..122e7e2b34 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -6,6 +6,9 @@ # since you don't have to restart the web server when you make code changes. config.cache_classes = false + # Do not eager load code on boot. + config.eager_load = false + # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false @@ -39,6 +42,6 @@ config.assets.debug = true <%- end -%> - # In development, use an in-memory queue for queueing + # In development, use an in-memory queue for queueing. config.queue = Rails::Queueing::Queue end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 2ffae66a57..a627636089 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -4,6 +4,12 @@ # Code is not reloaded between requests. config.cache_classes = true + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both thread web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true @@ -71,10 +77,10 @@ # Disable automatic flushing of the log to improve performance. # config.autoflush_log = false - # Use default logging formatter so that PID and timestamp are not suppressed + # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new # Default the production mode queue to an in-memory queue. You will probably - # want to replace this with an out-of-process queueing solution + # want to replace this with an out-of-process queueing solution. config.queue = Rails::Queueing::Queue end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index b27b88a3c6..8ab27eb6e1 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -7,6 +7,11 @@ # and recreated between test runs. Don't rely on the data there! config.cache_classes = true + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + # Configure static asset server for tests with Cache-Control for performance. config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" @@ -34,6 +39,6 @@ # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr - # Use the testing queue + # Use the testing queue. config.queue = Rails::Queueing::TestQueue end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt index e02397aaf9..21a5de4cba 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt @@ -2,8 +2,11 @@ # Your secret key for verifying the integrity of signed cookies. # 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. +# You can generate a safe secret key with the command `rake secret`. + # Make sure your secret_token is kept private # if you're sharing your code publicly. <%= app_const %>.config.secret_token = '<%= app_secret %>' 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 ade0c4f78c..ff676280cb 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 @@ -4,5 +4,5 @@ # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information -# (create the session table with "rails generate session_migration") +# (create the session table with "rails generate session_migration"). # <%= app_const %>.config.session_store :active_record_store diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index 85acac6725..f6b1ef1feb 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -8,11 +8,11 @@ # Sample of regular route: # get 'products/:id' => 'catalog#view' - # Keep in mind you can assign values other than :controller and :action + # Keep in mind you can assign values other than :controller and :action. # Sample of named route: # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase - # This route can be invoked with purchase_url(id: product.id) + # This route can be invoked with purchase_url(id: product.id). # Sample resource route (maps HTTP verbs to controller actions automatically): # resources :products @@ -35,7 +35,7 @@ # resource :seller # end - # Sample resource route with more complex sub-resources + # Sample resource route with more complex sub-resources: # resources :products do # resources :comments # resources :sales do @@ -51,5 +51,5 @@ # end - # See how all your routes lay out with "rake routes" + # See how all your routes lay out with "rake routes". end diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index ab0e440bc4..4f937ad65a 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -155,7 +155,7 @@ task :default => :test :desc => "Create dummy application at given path" class_option :full, :type => :boolean, :default => false, - :desc => "Generate rails engine with integration tests" + :desc => "Generate a rails engine with bundled Rails application for testing" class_option :mountable, :type => :boolean, :default => false, :desc => "Generate mountable isolated application" diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 1cb99463cc..fba685c769 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -178,9 +178,6 @@ module Rails @config ||= Railtie::Configuration.new end - def eager_load! - end - def railtie_namespace @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) } end diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index 1c6b3769a5..9dc8843887 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -7,6 +7,16 @@ module Rails @@options ||= {} end + # Expose the eager_load_namespaces at "module" level for convenience. + def self.eager_load_namespaces #:nodoc: + @@eager_load_namespaces ||= [] + end + + # All namespaces that are eager loaded + def eager_load_namespaces + @@eager_load_namespaces ||= [] + end + # Add files that should be watched for change. def watchable_files @@watchable_files ||= [] diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index c813defe93..ebdbbaee8b 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -139,22 +139,19 @@ module ApplicationTests assert_instance_of Pathname, Rails.root end - test "marking the application as threadsafe sets the correct config variables" do + test "initialize an eager loaded, cache classes app" do add_to_config <<-RUBY - config.threadsafe! + config.eager_load = true + config.cache_classes = true RUBY require "#{app_path}/config/application" - assert AppTemplate::Application.config.allow_concurrency + assert AppTemplate::Application.initialize! end - test "initialize a threadsafe app" do - add_to_config <<-RUBY - config.threadsafe! - RUBY - + test "application is always added to eager_load namespaces" do require "#{app_path}/config/application" - assert AppTemplate::Application.initialize! + assert AppTemplate::Application, AppTemplate::Application.config.eager_load_namespaces end test "asset_path defaults to nil for application" do @@ -162,10 +159,11 @@ module ApplicationTests assert_equal nil, AppTemplate::Application.config.asset_path end - test "the application can be marked as threadsafe when there are no frameworks" do + test "the application can be eager loaded even when there are no frameworks" do FileUtils.rm_rf("#{app_path}/config/environments") add_to_config <<-RUBY - config.threadsafe! + config.eager_load = true + config.cache_classes = true RUBY use_frameworks [] @@ -175,22 +173,6 @@ module ApplicationTests end end - test "frameworks are not preloaded by default" do - require "#{app_path}/config/environment" - - assert ActionController.autoload?(:Caching) - end - - test "frameworks are preloaded with config.preload_frameworks is set" do - add_to_config <<-RUBY - config.preload_frameworks = true - RUBY - - require "#{app_path}/config/environment" - - assert !ActionView.autoload?(:AssetPaths) - end - test "filter_parameters should be able to set via config.filter_parameters" do add_to_config <<-RUBY config.filter_parameters += [ :foo, 'bar', lambda { |key, value| diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb index 066f0c1c84..9d97bae9ae 100644 --- a/railties/test/application/middleware/remote_ip_test.rb +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -4,20 +4,6 @@ module ApplicationTests class RemoteIpTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation - def setup - build_app - boot_rails - FileUtils.rm_rf "#{app_path}/config/environments" - end - - def teardown - teardown_app - end - - def app - @app ||= Rails.application - end - def remote_ip(env = {}) remote_ip = nil env = Rack::MockRequest.env_for("/").merge(env).merge!( diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 9c9ed0cd6b..34b460d0a7 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -87,8 +87,8 @@ module ApplicationTests assert !middleware.include?("ActiveRecord::QueryCache") end - test "removes lock if allow concurrency is set" do - add_to_config "config.allow_concurrency = true" + test "removes lock if cache classes is set" do + add_to_config "config.cache_classes = true" boot! assert !middleware.include?("Rack::Lock") end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index a450f90dbf..b05fe3aed5 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -55,6 +55,29 @@ module ApplicationTests assert_match "Doing something...", output end + def test_does_not_explode_when_accessing_a_model_with_eager_load + add_to_config <<-RUBY + config.eager_load = true + + rake_tasks do + task :do_nothing => :environment do + Hello.new.world + end + end + RUBY + + app_file "app/models/hello.rb", <<-RUBY + class Hello + def world + puts "Hello world" + end + end + RUBY + + output = Dir.chdir(app_path){ `rake do_nothing` } + assert_match "Hello world", output + end + def test_code_statistics_sanity assert_match "Code LOC: 5 Test LOC: 0 Code to Test Ratio: 1:0.0", Dir.chdir(app_path){ `rake stats` } diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 8f04692aef..37839a1c54 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -12,7 +12,6 @@ require 'bundler/setup' require 'minitest/autorun' require 'active_support/test_case' -# TODO: Remove setting this magic constant RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..") # These files do not require any others and are needed @@ -118,6 +117,7 @@ module TestHelpers end add_to_config <<-RUBY + config.eager_load = false config.secret_token = "3b7cd727ee24e8444053437c36cc66c4" config.session_store :cookie_store, :key => "_myapp_session" config.active_support.deprecation = :log @@ -136,6 +136,7 @@ module TestHelpers require "action_controller/railtie" app = Class.new(Rails::Application) + app.config.eager_load = false app.config.secret_token = "3b7cd727ee24e8444053437c36cc66c4" app.config.session_store :cookie_store, :key => "_myapp_session" app.config.active_support.deprecation = :log |