diff options
98 files changed, 1062 insertions, 415 deletions
@@ -81,7 +81,7 @@ group :test do end platforms :ruby do - gem 'nokogiri', '>= 1.6.7.rc3' + gem 'nokogiri', '>= 1.6.7' # Needed for compiling the ActionDispatch::Journey parser. gem 'racc', '>=1.4.6', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 6c31a26a25..d7abc12b21 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -230,7 +230,7 @@ GEM metaclass (0.0.4) method_source (0.8.2) mime-types (2.6.2) - mini_portile (0.7.0.rc4) + mini_portile2 (2.0.0) minitest (5.3.3) mocha (0.14.0) metaclass (~> 0.0.1) @@ -239,8 +239,8 @@ GEM mustache (1.0.2) mysql (2.9.1) mysql2 (0.4.1) - nokogiri (1.6.7.rc3) - mini_portile (~> 0.7.0.rc4) + nokogiri (1.6.7) + mini_portile2 (~> 2.0.0.rc2) pg (0.18.3) psych (2.0.15) que (0.11.2) @@ -348,7 +348,7 @@ DEPENDENCIES mocha (~> 0.14) mysql (>= 2.9.0) mysql2 (>= 0.4.0) - nokogiri (>= 1.6.7.rc3) + nokogiri (>= 1.6.7) pg (>= 0.18.0) psych (~> 2.0) qu-rails! diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc index e5c2ed8c77..397ebe4201 100644 --- a/actionmailer/README.rdoc +++ b/actionmailer/README.rdoc @@ -146,7 +146,7 @@ The Base class has the full list of configuration options. Here's an example: The latest version of Action Mailer can be installed with RubyGems: - % gem install actionmailer + $ gem install actionmailer Source code can be downloaded as part of the Rails project on GitHub @@ -173,4 +173,3 @@ Bug reports can be filed for the Ruby on Rails project here: Feature requests should be discussed on the rails-core mailing list here: * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core - diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 0b12860619..cbbf480da8 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -541,10 +541,6 @@ module ActionMailer end end - def respond_to?(method, include_private = false) #:nodoc: - super || action_methods.include?(method.to_s) - end - protected def set_payload_for_mail(payload, mail) #:nodoc: @@ -566,6 +562,12 @@ module ActionMailer super end end + + private + + def respond_to_missing?(method, include_all = false) #:nodoc: + action_methods.include?(method.to_s) + end end attr_internal :message diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index cab7d85ee7..608512d846 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,48 @@ +* `ActionController::TestCase` will be moved to it's own gem in Rails 5.1 + + With the speed improvements made to `ActionDispatch::IntegrationTest` we no + longer need to keep two separate code bases for testing controllers. In + Rails 5.1 `ActionController::TestCase` will be deprecated and moved into a + gem outside of Rails source. + + This is a documentation deprecation so that going forward so new tests will use + `ActionDispatch::IntegrationTest` instead of `ActionController::TestCase`. + + *Eileen M. Uchitelle* + +* Add a `response_format` option to `ActionDispatch::DebugExceptions` + to configure the format of the response when errors occur in + development mode. + + If `response_format` is `:default` the debug info will be rendered + in an HTML page. In the other hand, if the provided value is `:api` + the debug info will be rendered in the original response format. + + *Jorge Bejar* + +* Change the `protect_from_forgery` prepend default to `false` + + Per this comment + https://github.com/rails/rails/pull/18334#issuecomment-69234050 we want + `protect_from_forgery` to default to `prepend: false`. + + `protect_from_forgery` will now be insterted into the callback chain at the + point it is called in your application. This is useful for cases where you + want to `protect_from_forgery` after you perform required authentication + callbacks or other callbacks that are required to run after forgery protection. + + If you want `protect_from_forgery` callbacks to always run first, regardless of + position they are called in your application then you can add `prepend: true` + to your `protect_from_forgery` call. + + Example: + + ```ruby + protect_from_forgery prepend: true + ``` + + *Eileen M. Uchitelle* + * In url_for, never append a question mark to the URL when the query string is empty anyway. (It used to do that when called like `url_for(controller: 'x', action: 'y', q: {})`.) diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc index 44c980b070..0720c66cb9 100644 --- a/actionpack/README.rdoc +++ b/actionpack/README.rdoc @@ -28,7 +28,7 @@ can be used outside of Rails. The latest version of Action Pack can be installed with RubyGems: - % gem install actionpack + $ gem install actionpack Source code can be downloaded as part of the Rails project on GitHub @@ -55,4 +55,3 @@ Bug reports can be filed for the Ruby on Rails project here: Feature requests should be discussed on the rails-core mailing list here: * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core - diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 27b3eb4e58..e3c540bf5f 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -36,8 +36,7 @@ module ActionController extend ActiveSupport::Concern module ClassMethods - def make_response!(response) - request = response.request + def make_response!(request) if request.get_header("HTTP_VERSION") == "HTTP/1.0" super else @@ -223,12 +222,6 @@ module ActionController jar.write self unless committed? end - def before_sending - super - request.cookie_jar.commit! - headers.freeze - end - def build_buffer(response, body) buf = Live::Buffer.new response body.each { |part| buf.write part } @@ -293,9 +286,5 @@ module ActionController super response.close if response end - - def set_response!(response) - @_response = self.class.make_response! response - end end end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 836ed892dc..26c4550f89 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -102,13 +102,13 @@ module ActionController #:nodoc: # # Valid Options: # - # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. Like <tt>only: [ :create, :create_all ]</tt>. + # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>. # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference. - # * <tt>:prepend</tt> - By default, the verification of the authentication token is added to the front of the - # callback chain. If you need to make the verification depend on other callbacks, like authentication methods - # (say cookies vs OAuth), this might not work for you. Pass <tt>prepend: false</tt> to just add the - # verification callback in the position of the protect_from_forgery call. This means any callbacks added - # before are run first. + # * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the + # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful + # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth). + # + # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>. # * <tt>:with</tt> - Set the method to handle unverified request. # # Valid unverified request handling methods are: @@ -116,7 +116,7 @@ module ActionController #:nodoc: # * <tt>:reset_session</tt> - Resets the session. # * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified. def protect_from_forgery(options = {}) - options = options.reverse_merge(prepend: true) + options = options.reverse_merge(prepend: false) self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session) self.request_forgery_protection_token ||= :authenticity_token diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 130ba61786..8af94551cf 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/string/filters' require 'active_support/rescuable' require 'action_dispatch/http/upload' +require 'rack/test' require 'stringio' require 'set' diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 442ffd6d7c..c55720859e 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -7,6 +7,9 @@ require 'action_controller/template_assertions' require 'rails-dom-testing' module ActionController + # :stopdoc: + # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1. + # Please use ActionDispatch::IntegrationTest going forward. class TestRequest < ActionDispatch::TestRequest #:nodoc: DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup DEFAULT_ENV.delete 'PATH_INFO' @@ -658,4 +661,5 @@ module ActionController include Behavior end + # :startdoc: end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 7acf91902d..0152c17ed4 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -67,6 +67,8 @@ module ActionDispatch v = if params_readable Array(Mime[parameters[:format]]) + elsif format = format_from_path_extension + Array(Mime[format]) elsif use_accept_header && valid_accept_header accepts elsif xhr? @@ -160,6 +162,13 @@ module ActionDispatch def use_accept_header !self.class.ignore_accept_header end + + def format_from_path_extension + path = @env['action_dispatch.original_path'] || @env['PATH_INFO'] + if match = path && path.match(/\.(\w+)\z/) + match.captures.first + end + end end end end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index b64f660ec5..b8d395854c 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -47,15 +47,10 @@ module Mime def const_missing(sym) ext = sym.downcase if Mime[ext] - ActiveSupport::Deprecation.warn <<-eow -Accessing mime types via constants is deprecated. Please change: - - `Mime::#{sym}` - -to: - - `Mime[:#{ext}]` - eow + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Accessing mime types via constants is deprecated. + Please change `Mime::#{sym}` to `Mime[:#{ext}]`. + MSG Mime[ext] else super @@ -65,15 +60,10 @@ to: def const_defined?(sym, inherit = true) ext = sym.downcase if Mime[ext] - ActiveSupport::Deprecation.warn <<-eow -Accessing mime types via constants is deprecated. Please change: - - `Mime.const_defined?(#{sym})` - -to: - - `Mime[:#{ext}]` - eow + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Accessing mime types via constants is deprecated. + Please change `Mime.const_defined?(#{sym})` to `Mime[:#{ext}]`. + MSG true else super diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 3280799647..29cf821090 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -49,6 +49,10 @@ module ActionDispatch METHOD end + def self.empty + new({}) + end + def initialize(env) super @method = nil @@ -59,6 +63,9 @@ module ActionDispatch @ip = nil end + def commit_cookie_jar! # :nodoc: + end + def check_path_parameters! # If any of the path parameters has an invalid encoding then # raise since it's likely to trigger errors further on. diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index f0127aa276..9b11111a67 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -412,6 +412,8 @@ module ActionDispatch # :nodoc: end def before_sending + headers.freeze + request.commit_cookie_jar! unless committed? end def build_buffer(response, body) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 65baf117ba..3477aa8b29 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -12,6 +12,12 @@ module ActionDispatch end # :stopdoc: + prepend Module.new { + def commit_cookie_jar! + cookie_jar.commit! + end + } + def have_cookie_jar? has_header? 'action_dispatch.cookies'.freeze end diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 66bb74b9c5..b55c937e0c 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -38,9 +38,10 @@ module ActionDispatch end end - def initialize(app, routes_app = nil) - @app = app - @routes_app = routes_app + def initialize(app, routes_app = nil, response_format = :default) + @app = app + @routes_app = routes_app + @response_format = response_format end def call(env) @@ -66,41 +67,79 @@ module ActionDispatch log_error(request, wrapper) if request.get_header('action_dispatch.show_detailed_exceptions') - traces = wrapper.traces - - trace_to_show = 'Application Trace' - if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error' - trace_to_show = 'Full Trace' + case @response_format + when :api + render_for_api_application(request, wrapper) + when :default + render_for_default_application(request, wrapper) end + else + raise exception + end + end - if source_to_show = traces[trace_to_show].first - source_to_show_id = source_to_show[:id] - end + def render_for_default_application(request, wrapper) + template = create_template(request, wrapper) + file = "rescues/#{wrapper.rescue_template}" - template = DebugView.new([RESCUES_TEMPLATE_PATH], - request: request, - exception: wrapper.exception, - traces: traces, - show_source_idx: source_to_show_id, - trace_to_show: trace_to_show, - routes_inspector: routes_inspector(exception), - source_extracts: wrapper.source_extracts, - line_number: wrapper.line_number, - file: wrapper.file - ) - file = "rescues/#{wrapper.rescue_template}" - - if request.xhr? - body = template.render(template: file, layout: false, formats: [:text]) - format = "text/plain" - else - body = template.render(template: file, layout: 'rescues/layout') - format = "text/html" - end - render(wrapper.status_code, body, format) + if request.xhr? + body = template.render(template: file, layout: false, formats: [:text]) + format = "text/plain" else - raise exception + body = template.render(template: file, layout: 'rescues/layout') + format = "text/html" end + render(wrapper.status_code, body, format) + end + + def render_for_api_application(request, wrapper) + body = { + status: wrapper.status_code, + error: Rack::Utils::HTTP_STATUS_CODES.fetch( + wrapper.status_code, + Rack::Utils::HTTP_STATUS_CODES[500] + ), + exception: wrapper.exception.inspect, + traces: wrapper.traces + } + + content_type = request.formats.first + to_format = "to_#{content_type.to_sym}" + + if content_type && body.respond_to?(to_format) + formatted_body = body.public_send(to_format) + format = content_type + else + formatted_body = body.to_json + format = Mime[:json] + end + + render(wrapper.status_code, formatted_body, format) + end + + def create_template(request, wrapper) + traces = wrapper.traces + + trace_to_show = 'Application Trace' + if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error' + trace_to_show = 'Full Trace' + end + + if source_to_show = traces[trace_to_show].first + source_to_show_id = source_to_show[:id] + end + + DebugView.new([RESCUES_TEMPLATE_PATH], + request: request, + exception: wrapper.exception, + traces: traces, + show_source_idx: source_to_show_id, + trace_to_show: trace_to_show, + routes_inspector: routes_inspector(wrapper.exception), + source_extracts: wrapper.source_extracts, + line_number: wrapper.line_number, + file: wrapper.file + ) end def render(status, body, format) diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index eab20b075d..c138660a21 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -27,9 +27,11 @@ module ActionDispatch # # Asserts that the response code was status code 401 (unauthorized) # assert_response 401 def assert_response(type, message = nil) + message ||= generate_response_message(type) + if Symbol === type if [:success, :missing, :redirect, :error].include?(type) - assert_predicate @response, RESPONSE_PREDICATES[type], message + assert @response.send(RESPONSE_PREDICATES[type]), message else code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type] if code.nil? @@ -82,6 +84,17 @@ module ActionDispatch handle._compute_redirect_to_location(@request, fragment) end end + + def generate_response_message(type, code = @response.response_code) + "Expected response to be a <#{type}>, but was a <#{code}>" + .concat location_if_redirected + end + + def location_if_redirected + return '' unless @response.redirection? && @response.location.present? + location = normalize_argument_to_redirection(@response.location) + " redirect to <#{location}>" + end end end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 790f9ea5d2..711ca10419 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -375,6 +375,7 @@ module ActionDispatch @request = ActionDispatch::Request.new(session.last_request.env) response = _mock_session.last_response @response = ActionDispatch::TestResponse.from_response(response) + @response.request = @request @html_document = nil @url_options = nil diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index c28d701b48..eca0439909 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -26,7 +26,7 @@ module ActionDispatch @response.redirect_url end - # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionController::TestCase.fixture_path, path), type)</tt>: + # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.fixture_path, path), type)</tt>: # # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png') # diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb index 82c747680d..e76c222824 100644 --- a/actionpack/test/assertions/response_assertions_test.rb +++ b/actionpack/test/assertions/response_assertions_test.rb @@ -6,7 +6,12 @@ module ActionDispatch class ResponseAssertionsTest < ActiveSupport::TestCase include ResponseAssertions - FakeResponse = Struct.new(:response_code) do + FakeResponse = Struct.new(:response_code, :location) do + def initialize(*) + super + self.location ||= "http://test.example.com/posts" + end + [:successful, :not_found, :redirection, :server_error].each do |sym| define_method("#{sym}?") do sym == response_code @@ -58,6 +63,37 @@ module ActionDispatch assert_response :succezz } end + + def test_error_message_shows_404_when_404_asserted_for_success + @response = ActionDispatch::Response.new + @response.status = 404 + + error = assert_raises(Minitest::Assertion) { assert_response :success } + expected = "Expected response to be a <success>, but was a <404>" + assert_match expected, error.message + end + + def test_error_message_shows_302_redirect_when_302_asserted_for_success + @response = ActionDispatch::Response.new + @response.status = 302 + @response.location = 'http://test.host/posts/redirect/1' + + error = assert_raises(Minitest::Assertion) { assert_response :success } + expected = "Expected response to be a <success>, but was a <302>" \ + " redirect to <http://test.host/posts/redirect/1>" + assert_match expected, error.message + end + + def test_error_message_shows_302_redirect_when_302_asserted_for_301 + @response = ActionDispatch::Response.new + @response.status = 302 + @response.location = 'http://test.host/posts/redirect/2' + + error = assert_raises(Minitest::Assertion) { assert_response 301 } + expected = "Expected response to be a <301>, but was a <302>" \ + " redirect to <http://test.host/posts/redirect/2>" + assert_match expected, error.message + end end end end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index fb60dbd993..e3f669dbb5 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -93,7 +93,7 @@ end class ControllerInstanceTests < ActiveSupport::TestCase def setup @empty = EmptyController.new - @empty.set_request!(ActionDispatch::Request.new({})) + @empty.set_request!(ActionDispatch::Request.empty) @empty.set_response!(EmptyController.make_response!(@empty.request)) @contained = Submodule::ContainedEmptyController.new @empty_controllers = [@empty, @contained] diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 843dafac06..aab2d9545d 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -442,3 +442,42 @@ module ActionController end end end + +class LiveStreamRouterTest < ActionDispatch::IntegrationTest + class TestController < ActionController::Base + include ActionController::Live + + def index + response.headers['Content-Type'] = 'text/event-stream' + sse = SSE.new(response.stream) + sse.write("{\"name\":\"John\"}") + sse.write({ name: "Ryan" }) + ensure + sse.close + end + end + + def self.call(env) + routes.call(env) + end + + def self.routes + @routes ||= ActionDispatch::Routing::RouteSet.new + end + + routes.draw do + get '/test' => 'live_stream_router_test/test#index' + end + + def app + self.class + end + + test "streaming served through the router" do + get "/test" + + assert_response :ok + assert_match(/data: {\"name\":\"John\"}/, response.body) + assert_match(/data: {\"name\":\"Ryan\"}/, response.body) + end +end diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index c025c7fa00..76e2d3ff43 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -661,10 +661,6 @@ class RespondToControllerTest < ActionController::TestCase end def test_variant_inline_syntax - get :variant_inline_syntax, format: :js - assert_equal "text/javascript", @response.content_type - assert_equal "js", @response.body - get :variant_inline_syntax assert_equal "text/html", @response.content_type assert_equal "none", @response.body @@ -674,6 +670,12 @@ class RespondToControllerTest < ActionController::TestCase assert_equal "phone", @response.body end + def test_variant_inline_syntax_with_format + get :variant_inline_syntax, format: :js + assert_equal "text/javascript", @response.content_type + assert_equal "js", @response.body + end + def test_variant_inline_syntax_without_block get :variant_inline_syntax_without_block, params: { v: :phone } assert_equal "text/html", @response.content_type diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb index e61f4d241b..c226fa57ee 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -26,7 +26,7 @@ module BareMetalTest test "response_body value is wrapped in an array when the value is a String" do controller = BareController.new - controller.set_request!(ActionDispatch::Request.new({})) + controller.set_request!(ActionDispatch::Request.empty) controller.set_response!(BareController.make_response!(controller.request)) controller.index assert_equal ["Hello world"], controller.response_body diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 2a3704c300..87a8ed3dc9 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -540,10 +540,10 @@ class PrependProtectForgeryBaseControllerTest < ActionController::TestCase assert_equal(expected_callback_order, @controller.called_callbacks) end - def test_verify_authenticity_token_is_prepended_by_default + def test_verify_authenticity_token_is_not_prepended_by_default @controller = PrependDefaultController.new get :index - expected_callback_order = ["verify_authenticity_token", "custom_action"] + expected_callback_order = ["custom_action", "verify_authenticity_token"] assert_equal(expected_callback_order, @controller.called_callbacks) end end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 84c244c72a..dfcef14344 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -7,7 +7,7 @@ class CookieJarTest < ActiveSupport::TestCase attr_reader :request def setup - @request = ActionDispatch::Request.new({}) + @request = ActionDispatch::Request.empty end def test_fetch diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 30772bd9ed..159bf10545 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -75,6 +75,13 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest end end + class BoomerAPI < Boomer + def call(env) + env['action_dispatch.show_detailed_exceptions'] = @detailed + raise "puke!" + end + end + RoutesApp = Struct.new(:routes).new(SharedTestRoutes) ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp) DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp) @@ -205,6 +212,68 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_match(/ActionController::ParameterMissing/, body) end + test "rescue with json error for API request" do + @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api) + + get "/", headers: { 'action_dispatch.show_exceptions' => true } + assert_response 500 + assert_no_match(/<header>/, body) + assert_no_match(/<body>/, body) + assert_equal "application/json", response.content_type + assert_match(/RuntimeError: puke/, body) + + get "/not_found", headers: { 'action_dispatch.show_exceptions' => true } + assert_response 404 + assert_no_match(/<body>/, body) + assert_equal "application/json", response.content_type + assert_match(/#{AbstractController::ActionNotFound.name}/, body) + + get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true } + assert_response 405 + assert_no_match(/<body>/, body) + assert_equal "application/json", response.content_type + assert_match(/ActionController::MethodNotAllowed/, body) + + get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true } + assert_response 405 + assert_no_match(/<body>/, body) + assert_equal "application/json", response.content_type + assert_match(/ActionController::UnknownHttpMethod/, body) + + get "/bad_request", headers: { 'action_dispatch.show_exceptions' => true } + assert_response 400 + assert_no_match(/<body>/, body) + assert_equal "application/json", response.content_type + assert_match(/ActionController::BadRequest/, body) + + get "/parameter_missing", headers: { 'action_dispatch.show_exceptions' => true } + assert_response 400 + assert_no_match(/<body>/, body) + assert_equal "application/json", response.content_type + assert_match(/ActionController::ParameterMissing/, body) + end + + test "rescue with json on API request returns only allowed formats or json as a fallback" do + @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api) + + get "/index.json", headers: { 'action_dispatch.show_exceptions' => true } + assert_response 500 + assert_equal "application/json", response.content_type + assert_match(/RuntimeError: puke/, body) + + get "/index.html", headers: { 'action_dispatch.show_exceptions' => true } + assert_response 500 + assert_no_match(/<header>/, body) + assert_no_match(/<body>/, body) + assert_equal "application/json", response.content_type + assert_match(/RuntimeError: puke/, body) + + get "/index.xml", headers: { 'action_dispatch.show_exceptions' => true } + assert_response 500 + assert_equal "application/xml", response.content_type + assert_match(/RuntimeError: puke/, body) + end + test "does not show filtered parameters" do @app = DevelopmentApp diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb index 160cdc1582..e4475f4233 100644 --- a/actionpack/test/dispatch/live_response_test.rb +++ b/actionpack/test/dispatch/live_response_test.rb @@ -6,7 +6,7 @@ module ActionController class ResponseTest < ActiveSupport::TestCase def setup @response = Live::Response.new - @response.request = ActionDispatch::Request.new({}) #yolo + @response.request = ActionDispatch::Request.empty end def test_header_merge diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb index ae0e7e93ed..7dcbcc5c21 100644 --- a/actionpack/test/dispatch/request/session_test.rb +++ b/actionpack/test/dispatch/request/session_test.rb @@ -7,7 +7,7 @@ module ActionDispatch attr_reader :req def setup - @req = ActionDispatch::Request.new({}) + @req = ActionDispatch::Request.empty end def test_create_adds_itself_to_env diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 08c4554721..7dd9d05e62 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -4,9 +4,13 @@ class BaseRequestTest < ActiveSupport::TestCase def setup @env = { :ip_spoofing_check => true, - :tld_length => 1, "rack.input" => "foo" } + @original_tld_length = ActionDispatch::Http::URL.tld_length + end + + def teardown + ActionDispatch::Http::URL.tld_length = @original_tld_length end def url_for(options = {}) @@ -19,9 +23,9 @@ class BaseRequestTest < ActiveSupport::TestCase ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true @trusted_proxies ||= nil ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies) - tld_length = env.key?(:tld_length) ? env.delete(:tld_length) : 1 + ActionDispatch::Http::URL.tld_length = env.delete(:tld_length) if env.key?(:tld_length) + ip_app.call(env) - ActionDispatch::Http::URL.tld_length = tld_length env = @env.merge(env) ActionDispatch::Request.new(env) @@ -254,15 +258,6 @@ end class RequestDomain < BaseRequestTest test "domains" do - request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org' - assert_equal "rubyonrails.org", request.domain - - request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk" - assert_equal "rubyonrails.co.uk", request.domain(2) - - request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk", :tld_length => 2 - assert_equal "rubyonrails.co.uk", request.domain - request = stub_request 'HTTP_HOST' => "192.168.1.200" assert_nil request.domain @@ -271,25 +266,18 @@ class RequestDomain < BaseRequestTest request = stub_request 'HTTP_HOST' => "192.168.1.200.com" assert_equal "200.com", request.domain - end - test "subdomains" do - request = stub_request 'HTTP_HOST' => "www.rubyonrails.org" - assert_equal %w( www ), request.subdomains - assert_equal "www", request.subdomain + request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org' + assert_equal "rubyonrails.org", request.domain request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk" - assert_equal %w( www ), request.subdomains(2) - assert_equal "www", request.subdomain(2) - - request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk" - assert_equal %w( dev www ), request.subdomains(2) - assert_equal "dev.www", request.subdomain(2) + assert_equal "rubyonrails.co.uk", request.domain(2) - request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk", :tld_length => 2 - assert_equal %w( dev www ), request.subdomains - assert_equal "dev.www", request.subdomain + request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk", :tld_length => 2 + assert_equal "rubyonrails.co.uk", request.domain + end + test "subdomains" do request = stub_request 'HTTP_HOST' => "foobar.foobar.com" assert_equal %w( foobar ), request.subdomains assert_equal "foobar", request.subdomain @@ -309,6 +297,22 @@ class RequestDomain < BaseRequestTest request = stub_request 'HTTP_HOST' => nil assert_equal [], request.subdomains assert_equal "", request.subdomain + + request = stub_request 'HTTP_HOST' => "www.rubyonrails.org" + assert_equal %w( www ), request.subdomains + assert_equal "www", request.subdomain + + request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk" + assert_equal %w( www ), request.subdomains(2) + assert_equal "www", request.subdomain(2) + + request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk" + assert_equal %w( dev www ), request.subdomains(2) + assert_equal "dev.www", request.subdomain(2) + + request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk", :tld_length => 2 + assert_equal %w( dev www ), request.subdomains + assert_equal "dev.www", request.subdomain end end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 981d820ccf..c37679bc5f 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -5,6 +5,7 @@ require 'rack/content_length' class ResponseTest < ActiveSupport::TestCase def setup @response = ActionDispatch::Response.create + @response.request = ActionDispatch::Request.empty end def test_can_wait_until_commit @@ -39,6 +40,7 @@ class ResponseTest < ActiveSupport::TestCase def test_response_body_encoding body = ["hello".encode(Encoding::UTF_8)] response = ActionDispatch::Response.new 200, {}, body + response.request = ActionDispatch::Request.empty assert_equal Encoding::UTF_8, response.body.encoding end @@ -261,6 +263,7 @@ class ResponseTest < ActiveSupport::TestCase test "can be explicitly destructured into status, headers and an enumerable body" do response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found']) + response.request = ActionDispatch::Request.empty status, headers, body = *response assert_equal 404, status @@ -356,6 +359,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest resp.cache_control[:public] = true resp.etag = '123' resp.body = 'Hello' + resp.request = ActionDispatch::Request.empty }.to_a } @@ -392,6 +396,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest resp.charset = 'utf-16' resp.content_type = Mime[:xml] resp.body = 'Hello' + resp.request = ActionDispatch::Request.empty }.to_a } diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index ffdf775836..14894d4b82 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -8,7 +8,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest case req.path when "/not_found" raise AbstractController::ActionNotFound - when "/bad_params" + when "/bad_params", "/bad_params.json" begin raise StandardError.new rescue @@ -120,4 +120,18 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest assert_response 405 assert_equal "", body end + + test "bad params exception is returned in the correct format" do + @app = ProductionApp + + get "/bad_params", headers: { 'action_dispatch.show_exceptions' => true } + assert_equal "text/html; charset=utf-8", response.headers["Content-Type"] + assert_response 400 + assert_match(/400 error/, body) + + get "/bad_params.json", headers: { 'action_dispatch.show_exceptions' => true } + assert_equal "application/json; charset=utf-8", response.headers["Content-Type"] + assert_response 400 + assert_equal("{\"status\":400,\"error\":\"Bad Request\"}", body) + end end diff --git a/actionview/README.rdoc b/actionview/README.rdoc index 8b1f85f748..7a3e5e31d0 100644 --- a/actionview/README.rdoc +++ b/actionview/README.rdoc @@ -9,7 +9,7 @@ used to inline short Ruby snippets inside HTML), and XML Builder. The latest version of Action View can be installed with RubyGems: - % gem install actionview + $ gem install actionview Source code can be downloaded as part of the Rails project on GitHub @@ -36,4 +36,3 @@ Bug reports can be filed for the Ruby on Rails project here: Feature requests should be discussed on the rails-core mailing list here: * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core - diff --git a/activejob/README.md b/activejob/README.md index d9ff561695..7268186c00 100644 --- a/activejob/README.md +++ b/activejob/README.md @@ -102,7 +102,7 @@ see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails. The latest version of Active Job can be installed with RubyGems: ``` - % gem install activejob + $ gem install activejob ``` Source code can be downloaded as part of the Rails project on GitHub diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc index 20414c1d61..77f5761993 100644 --- a/activemodel/README.rdoc +++ b/activemodel/README.rdoc @@ -235,7 +235,7 @@ behavior out of the box: The latest version of Active Model can be installed with RubyGems: - % gem install activemodel + $ gem install activemodel Source code can be downloaded as part of the Rails project on GitHub @@ -262,4 +262,3 @@ Bug reports can be filed for the Ruby on Rails project here: Feature requests should be discussed on the rails-core mailing list here: * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core - diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb index cedc812ec7..2c89388f14 100644 --- a/activemodel/test/cases/translation_test.rb +++ b/activemodel/test/cases/translation_test.rb @@ -88,6 +88,11 @@ class ActiveModelI18nTests < ActiveModel::TestCase assert_equal 'child model', Child.model_name.human end + def test_translated_model_with_namespace + I18n.backend.store_translations 'en', activemodel: { models: { 'person/gender': 'gender model' } } + assert_equal 'gender model', Person::Gender.model_name.human + end + def test_translated_model_names_with_ancestors_fallback I18n.backend.store_translations 'en', activemodel: { models: { person: 'person model' } } assert_equal 'person model', Child.model_name.human diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 79faa9326d..56a3232ee9 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,34 @@ +* Introduce after_{create,update,delete}_commit callbacks. + + Before: + + after_commit :add_to_index_later, on: :create + after_commit :update_in_index_later, on: :update + after_commit :remove_from_index_later, on: :destroy + + After: + + after_create_commit :add_to_index_later + after_update_commit :update_in_index_later + after_destroy_commit :remove_from_index_later + + Fixes #22515. + + *Genadi Samokovarov* + +* Respect the column default values for `inheritance_column` when + instantiating records through the base class. + + Fixes #17121. + + Example: + + # The schema of BaseModel has `t.string :type, default: 'SubType'` + subtype = BaseModel.new + assert_equals SubType, subtype.class + + *Kuldeep Aggarwal* + * Fix `rake db:structure:dump` on Postgres when multiple schemas are used. Fixes #22346. @@ -419,6 +450,13 @@ *Wojciech Wnętrzak* +* Instantiating an AR model with `ActionController::Parameters` now raises + an `ActiveModel::ForbiddenAttributesError` if the parameters include a + `type` field that has not been explicitly permitted. Previously, the + `type` field was simply ignored in the same situation. + + *Prem Sichanugrist* + * PostgreSQL, `create_schema`, `drop_schema` and `rename_table` now quote schema names. diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc index 3eac8cc422..7eb4e9db1a 100644 --- a/activerecord/README.rdoc +++ b/activerecord/README.rdoc @@ -188,7 +188,7 @@ Admit the Database: The latest version of Active Record can be installed with RubyGems: - % gem install activerecord + $ gem install activerecord Source code can be downloaded as part of the Rails project on GitHub: @@ -215,4 +215,3 @@ Bug reports can be filed for the Ruby on Rails project here: Feature requests should be discussed on the rails-core mailing list here: * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core - diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 81c535d962..f02d146e89 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -106,8 +106,7 @@ module ActiveRecord::Associations::Builder # :nodoc: touch = reflection.options[:touch] callback = lambda { |record| - touch_method = touching_delayed_records? ? :touch : :touch_later - BelongsTo.touch_record(record, foreign_key, n, touch, touch_method) + BelongsTo.touch_record(record, foreign_key, n, touch, belongs_to_touch_method) } model.after_save callback, if: :changed? diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 4ae585d3f5..423a93964e 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -191,6 +191,18 @@ module ActiveRecord end end + # Returns true if the given attribute exists, otherwise false. + # + # class Person < ActiveRecord::Base + # end + # + # Person.has_attribute?('name') # => true + # Person.has_attribute?(:age) # => true + # Person.has_attribute?(:nothing) # => false + def has_attribute?(attr_name) + attribute_types.key?(attr_name.to_s) + end + # Returns the column object for the named attribute. # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the # named attribute does not exist. diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index cfd8cbda67..4058affec3 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -208,12 +208,12 @@ module ActiveRecord # # Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+ # callback (+log_children+ in this case) should be executed before the children get destroyed by the - # <tt>dependent: destroy</tt> option. + # <tt>dependent: :destroy</tt> option. # # Let's look at the code below: # # class Topic < ActiveRecord::Base - # has_many :children, dependent: destroy + # has_many :children, dependent: :destroy # # before_destroy :log_children # @@ -228,7 +228,7 @@ module ActiveRecord # You can use the +prepend+ option on the +before_destroy+ callback to avoid this. # # class Topic < ActiveRecord::Base - # has_many :children, dependent: destroy + # has_many :children, dependent: :destroy # # before_destroy :log_children, prepend: true # @@ -238,7 +238,7 @@ module ActiveRecord # end # end # - # This way, the +before_destroy+ gets executed before the <tt>dependent: destroy</tt> is called, and the data is still available. + # This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available. # # == \Transactions # diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 486b7b6d25..ccd2899489 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -197,7 +197,7 @@ module ActiveRecord elapsed = Time.now - t0 if elapsed >= timeout - msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' % + msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' % [timeout, elapsed] raise ConnectionTimeoutError, msg end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 8b719e0bcb..6259c4cd33 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -51,8 +51,8 @@ module ActiveRecord end attrs = args.first - if subclass_from_attributes?(attrs) - subclass = subclass_from_attributes(attrs) + if has_attribute?(inheritance_column) + subclass = subclass_from_attributes(attrs) || subclass_from_attributes(column_defaults) end if subclass && subclass != self @@ -163,7 +163,7 @@ module ActiveRecord end def using_single_table_inheritance?(record) - record[inheritance_column].present? && columns_hash.include?(inheritance_column) + record[inheritance_column].present? && has_attribute?(inheritance_column) end def find_sti_class(type_name) @@ -195,18 +195,14 @@ module ActiveRecord # Detect the subclass from the inheritance column of attrs. If the inheritance column value # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound - # If this is a StrongParameters hash, and access to inheritance_column is not permitted, - # this will ignore the inheritance column and return nil - def subclass_from_attributes?(attrs) - attribute_names.include?(inheritance_column) && (attrs.is_a?(Hash) || attrs.respond_to?(:permitted?)) - end - def subclass_from_attributes(attrs) attrs = attrs.to_h if attrs.respond_to?(:permitted?) - subclass_name = attrs.with_indifferent_access[inheritance_column] + if attrs.is_a?(Hash) + subclass_name = attrs.with_indifferent_access[inheritance_column] - if subclass_name.present? - find_sti_class(subclass_name) + if subclass_name.present? + find_sti_class(subclass_name) + end end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 46c6d8c293..9e566031b8 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -298,6 +298,7 @@ module ActiveRecord # * \Validations are skipped. # * \Callbacks are skipped. # * +updated_at+/+updated_on+ are not updated. + # * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all # # This method raises an ActiveRecord::ActiveRecordError when called on new # objects, or when at least one of the attributes is marked as readonly. @@ -566,5 +567,9 @@ module ActiveRecord ensure @_association_destroy_exception = nil end + + def belongs_to_touch_method + :touch + end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index f100476374..2cf19c76c5 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -347,9 +347,8 @@ module ActiveRecord # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE # statement and sends it straight to the database. It does not instantiate the involved models and it does not - # trigger Active Record callbacks or validations. Values passed to #update_all will not go through - # Active Record's type-casting behavior. It should receive only values that can be passed as-is to the SQL - # database. + # trigger Active Record callbacks or validations. However, values passed to #update_all will still go through + # Active Record's normal type casting and serialization. # # ==== Parameters # diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index b1333f110c..e4e5d63006 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -37,7 +37,7 @@ module ActiveRecord # for each different klass, and the delegations are compiled into that subclass only. delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, - :[], :&, :|, :+, :-, :sample, :reverse, :compact, to: :to_a + :[], :&, :|, :+, :-, :sample, :shuffle, :reverse, :compact, to: :to_a delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :columns_hash, :to => :klass diff --git a/activerecord/lib/active_record/touch_later.rb b/activerecord/lib/active_record/touch_later.rb index 4352a0ffea..9a80a63e28 100644 --- a/activerecord/lib/active_record/touch_later.rb +++ b/activerecord/lib/active_record/touch_later.rb @@ -16,6 +16,13 @@ module ActiveRecord surreptitiously_touch @_defer_touch_attrs self.class.connection.add_transaction_record self + + # touch the parents as we are not calling the after_save callbacks + self.class.reflect_on_all_associations(:belongs_to).each do |r| + if touch = r.options[:touch] + ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, r.foreign_key, r.name, touch, :touch_later) + end + end end def touch(*names, time: nil) # :nodoc: @@ -26,6 +33,7 @@ module ActiveRecord end private + def surreptitiously_touch(attrs) attrs.each { |attr| write_attribute attr, @_touch_time } clear_attribute_changes attrs @@ -33,9 +41,8 @@ module ActiveRecord def touch_deferred_attributes if has_defer_touch_attrs? && persisted? - @_touching_delayed_records = true touch(*@_defer_touch_attrs, time: @_touch_time) - @_touching_delayed_records, @_defer_touch_attrs, @_touch_time = nil, nil, nil + @_defer_touch_attrs, @_touch_time = nil, nil end end @@ -43,8 +50,9 @@ module ActiveRecord defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present? end - def touching_delayed_records? - defined?(@_touching_delayed_records) && @_touching_delayed_records + def belongs_to_touch_method + :touch_later end + end end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 8de82feae3..38ab1f3fc6 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -233,6 +233,24 @@ module ActiveRecord set_callback(:commit, :after, *args, &block) end + # Shortcut for +after_commit :hook, on: :create+. + def after_create_commit(*args, &block) + set_options_for_callbacks!(args, on: :create) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for +after_commit :hook, on: :update+. + def after_update_commit(*args, &block) + set_options_for_callbacks!(args, on: :update) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for +after_commit :hook, on: :destroy+. + def after_destroy_commit(*args, &block) + set_options_for_callbacks!(args, on: :destroy) + set_callback(:commit, :after, *args, &block) + end + # This callback is called after a create, update, or destroy are rolled back. # # Please check the documentation of #after_commit for options. @@ -268,9 +286,11 @@ module ActiveRecord private - def set_options_for_callbacks!(args) - options = args.last - if options.is_a?(Hash) && options[:on] + def set_options_for_callbacks!(args, enforced_options = {}) + options = args.extract_options!.merge!(enforced_options) + args << options + + if options[:on] fire_on = Array(options[:on]) assert_valid_transaction_action(fire_on) options[:if] = Array(options[:if]) diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index 8d0c5bf23f..3b97cb4ad4 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -262,7 +262,9 @@ class PostgreSQLGeometricLineTest < ActiveRecord::PostgreSQLTestCase end teardown do - @connection.drop_table 'postgresql_lines', if_exists: true + if defined?(@connection) + @connection.drop_table 'postgresql_lines', if_exists: true + end end def test_geometric_line_type diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index d961f4710e..3a9d60a79f 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1345,6 +1345,19 @@ class BasicsTest < ActiveRecord::TestCase Company.attribute_names end + def test_has_attribute + assert Company.has_attribute?('id') + assert Company.has_attribute?('type') + assert Company.has_attribute?('name') + assert_not Company.has_attribute?('lastname') + assert_not Company.has_attribute?('age') + end + + def test_has_attribute_with_symbol + assert Company.has_attribute?(:id) + assert_not Company.has_attribute?(:age) + end + def test_attribute_names_on_table_not_exists assert_equal [], NonExistentTable.attribute_names end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 52e3734dd0..03bce547da 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -478,4 +478,49 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase product = Shop::Product.new(:type => phone) assert product.save end + + def test_inheritance_new_with_subclass_as_default + original_type = Company.columns_hash["type"].default + ActiveRecord::Base.connection.change_column_default :companies, :type, 'Firm' + Company.reset_column_information + + firm = Company.new # without arguments + assert_equal 'Firm', firm.type + assert_instance_of Firm, firm + + firm = Company.new(firm_name: 'Shri Hans Plastic') # with arguments + assert_equal 'Firm', firm.type + assert_instance_of Firm, firm + + firm = Company.new(type: 'Client') # overwrite the default type + assert_equal 'Client', firm.type + assert_instance_of Client, firm + ensure + ActiveRecord::Base.connection.change_column_default :companies, :type, original_type + Company.reset_column_information + end +end + +class InheritanceAttributeTest < ActiveRecord::TestCase + + class Company < ActiveRecord::Base + self.table_name = 'companies' + attribute :type, :string, default: "InheritanceAttributeTest::Startup" + end + + class Startup < Company + end + + class Empire < Company + end + + def test_inheritance_new_with_subclass_as_default + startup = Company.new # without arguments + assert_equal 'InheritanceAttributeTest::Startup', startup.type + assert_instance_of Startup, startup + + empire = Company.new(type: 'InheritanceAttributeTest::Empire') # without arguments + assert_equal 'InheritanceAttributeTest::Empire', empire.type + assert_instance_of Empire, empire + end end diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb index b4269bd56d..f0e07e0731 100644 --- a/activerecord/test/cases/relation/delegation_test.rb +++ b/activerecord/test/cases/relation/delegation_test.rb @@ -27,7 +27,7 @@ module ActiveRecord module DelegationWhitelistBlacklistTests ARRAY_DELEGATES = [ - :+, :-, :|, :&, :[], + :+, :-, :|, :&, :[], :shuffle, :all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index, :exclude?, :find_all, :flat_map, :group_by, :include?, :length, :map, :none?, :one?, :partition, :reject, :reverse, diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb index 7058f4fbe2..07dbb4b8f8 100644 --- a/activerecord/test/cases/touch_later_test.rb +++ b/activerecord/test/cases/touch_later_test.rb @@ -95,8 +95,6 @@ class TouchLaterTest < ActiveRecord::TestCase end def test_touching_three_deep - skip "Pending from #19324" - previous_tree_updated_at = trees(:root).updated_at previous_grandparent_updated_at = nodes(:grandparent).updated_at previous_parent_updated_at = nodes(:parent_a).updated_at diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index f2229939c8..637f89196e 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -35,9 +35,9 @@ class TransactionCallbacksTest < ActiveRecord::TestCase has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id" after_commit { |record| record.do_after_commit(nil) } - after_commit(on: :create) { |record| record.do_after_commit(:create) } - after_commit(on: :update) { |record| record.do_after_commit(:update) } - after_commit(on: :destroy) { |record| record.do_after_commit(:destroy) } + after_create_commit { |record| record.do_after_commit(:create) } + after_update_commit { |record| record.do_after_commit(:update) } + after_destroy_commit { |record| record.do_after_commit(:destroy) } after_rollback { |record| record.do_after_rollback(nil) } after_rollback(on: :create) { |record| record.do_after_rollback(:create) } after_rollback(on: :update) { |record| record.do_after_rollback(:update) } diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 99098017d7..025184f63a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -356,7 +356,7 @@ ActiveRecord::Schema.define do t.column :key, :string end - create_table :guitar, force: true do |t| + create_table :guitars, force: true do |t| t.string :color end diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc index cd72f53821..14ce204303 100644 --- a/activesupport/README.rdoc +++ b/activesupport/README.rdoc @@ -10,7 +10,7 @@ outside of Rails. The latest version of Active Support can be installed with RubyGems: - % gem install activesupport + $ gem install activesupport Source code can be downloaded as part of the Rails project on GitHub: @@ -37,4 +37,3 @@ Bug reports can be filed for the Ruby on Rails project here: Feature requests should be discussed on the rails-core mailing list here: * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core - diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 3a2a7d28cb..2019afeb00 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -34,7 +34,7 @@ module ActiveSupport autoload :Dependencies autoload :DescendantsTracker autoload :FileUpdateChecker - autoload :FileEventedUpdateChecker + autoload :EventedFileUpdateChecker autoload :LogSubscriber autoload :Notifications diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 4b0ad37586..174913365a 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -36,13 +36,13 @@ module ActiveSupport end def write_entry(key, entry, options) # :nodoc: - retval = super - if options[:raw] && local_cache && retval + if options[:raw] && local_cache raw_entry = Entry.new(entry.value.to_s) raw_entry.expires_at = entry.expires_at - local_cache.write_entry(key, raw_entry, options) + super(key, raw_entry, options) + else + super end - retval end end @@ -115,11 +115,10 @@ module ActiveSupport def increment(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) instrument(:increment, name, :amount => amount) do - @data.incr(normalize_key(name, options), amount) + rescue_error_with nil do + @data.incr(normalize_key(name, options), amount) + end end - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil end # Decrement a cached value. This method uses the memcached decr atomic @@ -129,20 +128,16 @@ module ActiveSupport def decrement(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) instrument(:decrement, name, :amount => amount) do - @data.decr(normalize_key(name, options), amount) + rescue_error_with nil do + @data.decr(normalize_key(name, options), amount) + end end - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil end # Clear the entire cache on all memcached servers. This method should # be used with care when shared cache is being used. def clear(options = nil) - @data.flush_all - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil + rescue_error_with(nil) { @data.flush_all } end # Get the statistics from the memcached servers. @@ -153,10 +148,7 @@ module ActiveSupport protected # Read an entry from the cache. def read_entry(key, options) # :nodoc: - deserialize_entry(@data.get(key, options)) - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil + rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) } end # Write an entry to the cache. @@ -168,18 +160,14 @@ module ActiveSupport # Set the memcache expire a few minutes in the future to support race condition ttls on read expires_in += 5.minutes end - @data.send(method, key, value, expires_in, options) - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - false + rescue_error_with false do + @data.send(method, key, value, expires_in, options) + end end # Delete an entry from the cache. def delete_entry(key, options) # :nodoc: - @data.delete(key) - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - false + rescue_error_with(false) { @data.delete(key) } end private @@ -207,10 +195,15 @@ module ActiveSupport if raw_value entry = Marshal.load(raw_value) rescue raw_value entry.is_a?(Entry) ? entry : Entry.new(entry) - else - nil end end + + def rescue_error_with(fallback) + yield + rescue Dalli::DalliError => e + logger.error("DalliError (#{e}): #{e.message}") if logger + fallback + end end end end diff --git a/activesupport/lib/active_support/file_evented_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb index bb0f26f874..c1c30b1a86 100644 --- a/activesupport/lib/active_support/file_evented_update_checker.rb +++ b/activesupport/lib/active_support/evented_file_update_checker.rb @@ -4,7 +4,7 @@ require 'pathname' require 'concurrent/atomic/atomic_boolean' module ActiveSupport - class FileEventedUpdateChecker #:nodoc: all + class EventedFileUpdateChecker #:nodoc: all def initialize(files, dirs = {}, &block) @ph = PathHelper.new @files = files.map { |f| @ph.xpath(f) }.to_set diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 64c5232cf4..854029bf83 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -15,7 +15,7 @@ module ActiveSupport # In the authentication filter: # # id, time = @verifier.verify(cookies[:remember_me]) - # if time < Time.now + # if Time.now < time # self.current_user = User.find(id) # end # diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 7798c7ec60..c53f9c1039 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -42,8 +42,8 @@ module ActiveSupport listeners_for(name).each { |s| s.start(name, id, payload) } end - def finish(name, id, payload) - listeners_for(name).each { |s| s.finish(name, id, payload) } + def finish(name, id, payload, listeners = listeners_for(name)) + listeners.each { |s| s.finish(name, id, payload) } end def publish(name, *args) diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 075ddc2382..67f2ee1a7f 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -15,14 +15,15 @@ module ActiveSupport # and publish it. Notice that events get sent even if an error occurs # in the passed-in block. def instrument(name, payload={}) - start name, payload + # some of the listeners might have state + listeners_state = start name, payload begin yield payload rescue Exception => e payload[:exception] = [e.class.name, e.message] raise e ensure - finish name, payload + finish_with_state listeners_state, name, payload end end @@ -36,6 +37,10 @@ module ActiveSupport @notifier.finish name, @id, payload end + def finish_with_state(listeners_state, name, payload) + @notifier.finish name, @id, payload, listeners_state + end + private def unique_id diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 7bef73136c..a1bd2d5356 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -655,6 +655,14 @@ module LocalCacheBehavior end end + def test_local_cache_of_read_nil + @cache.with_local_cache do + assert_equal nil, @cache.read('foo') + @cache.send(:bypass_local_cache) { @cache.write 'foo', 'bar' } + assert_equal nil, @cache.read('foo') + end + end + def test_local_cache_of_delete @cache.with_local_cache do @cache.write('foo', 'bar') diff --git a/activesupport/test/file_evented_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb index ec3a7e28f3..bc3f77bd54 100644 --- a/activesupport/test/file_evented_update_checker_test.rb +++ b/activesupport/test/evented_file_update_checker_test.rb @@ -2,7 +2,7 @@ require 'abstract_unit' require 'pathname' require 'file_update_checker_shared_tests' -class FileEventedUpdateCheckerTest < ActiveSupport::TestCase +class EventedFileUpdateCheckerTest < ActiveSupport::TestCase include FileUpdateCheckerSharedTests def setup @@ -11,7 +11,7 @@ class FileEventedUpdateCheckerTest < ActiveSupport::TestCase end def new_checker(files = [], dirs = {}, &block) - ActiveSupport::FileEventedUpdateChecker.new(files, dirs, &block).tap do + ActiveSupport::EventedFileUpdateChecker.new(files, dirs, &block).tap do wait end end @@ -36,13 +36,13 @@ class FileEventedUpdateCheckerTest < ActiveSupport::TestCase end end -class FileEventedUpdateCheckerPathHelperTest < ActiveSupport::TestCase +class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase def pn(path) Pathname.new(path) end setup do - @ph = ActiveSupport::FileEventedUpdateChecker::PathHelper.new + @ph = ActiveSupport::EventedFileUpdateChecker::PathHelper.new end test '#xpath returns the expanded path as a Pathname object' do diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index f729f0a95b..d9cc392ac9 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -42,6 +42,21 @@ module Notifications ActiveSupport::Notifications.instrument(name) assert_equal expected, events end + + def test_subsribing_to_instrumentation_while_inside_it + # the repro requires that there are no evented subscribers for the "foo" event, + # so we have to duplicate some of the setup code + old_notifier = ActiveSupport::Notifications.notifier + ActiveSupport::Notifications.notifier = ActiveSupport::Notifications::Fanout.new + + ActiveSupport::Notifications.subscribe('foo', TestSubscriber.new) + + ActiveSupport::Notifications.instrument('foo') do + ActiveSupport::Notifications.subscribe('foo') {} + end + ensure + ActiveSupport::Notifications.notifier = old_notifier + end end class UnsubscribeTest < TestCase diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 13989a3b33..b5ad3e9411 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -412,4 +412,23 @@ end NOTE: the `:on` option specifies when a callback will be fired. If you don't supply the `:on` option the callback will fire for every action. +Since using `after_commit` callback only on create, update or delete is +common, there are aliases for those operations: + +* `after_create_commit` +* `after_update_commit` +* `after_destroy_commit` + +```ruby +class PictureFile < ActiveRecord::Base + after_destroy_commit :delete_picture_file_from_disk + + def delete_picture_file_from_disk + if File.exist?(filepath) + File.delete(filepath) + end + end +end +``` + WARNING. The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index ec31fa9d67..ed1c3e7061 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1374,8 +1374,15 @@ Client.unscoped.load This method removes all scoping and will do a normal query on the table. -Note that chaining `unscoped` with a `scope` does not work. In these cases, it is -recommended that you use the block form of `unscoped`: +```ruby +Client.unscoped.all +# SELECT "clients".* FROM "clients" + +Client.where(published: false).unscoped.all +# SELECT "clients".* FROM "clients" +``` + +`unscoped` can also accept a block. ```ruby Client.unscoped { @@ -1392,6 +1399,36 @@ You can specify an exclamation point (`!`) on the end of the dynamic finders to If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`. +Enums +----- + +The `enum` macro maps an integer column to a set of possible values. + +```ruby +class Book < ActiveRecord::Base + enum availability: [:available, :unavailable] +end +``` + +This will automatically create the corresponding [scopes](#scopes) to query the +model. Methods to transition between states and query the current state are also +added. + +```ruby +# Both examples below query just available books. +Book.available +# or +Book.where(availability: :available) + +book = Book.new(availability: :available) +book.available? # => true +book.unavailable! # => true +book.available? # => false +``` + +Read the full documentation about enums +[in the Rails API docs](http://api.rubyonrails.org/classes/ActiveRecord/Enum.html). + Understanding The Method Chaining --------------------------------- diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 181dca4b71..06c5476d45 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -517,17 +517,17 @@ Extensions to `Module` Using plain Ruby you can wrap methods with other methods, that's called _alias chaining_. -For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap `ActionController::TestCase#process` this way in `test/test_helper.rb`: +For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap `ActionDispatch::IntegrationTest#process` this way in `test/test_helper.rb`: ```ruby -ActionController::TestCase.class_eval do +ActionDispatch::IntegrationTest.class_eval do # save a reference to the original process method alias_method :original_process, :process # now redefine process and delegate to original_process - def process(action, params=nil, session=nil, flash=nil, http_method='GET') + def process('GET', path, params: nil, headers: nil, env: nil, xhr: false) params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - original_process(action, params, session, flash, http_method) + original_process('GET', path, params: params) end end ``` @@ -537,10 +537,10 @@ That's the method `get`, `post`, etc., delegate the work to. That technique has a risk, it could be the case that `:original_process` was taken. To try to avoid collisions people choose some label that characterizes what the chaining is about: ```ruby -ActionController::TestCase.class_eval do +ActionDispatch::IntegrationTest.class_eval do def process_with_stringified_params(...) params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - process_without_stringified_params(action, params, session, flash, http_method) + process_without_stringified_params(method, path, params: params) end alias_method :process_without_stringified_params, :process alias_method :process, :process_with_stringified_params @@ -550,10 +550,10 @@ end The method `alias_method_chain` provides a shortcut for that pattern: ```ruby -ActionController::TestCase.class_eval do +ActionDispatch::IntegrationTest.class_eval do def process_with_stringified_params(...) params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - process_without_stringified_params(action, params, session, flash, http_method) + process_without_stringified_params(method, path, params: params) end alias_method_chain :process, :stringified_params end diff --git a/guides/source/api_app.md b/guides/source/api_app.md index fb3127555e..17695c5db0 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -163,6 +163,14 @@ class definition: config.api_only = true ``` +Optionally, in `config/environments/development.rb` add the following line +to render error responses using the API format (JSON by default) when it +is a local request: + +```ruby +config.debug_exception_response_format = :api +``` + Finally, inside `app/controllers/application_controller.rb`, instead of: ```ruby diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md index 5126d87bee..a39b975c3e 100644 --- a/guides/source/autoloading_and_reloading_constants.md +++ b/guides/source/autoloading_and_reloading_constants.md @@ -685,7 +685,7 @@ to trigger the heuristic is defined in the conflicting place. ### Automatic Modules When a module acts as a namespace, Rails does not require the application to -defines a file for it, a directory matching the namespace is enough. +define a file for it, a directory matching the namespace is enough. Suppose an application has a back office whose controllers are stored in `app/controllers/admin`. If the `Admin` module is not yet loaded when diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 5885eb6e1c..ed88ecf6ac 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -16,7 +16,7 @@ After reading this guide, you will know: Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. As mentioned in [Rails -README](https://github.com/rails/rails/blob/master/README.md), everyone interacting in Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](https://github.com/rails/rails/blob/master/CODE_OF_CONDUCT.md). +README](https://github.com/rails/rails/blob/master/README.md), everyone interacting in Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](http://rubyonrails.org/conduct/). -------------------------------------------------------------------------------- diff --git a/guides/source/engines.md b/guides/source/engines.md index 359796b1ff..a50ef9a95f 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1033,9 +1033,9 @@ typical `GET` to a controller in a controller's functional test like this: ```ruby module Blorgh - class FooControllerTest < ActionController::TestCase + class FooControllerTest < ActionDispatch::IntegrationTest def test_index - get :index + get foos_url ... end end @@ -1049,13 +1049,13 @@ in your setup code: ```ruby module Blorgh - class FooControllerTest < ActionController::TestCase + class FooControllerTest < ActionDispatch::IntegrationTest setup do @routes = Engine.routes end def test_index - get :index + get foos_url ... end end diff --git a/guides/source/testing.md b/guides/source/testing.md index 3bfbf4f7ff..d09c0ae464 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -330,7 +330,6 @@ You'll see the usage of some of these assertions in the next chapter. All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from: * `ActiveSupport::TestCase` -* `ActionController::TestCase` * `ActionMailer::TestCase` * `ActionView::TestCase` * `ActionDispatch::IntegrationTest` @@ -682,9 +681,9 @@ Let me take you through one such test, `test_should_get_index` from the file `ar ```ruby # articles_controller_test.rb -class ArticlesControllerTest < ActionController::TestCase +class ArticlesControllerTest < ActionDispatch::IntegrationTest test "should get index" do - get :index + get '/articles' assert_response :success assert_includes @response.body, 'Articles' end @@ -697,7 +696,7 @@ and also ensuring that the right response body has been generated. The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments: * The action of the controller you are requesting. - This can be in the form of a string or a symbol. + This can be in the form of a string or a route (i.e. `articles_url`). * `params`: option with a hash of request parameters to pass into the action (e.g. query string parameters or article variables). @@ -717,7 +716,7 @@ get(:show, params: { id: 12 }, session: { user_id: 5 }) Another example: Calling the `:view` action, passing an `id` of 12 as the `params`, this time with no session, but with a flash message. ```ruby -get(:view, params: { id: 12 }, flash: { message: 'booya!' }) +get(view_url, params: { id: 12 }, flash: { message: 'booya!' }) ``` NOTE: If you try running `test_should_create_article` test from `articles_controller_test.rb` it will fail on account of the newly added model level validation and rightly so. @@ -727,7 +726,7 @@ Let us modify `test_should_create_article` test in `articles_controller_test.rb` ```ruby test "should create article" do assert_difference('Article.count') do - post :create, params: { article: { title: 'Some title' } } + post '/article', params: { article: { title: 'Some title' } } end assert_redirected_to article_path(Article.last) @@ -758,7 +757,8 @@ To test AJAX requests, you can specify the `xhr: true` option to `get`, `post`, ```ruby test "ajax request" do - get :show, params: { id: articles(:first).id }, xhr: true + article = articules(:first) + get article_url(article), xhr: true assert_equal 'hello world', @response.body assert_equal "text/javascript", @response.content_type @@ -799,11 +799,11 @@ can be set directly on the `@request` instance variable: ```ruby # setting a HTTP Header @request.headers["Accept"] = "text/plain, text/html" -get :index # simulate the request with custom header +get articles_url # simulate the request with custom header # setting a CGI variable @request.headers["HTTP_REFERER"] = "http://example.com/home" -post :create # simulate the request with custom env variable +post article_url # simulate the request with custom env variable ``` ### Testing `flash` notices @@ -818,7 +818,7 @@ Let's start by adding this assertion to our `test_should_create_article` test: ```ruby test "should create article" do assert_difference('Article.count') do - post :create, params: { article: { title: 'Some title' } } + post article_url, params: { article: { title: 'Some title' } } end assert_redirected_to article_path(Article.last) @@ -888,7 +888,7 @@ Let's write a test for the `:show` action: ```ruby test "should show article" do article = articles(:one) - get :show, params: { id: article.id } + get '/article', params: { id: article.id } assert_response :success end ``` @@ -901,7 +901,7 @@ How about deleting an existing Article? test "should destroy article" do article = articles(:one) assert_difference('Article.count', -1) do - delete :destroy, params: { id: article.id } + delete article_url(article) end assert_redirected_to articles_path @@ -913,7 +913,7 @@ We can also add a test for updating an existing Article. ```ruby test "should update article" do article = articles(:one) - patch :update, params: { id: article.id, article: { title: "updated" } } + patch '/article', params: { id: article.id, article: { title: "updated" } } assert_redirected_to article_path(article) end ``` @@ -925,7 +925,7 @@ Our test should now look something like this, disregard the other tests we're le ```ruby require 'test_helper' -class ArticlesControllerTest < ActionController::TestCase +class ArticlesControllerTest < ActionDispatch::IntegrationTest # called before every single test setup do @article = articles(:one) @@ -939,20 +939,20 @@ class ArticlesControllerTest < ActionController::TestCase test "should show article" do # Reuse the @article instance variable from setup - get :show, params: { id: @article.id } + get article_url(@article) assert_response :success end test "should destroy article" do assert_difference('Article.count', -1) do - delete :destroy, params: { id: @article.id } + delete article_url(@article) end assert_redirected_to articles_path end test "should update article" do - patch :update, params: { id: @article.id, article: { title: "updated" } } + patch article_url(@article), params: { article: { title: "updated" } } assert_redirected_to article_path(@article) end end @@ -974,7 +974,7 @@ module SignInHelper end end -class ActionController::TestCase +class ActionDispatch::IntegrationTest include SignInHelper end ``` @@ -982,13 +982,13 @@ end ```ruby require 'test_helper' -class ProfileControllerTest < ActionController::TestCase +class ProfileControllerTest < ActionDispatch::IntegrationTest test "should show profile" do # helper is now reusable from any controller test case sign_in users(:david) - get :show + get profile_url assert_response :success end end @@ -1186,10 +1186,10 @@ Functional testing for mailers involves more than just checking that the email b ```ruby require 'test_helper' -class UserControllerTest < ActionController::TestCase +class UserControllerTest < ActionDispatch::IntegrationTest test "invite friend" do assert_difference 'ActionMailer::Base.deliveries.size', +1 do - post :invite_friend, params: { email: 'friend@example.com' } + post invite_friend_url, params: { email: 'friend@example.com' } end invite_email = ActionMailer::Base.deliveries.last diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index d20ec75b61..e5ab31005e 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,26 @@ +* `config.debug_exception_response_format` configures the format used + in responses when errors occur in development mode. + + Set `config.debug_exception_response_format` to render an HTML page with + debug info (using the value `:default`) or render debug info preserving + the response format (using the value `:api`). + + *Jorge Bejar* + +* Fix setting exit status code for rake test tasks. The exit status code + was not set when tests were fired with `rake`. Now, it is being set and it matches + behavior of running tests via `rails` command (`rails test`), so no matter if + `rake test` or `rails test` command is used the exit code will be set. + + *Arkadiusz Fal* + +* Add Command infrastructure to replace rake. + + Also move `rake dev:cache` to new infrastructure. You'll need to use + `rails dev:cache` to toggle development caching from now on. + + *Chuck Callebs* + * Allow use of minitest-rails gem with Rails test runner. Fixes #22455. diff --git a/railties/Rakefile b/railties/Rakefile index 73d881b318..cf130a5f14 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -5,33 +5,20 @@ task :default => :test desc "Run all unit tests" task :test => 'test:isolated' -dash_i = [ - 'test', - 'lib', - "#{File.dirname(__FILE__)}/../activesupport/lib", - "#{File.dirname(__FILE__)}/../actionpack/lib", - "#{File.dirname(__FILE__)}/../activemodel/lib" -] - -dash_i.reverse_each do |x| - $:.unshift x unless $:.include? x -end -$-w = true - -require 'bundler/setup' unless defined?(Bundler) -require 'active_support' - namespace :test do task :isolated do dirs = (ENV["TEST_DIR"] || ENV["TEST_DIRS"] || "**").split(",") test_files = dirs.map { |dir| "test/#{dir}/*_test.rb" } Dir[*test_files].each do |file| next true if file.include?("fixtures") - puts "#{FileUtils::RUBY} -w -I#{dash_i.join ':'} #{file}" - - # We could run these in parallel, but pretty much all of the - # railties tests already run in parallel, so ¯\_(⊙︿⊙)_/¯ - Process.waitpid fork { ARGV.clear; load file } + dash_i = [ + 'test', + 'lib', + "#{File.dirname(__FILE__)}/../activesupport/lib", + "#{File.dirname(__FILE__)}/../actionpack/lib", + "#{File.dirname(__FILE__)}/../activemodel/lib" + ] + ruby "-w", "-I#{dash_i.join ':'}", file end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 80733c2d90..a5550df0de 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -24,35 +24,36 @@ module Rails def initialize(*) super self.encoding = "utf-8" - @allow_concurrency = nil - @consider_all_requests_local = false - @filter_parameters = [] - @filter_redirect = [] - @helpers_paths = [] - @public_file_server = ActiveSupport::OrderedOptions.new - @public_file_server.enabled = true - @public_file_server.index_name = "index" - @force_ssl = false - @ssl_options = {} - @session_store = :cookie_store - @session_options = {} - @time_zone = "UTC" - @beginning_of_week = :monday - @log_level = nil - @generators = app_generators - @cache_store = [ :file_store, "#{root}/tmp/cache/" ] - @railties_order = [:all] - @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] - @reload_classes_only_on_change = true - @file_watcher = file_update_checker - @exceptions_app = nil - @autoflush_log = true - @log_formatter = ActiveSupport::Logger::SimpleFormatter.new - @eager_load = nil - @secret_token = nil - @secret_key_base = nil - @api_only = false - @x = Custom.new + @allow_concurrency = nil + @consider_all_requests_local = false + @filter_parameters = [] + @filter_redirect = [] + @helpers_paths = [] + @public_file_server = ActiveSupport::OrderedOptions.new + @public_file_server.enabled = true + @public_file_server.index_name = "index" + @force_ssl = false + @ssl_options = {} + @session_store = :cookie_store + @session_options = {} + @time_zone = "UTC" + @beginning_of_week = :monday + @log_level = nil + @generators = app_generators + @cache_store = [ :file_store, "#{root}/tmp/cache/" ] + @railties_order = [:all] + @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] + @reload_classes_only_on_change = true + @file_watcher = file_update_checker + @exceptions_app = nil + @autoflush_log = true + @log_formatter = ActiveSupport::Logger::SimpleFormatter.new + @eager_load = nil + @secret_token = nil + @secret_key_base = nil + @api_only = false + @debug_exception_response_format = nil + @x = Custom.new end def static_cache_control=(value) @@ -95,6 +96,16 @@ module Rails def api_only=(value) @api_only = value generators.api_only = value + + @debug_exception_response_format ||= :api + end + + def debug_exception_response_format + @debug_exception_response_format || :default + end + + def debug_exception_response_format=(value) + @debug_exception_response_format = value end def paths @@ -182,11 +193,7 @@ module Rails private def file_update_checker - if defined?(Listen) && Listen::Adapter.select() != Listen::Adapter::Polling - ActiveSupport::FileEventedUpdateChecker - else - ActiveSupport::FileUpdateChecker - end + ActiveSupport::FileUpdateChecker end class Custom #:nodoc: diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 5cb5bfb8b7..ed6a1f82d3 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -57,7 +57,7 @@ module Rails # Must come after Rack::MethodOverride to properly log overridden methods middleware.use ::Rails::Rack::Logger, config.log_tags middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app - middleware.use ::ActionDispatch::DebugExceptions, app + middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies unless config.cache_classes diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb new file mode 100644 index 0000000000..6587984b53 --- /dev/null +++ b/railties/lib/rails/command.rb @@ -0,0 +1,70 @@ +require 'rails/commands/commands_tasks' + +module Rails + class Command + attr_reader :argv + + def initialize(argv = []) + @argv = argv + + @option_parser = build_option_parser + @options = {} + end + + def self.run(task_name, argv) + command_name = command_name_for(task_name) + + if command = command_for(command_name) + command.new(argv).run(command_name) + else + Rails::CommandsTasks.new(argv).run_command!(task_name) + end + end + + def run(command_name) + parse_options_for(command_name) + @option_parser.parse! @argv + + public_send(command_name) + end + + def self.options_for(command_name, &options_to_parse) + @@command_options[command_name] = options_to_parse + end + + def self.set_banner(command_name, banner) + options_for(command_name) { |opts, _| opts.banner = banner } + end + + private + @@commands = [] + @@command_options = {} + + def parse_options_for(command_name) + @@command_options.fetch(command_name, proc {}).call(@option_parser, @options) + end + + def build_option_parser + OptionParser.new do |opts| + opts.on('-h', '--help', 'Show this help.') do + puts opts + exit + end + end + end + + def self.inherited(command) + @@commands << command + end + + def self.command_name_for(task_name) + task_name.gsub(':', '_').to_sym + end + + def self.command_for(command_name) + @@commands.find do |command| + command.public_instance_methods.include?(command_name) + end + end + end +end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 12bd73db24..b9c4e02ca0 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -13,6 +13,7 @@ aliases = { command = ARGV.shift command = aliases[command] || command -require 'rails/commands/commands_tasks' +require 'rails/command' +require 'rails/commands/dev_cache' -Rails::CommandsTasks.new(ARGV).run_command!(command) +Rails::Command.run(command, ARGV) diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb index 685d55eea8..7e6b49e2a3 100644 --- a/railties/lib/rails/commands/commands_tasks.rb +++ b/railties/lib/rails/commands/commands_tasks.rb @@ -36,10 +36,9 @@ EOT def run_command!(command) command = parse_command(command) + if COMMAND_WHITELIST.include?(command) send(command) - else - write_error_message(command) end end @@ -151,26 +150,6 @@ EOT puts HELP_MESSAGE end - # Output an error message stating that the attempted command is not a valid rails command. - # Run the attempted command as a rake command with the --dry-run flag. If successful, suggest - # to the user that they possibly meant to run the given rails command as a rake command. - # Append the help message. - # - # Example: - # $ rails db:migrate - # Error: Command 'db:migrate' not recognized - # Did you mean: `$ rake db:migrate` ? - # (Help message output) - # - def write_error_message(command) - puts "Error: Command '#{command}' not recognized" - if %x{rake #{command} --dry-run 2>&1 } && $?.success? - puts "Did you mean: `$ rake #{command}` ?\n\n" - end - write_help_message - exit(1) - end - def parse_command(command) case command when '--version', '-v' diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index dca60f948f..2c36edfa3f 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -65,7 +65,7 @@ module Rails 'sslca' => '--ssl-ca', 'sslcert' => '--ssl-cert', 'sslcapath' => '--ssl-capath', - 'sslcipher' => '--ssh-cipher', + 'sslcipher' => '--ssl-cipher', 'sslkey' => '--ssl-key' }.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact diff --git a/railties/lib/rails/commands/dev_cache.rb b/railties/lib/rails/commands/dev_cache.rb new file mode 100644 index 0000000000..ec96e8f630 --- /dev/null +++ b/railties/lib/rails/commands/dev_cache.rb @@ -0,0 +1,21 @@ +require 'rails/command' + +module Rails + module Commands + # This is a wrapper around the Rails dev:cache command + class DevCache < Command + set_banner :dev_cache, 'Toggle development mode caching on/off' + def dev_cache + if File.exist? 'tmp/caching-dev.txt' + File.delete 'tmp/caching-dev.txt' + puts 'Development mode is no longer being cached.' + else + FileUtils.touch 'tmp/caching-dev.txt' + puts 'Development mode is now being cached.' + end + + FileUtils.touch 'tmp/restart.txt' + end + end + end +end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index b813b083f4..889154494d 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -303,6 +303,7 @@ module Rails if options[:api] remove_file 'config/initializers/session_store.rb' remove_file 'config/initializers/cookies_serializer.rb' + remove_file 'config/initializers/request_forgery_protection.rb' end end 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 4dd20a9d2e..2778dd8247 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 @@ -23,7 +23,6 @@ Rails.application.configure do config.action_controller.perform_caching = false config.cache_store = :null_store end - <%- unless options.skip_action_mailer? -%> # Don't care if the mailer can't send. diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index d3e33584d7..d60eaf6f4f 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -3,7 +3,6 @@ require 'rake' # Load Rails Rakefile extensions %w( annotations - dev framework initializers log diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake deleted file mode 100644 index 4593100465..0000000000 --- a/railties/lib/rails/tasks/dev.rake +++ /dev/null @@ -1,14 +0,0 @@ -namespace :dev do - desc 'Toggle development mode caching on/off' - task :cache do - if File.exist? 'tmp/caching-dev.txt' - File.delete 'tmp/caching-dev.txt' - puts 'Development mode is no longer being cached.' - else - FileUtils.touch 'tmp/caching-dev.txt' - puts 'Development mode is now being cached.' - end - - FileUtils.touch 'tmp/restart.txt' - end -end diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index 4e1fb13009..d39d2f32bf 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -57,7 +57,9 @@ module Minitest # as the patterns would also contain the other Rake tasks. def self.rake_run(patterns) # :nodoc: @rake_patterns = patterns - run + passed = run + exit passed unless passed + passed end def self.plugin_rails_init(options) diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb index de4b002e55..00ea32d1b8 100644 --- a/railties/lib/rails/test_unit/reporter.rb +++ b/railties/lib/rails/test_unit/reporter.rb @@ -44,7 +44,7 @@ module Rails end def relative_path_for(file) - file.sub(/^#{Rails.root}\/?/, '') + file.sub(/^#{app_root}\/?/, '') end private @@ -66,5 +66,9 @@ module Rails "#{self.executable} #{relative_path_for(assertion_path)}" end + + def app_root + @app_root ||= defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root + end end end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 5f3d1879eb..49f63d5d31 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -1393,5 +1393,45 @@ module ApplicationTests assert_equal 'unicorn', Rails.application.config.my_custom_config['key'] end + + test "api_only is false by default" do + app 'development' + refute Rails.application.config.api_only + end + + test "api_only generator config is set when api_only is set" do + add_to_config <<-RUBY + config.api_only = true + RUBY + app 'development' + + Rails.application.load_generators + assert Rails.configuration.api_only + end + + test "debug_exception_response_format is :api by default if only_api is enabled" do + add_to_config <<-RUBY + config.api_only = true + RUBY + app 'development' + + assert_equal :api, Rails.configuration.debug_exception_response_format + end + + test "debug_exception_response_format can be override" do + add_to_config <<-RUBY + config.api_only = true + RUBY + + app_file 'config/environments/development.rb', <<-RUBY + Rails.application.configure do + config.debug_exception_response_format = :default + end + RUBY + + app 'development' + + assert_equal :default, Rails.configuration.debug_exception_response_format + end end end diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index 1027bca2c1..2106708c98 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -169,6 +169,8 @@ class LoadingTest < ActiveSupport::TestCase config.file_watcher = Class.new do def initialize(*); end def updated?; false; end + def execute; end + def execute_if_updated; false; end end RUBY diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index f94d08673a..0b0fb50fe1 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -61,7 +61,7 @@ module ApplicationTests test 'db:create failure because database exists' do with_database_existing do output = `bin/rake db:create 2>&1` - assert_match /already exists/, output + assert_match(/already exists/, output) assert_equal 0, $?.exitstatus end end @@ -78,7 +78,7 @@ module ApplicationTests test 'db:create failure because bad permissions' do with_bad_permissions do output = `bin/rake db:create 2>&1` - assert_match /Couldn't create database/, output + assert_match(/Couldn't create database/, output) assert_equal 1, $?.exitstatus end end @@ -86,7 +86,7 @@ module ApplicationTests test 'db:drop failure because database does not exist' do Dir.chdir(app_path) do output = `bin/rake db:drop 2>&1` - assert_match /does not exist/, output + assert_match(/does not exist/, output) assert_equal 0, $?.exitstatus end end @@ -95,7 +95,7 @@ module ApplicationTests with_database_existing do with_bad_permissions do output = `bin/rake db:drop 2>&1` - assert_match /Couldn't drop/, output + assert_match(/Couldn't drop/, output) assert_equal 1, $?.exitstatus end end diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb deleted file mode 100644 index 28d8b22a37..0000000000 --- a/railties/test/application/rake/dev_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'isolation/abstract_unit' - -module ApplicationTests - module RakeTests - class RakeDevTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - def setup - build_app - boot_rails - end - - def teardown - teardown_app - end - - test 'dev:cache creates file and outputs message' do - Dir.chdir(app_path) do - output = `rake dev:cache` - assert File.exist?('tmp/caching-dev.txt') - assert_match(/Development mode is now being cached/, output) - end - end - - test 'dev:cache deletes file and outputs message' do - Dir.chdir(app_path) do - output = `rake dev:cache` - output = `rake dev:cache` - assert_not File.exist?('tmp/caching-dev.txt') - assert_match(/Development mode is no longer being cached/, output) - end - end - end - end -end diff --git a/railties/test/commands/dev_cache_test.rb b/railties/test/commands/dev_cache_test.rb new file mode 100644 index 0000000000..1b7a72e7fc --- /dev/null +++ b/railties/test/commands/dev_cache_test.rb @@ -0,0 +1,32 @@ +require_relative '../isolation/abstract_unit' + +module CommandsTests + class DevCacheTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test 'dev:cache creates file and outputs message' do + Dir.chdir(app_path) do + output = `rails dev:cache` + assert File.exist?('tmp/caching-dev.txt') + assert_match(%r{Development mode is now being cached}, output) + end + end + + test 'dev:cache deletes file and outputs message' do + Dir.chdir(app_path) do + `rails dev:cache` # Create caching file. + output = `rails dev:cache` # Delete caching file. + assert_not File.exist?('tmp/caching-dev.txt') + assert_match(%r{Development mode is no longer being cached}, output) + end + end + end +end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 998da3ef84..be2cd3a853 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -89,6 +89,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase config/initializers/assets.rb config/initializers/cookies_serializer.rb config/initializers/session_store.rb + config/initializers/request_forgery_protection.rb lib/assets vendor/assets test/helpers diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 67c744b529..60390cfa01 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -387,7 +387,6 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/ assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.rdoc"\]/ - assert_file "bukkits.gemspec", /s.test_files = Dir\["test\/\*\*\/\*"\]/ assert_file "bukkits.gemspec", /s.version\s+ = Bukkits::VERSION/ end @@ -461,9 +460,6 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_skipping_test_files run_generator [destination_root, "--skip-test"] assert_no_file "test" - assert_file "bukkits.gemspec" do |contents| - assert_no_match(/s.test_files = Dir\["test\/\*\*\/\*"\]/, contents) - end assert_file '.gitignore' do |contents| assert_no_match(/test\dummy/, contents) end diff --git a/railties/test/generators/plugin_test_helper.rb b/railties/test/generators/plugin_test_helper.rb new file mode 100644 index 0000000000..96c1b1d31f --- /dev/null +++ b/railties/test/generators/plugin_test_helper.rb @@ -0,0 +1,24 @@ +require 'abstract_unit' +require 'tmpdir' + +module PluginTestHelper + def create_test_file(name, pass: true) + plugin_file "test/#{name}_test.rb", <<-RUBY + require 'test_helper' + + class #{name.camelize}Test < ActiveSupport::TestCase + def test_truth + puts "#{name.camelize}Test" + assert #{pass}, 'wups!' + end + end + RUBY + end + + def plugin_file(path, contents, mode: 'w') + FileUtils.mkdir_p File.dirname("#{plugin_path}/#{path}") + File.open("#{plugin_path}/#{path}", mode) do |f| + f.puts contents + end + end +end diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb index 0887afd0db..716728819e 100644 --- a/railties/test/generators/plugin_test_runner_test.rb +++ b/railties/test/generators/plugin_test_runner_test.rb @@ -1,7 +1,8 @@ -require 'tmpdir' -require 'abstract_unit' +require 'generators/plugin_test_helper' class PluginTestRunnerTest < ActiveSupport::TestCase + include PluginTestHelper + def setup @destination_root = Dir.mktmpdir('bukkits') Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --skip-bundle` } @@ -70,7 +71,7 @@ class PluginTestRunnerTest < ActiveSupport::TestCase create_test_file 'post', pass: false output = run_test_command('test/post_test.rb') - assert_match %r{Running:\n\nPostTest\nF\n\nwups!\n\nbin/test #{plugin_path}/test/post_test.rb:6}, output + assert_match %r{Running:\n\nPostTest\nF\n\nwups!\n\nbin/test (/private)?#{plugin_path}/test/post_test.rb:6}, output end def test_only_inline_failure_output @@ -100,24 +101,4 @@ class PluginTestRunnerTest < ActiveSupport::TestCase def run_test_command(arguments) Dir.chdir(plugin_path) { `bin/test #{arguments}` } end - - def create_test_file(name, pass: true) - plugin_file "test/#{name}_test.rb", <<-RUBY - require 'test_helper' - - class #{name.camelize}Test < ActiveSupport::TestCase - def test_truth - puts "#{name.camelize}Test" - assert #{pass}, 'wups!' - end - end - RUBY - end - - def plugin_file(path, contents, mode: 'w') - FileUtils.mkdir_p File.dirname("#{plugin_path}/#{path}") - File.open("#{plugin_path}/#{path}", mode) do |f| - f.puts contents - end - end end diff --git a/railties/test/generators/test_runner_in_engine_test.rb b/railties/test/generators/test_runner_in_engine_test.rb new file mode 100644 index 0000000000..641c5d0835 --- /dev/null +++ b/railties/test/generators/test_runner_in_engine_test.rb @@ -0,0 +1,31 @@ +require 'generators/plugin_test_helper' + +class TestRunnerInEngineTest < ActiveSupport::TestCase + include PluginTestHelper + + def setup + @destination_root = Dir.mktmpdir('bukkits') + Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --full --skip-bundle` } + plugin_file 'test/dummy/db/schema.rb', '' + end + + def teardown + FileUtils.rm_rf(@destination_root) + end + + def test_rerun_snippet_is_relative_path + create_test_file 'post', pass: false + + output = run_test_command('test/post_test.rb') + assert_match %r{Running:\n\nPostTest\nF\n\nwups!\n\nbin/rails test test/post_test.rb:6}, output + end + + private + def plugin_path + "#{@destination_root}/bukkits" + end + + def run_test_command(arguments) + Dir.chdir(plugin_path) { `bin/rails test #{arguments}` } + end +end |