diff options
59 files changed, 381 insertions, 158 deletions
diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb index ef937d7c16..95e1ac4c16 100644 --- a/actioncable/lib/action_cable/connection/client_socket.rb +++ b/actioncable/lib/action_cable/connection/client_socket.rb @@ -132,11 +132,8 @@ module ActionCable @ready_state = CLOSING @close_params = [reason, code] - if @stream - @stream.shutdown - else - finalize_close - end + @stream.shutdown if @stream + finalize_close end def finalize_close diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb index d7f95e6a62..24934e12f2 100644 --- a/actioncable/lib/action_cable/connection/subscriptions.rb +++ b/actioncable/lib/action_cable/connection/subscriptions.rb @@ -54,7 +54,7 @@ module ActionCable end def unsubscribe_from_all - subscriptions.each { |id, channel| channel.unsubscribe_from_channel } + subscriptions.each { |id, channel| remove_subscription(channel) } end protected diff --git a/actioncable/test/client/echo_channel.rb b/actioncable/test/client/echo_channel.rb index 63e35f194a..5a7bac25c5 100644 --- a/actioncable/test/client/echo_channel.rb +++ b/actioncable/test/client/echo_channel.rb @@ -3,6 +3,10 @@ class EchoChannel < ActionCable::Channel::Base stream_from "global" end + def unsubscribed + 'Goodbye from EchoChannel!' + end + def ding(data) transmit(dong: data['message']) end diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index d30c381131..1b07689127 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -54,7 +54,7 @@ class ClientTest < ActionCable::TestCase @ws = Faye::WebSocket::Client.new("ws://127.0.0.1:#{port}/") @messages = Queue.new @closed = Concurrent::Event.new - @has_messages = Concurrent::Event.new + @has_messages = Concurrent::Semaphore.new(0) @pings = 0 open = Concurrent::Event.new @@ -79,7 +79,7 @@ class ClientTest < ActionCable::TestCase @pings += 1 else @messages << hash - @has_messages.set + @has_messages.release end end @@ -92,8 +92,7 @@ class ClientTest < ActionCable::TestCase end def read_message - @has_messages.wait(WAIT_WHEN_EXPECTING_EVENT) if @messages.empty? - @has_messages.reset if @messages.size < 2 + @has_messages.try_acquire(1, WAIT_WHEN_EXPECTING_EVENT) msg = @messages.pop(true) raise msg if msg.is_a?(Exception) @@ -104,9 +103,11 @@ class ClientTest < ActionCable::TestCase def read_messages(expected_size = 0) list = [] loop do - @has_messages.wait(list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT) - if @has_messages.set? - list << read_message + if @has_messages.try_acquire(1, list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT) + msg = @messages.pop(true) + raise msg if msg.is_a?(Exception) + + list << msg else break end @@ -198,4 +199,25 @@ class ClientTest < ActionCable::TestCase c.close # disappear before read end end + + def test_unsubscribe_client + with_puma_server do |port| + app = ActionCable.server + identifier = JSON.dump(channel: 'EchoChannel') + + c = faye_client(port) + c.send_message command: 'subscribe', identifier: identifier + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + assert_equal(1, app.connections.count) + assert(app.remote_connections.where(identifier: identifier)) + + channel = app.connections.first.subscriptions.send(:subscriptions).first[1] + channel.expects(:unsubscribed) + c.close + sleep 0.1 # Data takes a moment to process + + # All data is removed: No more connection or subscription information! + assert_equal(0, app.connections.count) + end + end end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 931313612c..d473ab427d 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add application/gzip as a default mime type. + + *Mehmet Emin İNAÇ* + * Add request encoding and response parsing to integration tests. What previously was: diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 1641d01c30..f6e67b02d7 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -175,10 +175,7 @@ module ActionController body = [body] unless body.nil? || body.respond_to?(:each) response.reset_body! return unless body - body.each { |part| - next if part.empty? - response.write part - } + response.body = body super end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index ad3c765d9e..e17189f9f9 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -109,7 +109,7 @@ module ActionController cattr_accessor :permit_all_parameters, instance_accessor: false cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false - delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, :inspect, + delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, :as_json, to: :@parameters # By default, never raise an UnpermittedParameters exception if these @@ -574,6 +574,10 @@ module ActionController dup end + def inspect + "<#{self.class} #{@parameters}>" + end + def method_missing(method_sym, *args, &block) if @parameters.respond_to?(method_sym) message = <<-DEPRECATE.squish @@ -598,7 +602,7 @@ module ActionController end def fields_for_style? - @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) } + @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) } end private diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 8356d1a238..66cea88256 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -28,7 +28,8 @@ Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form # http://www.ietf.org/rfc/rfc4627.txt # http://www.json.org/JSONRequest.html -Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest application/vnd.api+json ) +Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) Mime::Type.register "application/pdf", :pdf, [], %w(pdf) Mime::Type.register "application/zip", :zip, [], %w(zip) +Mime::Type.register "application/gzip", :gzip, %w(application/x-gzip), %w(gz) diff --git a/actionpack/lib/action_dispatch/journey/backwards.rb b/actionpack/lib/action_dispatch/journey/backwards.rb deleted file mode 100644 index 3bd20fdf81..0000000000 --- a/actionpack/lib/action_dispatch/journey/backwards.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Rack # :nodoc: - Mount = ActionDispatch::Journey::Router - Mount::RouteSet = ActionDispatch::Journey::Router - Mount::RegexpWithNamedGroups = ActionDispatch::Journey::Path::Pattern -end diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index f649588520..06cdce1724 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -16,9 +16,6 @@ module ActionDispatch class RoutingError < ::StandardError # :nodoc: end - # :nodoc: - VERSION = '2.0.0' - attr_accessor :routes def initialize(routes) diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index b55c937e0c..51a471fb23 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -156,15 +156,20 @@ module ActionDispatch trace = wrapper.framework_trace if trace.empty? ActiveSupport::Deprecation.silence do - message = "\n#{exception.class} (#{exception.message}):\n" - message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) - message << " " << trace.join("\n ") - logger.fatal("#{message}\n\n") + logger.fatal " " + logger.fatal "#{exception.class} (#{exception.message}):" + log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code) + logger.fatal " " + log_array logger, trace end end + def log_array(logger, array) + array.map { |line| logger.fatal line } + end + def logger(request) - request.logger || stderr_logger + request.logger || ActionView::Base.logger || stderr_logger end def stderr_logger diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb index bd43ff7697..08b3d81bf0 100644 --- a/actionpack/test/controller/parameters/accessors_test.rb +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -134,4 +134,13 @@ class ParametersAccessorsTest < ActiveSupport::TestCase params1 = ActionController::Parameters.new(hash1) assert(params1 == hash1) end + + test "inspect shows both class name and parameters" do + assert_equal( + '<ActionController::Parameters {"person"=>{"age"=>"32", '\ + '"name"=>{"first"=>"David", "last"=>"Heinemeier Hansson"}, ' \ + '"addresses"=>[{"city"=>"Chicago", "state"=>"Illinois"}]}}>', + @params.inspect + ) + end end diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 3299f2d9d0..96048e2868 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -27,6 +27,27 @@ class ParametersPermitTest < ActiveSupport::TestCase end end + def walk_permitted params + params.each do |k,v| + case v + when ActionController::Parameters + walk_permitted v + when Array + v.each { |x| walk_permitted v } + end + end + end + + test 'iteration should not impact permit' do + hash = {"foo"=>{"bar"=>{"0"=>{"baz"=>"hello", "zot"=>"1"}}}} + params = ActionController::Parameters.new(hash) + + walk_permitted params + + sanitized = params[:foo].permit(bar: [:baz]) + assert_equal({"0"=>{"baz"=>"hello"}}, sanitized[:bar].to_unsafe_h) + end + test 'if nothing is permitted, the hash becomes empty' do params = ActionController::Parameters.new(id: '1234') permitted = params.permit diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index c814d4ea54..60c6518c62 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -509,7 +509,7 @@ class EtagRenderTest < ActionController::TestCase begin File.write path, 'foo' - ActionView::Digestor.cache.clear + ActionView::LookupContext::DetailsKey.clear request.if_none_match = etag get :with_template diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index 149e37bf3d..672b272590 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -49,7 +49,7 @@ class MimeTypeTest < ActiveSupport::TestCase test "parse application with trailing star" do accept = "application/*" - expect = [Mime[:html], Mime[:js], Mime[:xml], Mime[:rss], Mime[:atom], Mime[:yaml], Mime[:url_encoded_form], Mime[:json], Mime[:pdf], Mime[:zip]] + expect = [Mime[:html], Mime[:js], Mime[:xml], Mime[:rss], Mime[:atom], Mime[:yaml], Mime[:url_encoded_form], Mime[:json], Mime[:pdf], Mime[:zip], Mime[:gzip]] parsed = Mime::Type.parse(accept) assert_equal expect, parsed end diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index a3992ad008..64801bff39 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -37,9 +37,9 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest ) end - test "parses json params for application/vnd.api+json" do + test "does not parse unregistered media types such as application/vnd.api+json" do assert_parses( - {"person" => {"name" => "David"}}, + {}, "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/vnd.api+json' } ) end @@ -143,13 +143,6 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest ) end - test "parses json params for application/vnd.api+json" do - assert_parses( - {"user" => {"username" => "sikachu"}, "username" => "sikachu"}, - "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/vnd.api+json' } - ) - end - test "parses json with non-object JSON content" do assert_parses( {"user" => {"_json" => "string content" }, "_json" => "string content" }, diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 657026fa14..f3c5d6c8df 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -4,13 +4,11 @@ require 'monitor' module ActionView class Digestor - cattr_reader(:cache) - @@cache = Concurrent::Map.new @@digest_monitor = Monitor.new class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc: def call(env) - ActionView::Digestor.cache.clear + ActionView::LookupContext::DetailsKey.clear app.call(env) end end @@ -25,12 +23,12 @@ module ActionView def digest(name:, finder:, **options) options.assert_valid_keys(:dependencies, :partial) - cache_key = ([ name, finder.details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.') + cache_key = ([ name ].compact + Array.wrap(options[:dependencies])).join('.') # this is a correctly done double-checked locking idiom # (Concurrent::Map's lookups have volatile semantics) - @@cache[cache_key] || @@digest_monitor.synchronize do - @@cache.fetch(cache_key) do # re-check under lock + finder.digest_cache[cache_key] || @@digest_monitor.synchronize do + finder.digest_cache.fetch(cache_key) do # re-check under lock compute_and_store_digest(cache_key, name, finder, options) end end @@ -42,16 +40,16 @@ module ActionView # Prevent re-entry or else recursive templates will blow the stack. # There is no need to worry about other threads seeing the +false+ value, # as they will then have to wait for this thread to let go of the @@digest_monitor lock. - pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion + pre_stored = finder.digest_cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion PartialDigestor else Digestor end - @@cache[cache_key] = stored_digest = klass.new(name, finder, options).digest + finder.digest_cache[cache_key] = stored_digest = klass.new(name, finder, options).digest ensure # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache - @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest + finder.digest_cache.delete_pair(cache_key, false) if pre_stored && !stored_digest end end diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index 126f289f55..86afedaa2d 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -55,9 +55,7 @@ module ActionView class DetailsKey #:nodoc: alias :eql? :equal? - alias :object_hash :hash - attr_reader :hash @details_keys = Concurrent::Map.new def self.get(details) @@ -72,8 +70,16 @@ module ActionView @details_keys.clear end + def self.empty?; @details_keys.empty?; end + + def self.digest_caches + @details_keys.values.map(&:digest_cache) + end + + attr_reader :digest_cache + def initialize - @hash = object_hash + @digest_cache = Concurrent::Map.new end end @@ -200,6 +206,10 @@ module ActionView self.view_paths = view_paths end + def digest_cache + details_key.digest_cache + end + def initialize_details(target, details) registered_details.each do |k| target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index ccee785d3e..3f38c3d2b9 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -135,13 +135,13 @@ module ActionView end def formatted_code_for(source_code, line_counter, indent, output) - start_value = (output == :html) ? {} : "" + start_value = (output == :html) ? {} : [] source_code.inject(start_value) do |result, line| line_counter += 1 if output == :html result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line]) else - result << "%#{indent}s: %s\n" % [line_counter, line] + result << "%#{indent}s: %s" % [line_counter, line] end end end diff --git a/actionview/test/fixtures/digestor/messages/peek.html.erb b/actionview/test/fixtures/digestor/messages/peek.html.erb new file mode 100644 index 0000000000..84885ab0bc --- /dev/null +++ b/actionview/test/fixtures/digestor/messages/peek.html.erb @@ -0,0 +1,2 @@ +<%# Template Dependency: messages/message %> +<%= render "comments/comments" %> diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index bfab97cf1e..9ff5f7126b 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -33,7 +33,6 @@ class TemplateDigestorTest < ActionView::TestCase def teardown Dir.chdir @cwd FileUtils.rm_r @tmp_dir - ActionView::Digestor.cache.clear end def test_top_level_change_reflected @@ -130,6 +129,16 @@ class TemplateDigestorTest < ActionView::TestCase end end + def test_getting_of_singly_nested_dependencies + singly_nested_dependencies = ["messages/header", "messages/form", "messages/message", "events/event", "comments/comment"] + assert_equal singly_nested_dependencies, nested_dependencies('messages/edit') + end + + def test_getting_of_doubly_nested_dependencies + doubly_nested = [{"comments/comments"=>["comments/comment"]}, "messages/message"] + assert_equal doubly_nested, nested_dependencies('messages/peek') + end + def test_nested_template_directory assert_digest_difference("messages/show") do change_template("messages/actions/_move") @@ -291,12 +300,12 @@ class TemplateDigestorTest < ActionView::TestCase def assert_digest_difference(template_name, options = {}) previous_digest = digest(template_name, options) - ActionView::Digestor.cache.clear + finder.digest_cache.clear yield assert_not_equal previous_digest, digest(template_name, options), "digest didn't change" - ActionView::Digestor.cache.clear + finder.digest_cache.clear end def digest(template_name, options = {}) diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 333e0cca11..bf811abdd0 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -226,13 +226,13 @@ module RenderTestCases assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number - assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code.strip + assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code[0].strip assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name end def test_render_error_indentation e = assert_raises(ActionView::Template::Error) { @view.render(:partial => "test/raise_indentation") } - error_lines = e.annoted_source_code.split("\n") + error_lines = e.annoted_source_code assert_match %r!error\shere!, e.message assert_equal "11", e.line_number assert_equal " 9: <p>Ninth paragraph</p>", error_lines.second @@ -252,7 +252,7 @@ module RenderTestCases assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number - assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code.strip + assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code[0].strip assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 70549243a8..641e9195c6 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,16 @@ +* Fixed `where` for polymorphic associations when passed an array containing different types. + + Fixes #17011. + + Example: + + PriceEstimate.where(estimate_of: [Treasure.find(1), Car.find(2)]) + # => SELECT "price_estimates".* FROM "price_estimates" + WHERE (("price_estimates"."estimate_of_type" = 'Treasure' AND "price_estimates"."estimate_of_id" = 1) + OR ("price_estimates"."estimate_of_type" = 'Car' AND "price_estimates"."estimate_of_id" = 2)) + + *Philippe Huibonhoa* + * Fix a bug where using `t.foreign_key` twice with the same `to_table` within the same table definition would only create one foreign key. @@ -10,7 +23,7 @@ *Bogdan Gusiev*, *Jon Hinson* -* Rework `ActiveRecord::Relation#last` +* Rework `ActiveRecord::Relation#last`. 1. Never perform additional SQL on loaded relation 2. Use SQL reverse order instead of loading relation if relation doesn't have limit @@ -33,7 +46,7 @@ * Allow `joins` to be unscoped. - Closes #13775. + Fixes #13775. *Takashi Kokubun* diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index a6d81c82b4..4c22be8235 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -29,14 +29,6 @@ module ActiveRecord assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? end - # Tries to assign given value to given attribute. - # In case of an error, re-raises with the ActiveRecord constant. - def _assign_attribute(k, v) # :nodoc: - super - rescue ActiveModel::UnknownAttributeError - raise UnknownAttributeError.new(self, k) - end - # Assign any deferred nested attributes after the base attributes have been set. def assign_nested_parameter_attributes(pairs) pairs.each { |k, v| _assign_attribute(k, v) } diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index d9b42d4283..5ef434734a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,5 +1,4 @@ require 'active_record/type' -require 'active_support/core_ext/benchmark' require 'active_record/connection_adapters/determine_if_preparable_visitor' require 'active_record/connection_adapters/schema_cache' require 'active_record/connection_adapters/sql_type_metadata' @@ -398,7 +397,7 @@ module ActiveRecord if can_perform_case_insensitive_comparison_for?(column) table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new)) else - case_sensitive_comparison(table, attribute, column, value) + table[attribute].eq(Arel::Nodes::BindParam.new) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 8751b6da4b..b12bac2737 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -615,13 +615,10 @@ module ActiveRecord end end - def case_insensitive_comparison(table, attribute, column, value) - if column.case_sensitive? - super - else - table[attribute].eq(Arel::Nodes::BindParam.new) - end + def can_perform_case_insensitive_comparison_for?(column) + column.case_sensitive? end + private :can_perform_case_insensitive_comparison_for? # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 0f88791d92..953495a8b6 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -5,6 +5,7 @@ module ActiveRecord require 'active_record/relation/predicate_builder/base_handler' require 'active_record/relation/predicate_builder/basic_object_handler' require 'active_record/relation/predicate_builder/class_handler' + require 'active_record/relation/predicate_builder/polymorphic_array_handler' require 'active_record/relation/predicate_builder/range_handler' require 'active_record/relation/predicate_builder/relation_handler' @@ -22,6 +23,7 @@ module ActiveRecord register_handler(Relation, RelationHandler.new) register_handler(Array, ArrayHandler.new(self)) register_handler(AssociationQueryValue, AssociationQueryHandler.new(self)) + register_handler(PolymorphicArrayValue, PolymorphicArrayHandler.new(self)) end def build_from_hash(attributes) @@ -40,10 +42,7 @@ module ActiveRecord # # For polymorphic relationships, find the foreign key and type: # PriceEstimate.where(estimate_of: treasure) - if table.associated_with?(column) - value = AssociationQueryValue.new(table.associated_table(column), value) - end - + value = AssociationQueryHandler.value_for(table, column, value) if table.associated_with?(column) build(table.arel_attribute(column), value) end diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb index e81be63cd3..d7fd878265 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb @@ -1,6 +1,16 @@ module ActiveRecord class PredicateBuilder class AssociationQueryHandler # :nodoc: + def self.value_for(table, column, value) + klass = if table.associated_table(column).polymorphic_association? && ::Array === value && value.first.is_a?(Base) + PolymorphicArrayValue + else + AssociationQueryValue + end + + klass.new(table.associated_table(column), value) + end + def initialize(predicate_builder) @predicate_builder = predicate_builder end diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb new file mode 100644 index 0000000000..b6c6240343 --- /dev/null +++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb @@ -0,0 +1,57 @@ +module ActiveRecord + class PredicateBuilder + class PolymorphicArrayHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + table = value.associated_table + queries = value.type_to_ids_mapping.map do |type, ids| + { table.association_foreign_type.to_s => type, table.association_foreign_key.to_s => ids } + end + + predicates = queries.map { |query| predicate_builder.build_from_hash(query) } + + if predicates.size > 1 + type_and_ids_predicates = predicates.map { |type_predicate, id_predicate| Arel::Nodes::Grouping.new(type_predicate.and(id_predicate)) } + type_and_ids_predicates.inject(&:or) + else + predicates.first + end + end + + protected + + attr_reader :predicate_builder + end + + class PolymorphicArrayValue # :nodoc: + attr_reader :associated_table, :values + + def initialize(associated_table, values) + @associated_table = associated_table + @values = values + end + + def type_to_ids_mapping + default_hash = Hash.new { |hsh, key| hsh[key] = [] } + values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) } + end + + private + + def primary_key(value) + associated_table.association_primary_key(base_class(value)) + end + + def base_class(value) + value.class.base_class + end + + def convert_to_id(value) + value._read_attribute(primary_key(value)) + end + end + end +end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 91d486e902..4533f3263f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -655,6 +655,10 @@ module ActiveRecord # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'author_id = 3')) # def or(other) + unless other.is_a? Relation + raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead." + end + spawn.or!(other) end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 3602ee7ba2..84aac3e721 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -108,7 +108,7 @@ class EachTest < ActiveRecord::TestCase end end - def test_find_in_batches_should_finish_the_end_option + def test_find_in_batches_should_end_at_the_finish_option assert_queries(6) do Post.find_in_batches(batch_size: 1, finish: 5) do |batch| assert_kind_of Array, batch @@ -316,7 +316,7 @@ class EachTest < ActiveRecord::TestCase end end - def test_in_batches_should_finish_the_end_option + def test_in_batches_should_end_at_the_finish_option post = Post.order('id DESC').where('id <= ?', 5).first assert_queries(7) do relation = Post.in_batches(of: 1, finish: 5, load: true).reverse_each.first diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 6fbc6196cc..38a4072114 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -61,6 +61,13 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase assert_equal "No association found for name `honesty'. Has it been defined yet?", exception.message end + def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attributes + exception = assert_raise ActiveModel::UnknownAttributeError do + Pirate.new(:ship_attributes => { :sail => true }) + end + assert_equal "unknown attribute 'sail' for Ship.", exception.message + end + def test_should_disable_allow_destroy_by_default Pirate.accepts_nested_attributes_for :ship @@ -582,6 +589,13 @@ module NestedAttributesOnACollectionAssociationTests assert_respond_to @pirate, association_setter end + def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attributes_for_has_many + exception = assert_raise ActiveModel::UnknownAttributeError do + @pirate.parrots_attributes = [{ peg_leg: true }] + end + assert_equal "unknown attribute 'peg_leg' for Parrot.", exception.message + end + def test_should_save_only_one_association_on_create pirate = Pirate.create!({ :catchphrase => 'Arr', diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb index 28a0862f91..ce8c5ca489 100644 --- a/activerecord/test/cases/relation/or_test.rb +++ b/activerecord/test/cases/relation/or_test.rb @@ -82,5 +82,11 @@ module ActiveRecord assert_equal p.loaded?, true assert_equal expected, p.or(Post.where('id = 2')).to_a end + + def test_or_with_non_relation_object_raises_error + assert_raises ArgumentError do + Post.where(id: [1, 2, 3]).or(title: 'Rails') + end + end end end diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index bc6378b90e..56a2b5b8c6 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -2,6 +2,7 @@ require "cases/helper" require "models/author" require "models/binary" require "models/cake_designer" +require "models/car" require "models/chef" require "models/comment" require "models/edge" @@ -14,7 +15,7 @@ require "models/vertex" module ActiveRecord class WhereTest < ActiveRecord::TestCase - fixtures :posts, :edges, :authors, :binaries, :essays + fixtures :posts, :edges, :authors, :binaries, :essays, :cars, :treasures, :price_estimates def test_where_copies_bind_params author = authors(:david) @@ -114,6 +115,17 @@ module ActiveRecord assert_equal expected.to_sql, actual.to_sql end + def test_polymorphic_array_where_multiple_types + treasure_1 = treasures(:diamond) + treasure_2 = treasures(:sapphire) + car = cars(:honda) + + expected = [price_estimates(:diamond), price_estimates(:sapphire_1), price_estimates(:sapphire_2), price_estimates(:honda)].sort + actual = PriceEstimate.where(estimate_of: [treasure_1, treasure_2, car]).to_a.sort + + assert_equal expected, actual + end + def test_polymorphic_nested_relation_where expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: Treasure.where(id: [1,2])) actual = PriceEstimate.where(estimate_of: Treasure.where(id: [1,2])) diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index ab63f5825c..bce86875e1 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -177,6 +177,7 @@ class StoreTest < ActiveRecord::TestCase assert_equal [:color], first_model.stored_attributes[:data] assert_equal [:color, :width, :height], second_model.stored_attributes[:data] assert_equal [:color, :area, :volume], third_model.stored_attributes[:data] + assert_equal [:color], first_model.stored_attributes[:data] end test "YAML coder initializes the store when a Nil value is given" do diff --git a/activerecord/test/fixtures/price_estimates.yml b/activerecord/test/fixtures/price_estimates.yml index 1149ab17a2..406d65a142 100644 --- a/activerecord/test/fixtures/price_estimates.yml +++ b/activerecord/test/fixtures/price_estimates.yml @@ -1,7 +1,16 @@ -saphire_1: +sapphire_1: price: 10 estimate_of: sapphire (Treasure) sapphire_2: price: 20 estimate_of: sapphire (Treasure) + +diamond: + price: 30 + estimate_of: diamond (Treasure) + +honda: + price: 40 + estimate_of_type: Car + estimate_of_id: 1 diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 778c22b1f6..0f37e9a289 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -12,6 +12,8 @@ class Car < ActiveRecord::Base has_many :engines, :dependent => :destroy, inverse_of: :my_car has_many :wheels, :as => :wheelable, :dependent => :destroy + has_many :price_estimates, :as => :estimate_of + scope :incl_tyres, -> { includes(:tyres) } scope :incl_engines, -> { includes(:engines) } diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 854029bf83..4c3deffe6e 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -24,6 +24,12 @@ module ActiveSupport # hash upon initialization: # # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML) + # + # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default. + # If you want to use a different hash algorithm, you can change it by providing + # `:digest` key as an option while initializing the verifier: + # + # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256') class MessageVerifier class InvalidSignature < StandardError; end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index d9a668c0ea..4dc84e4a59 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -66,10 +66,13 @@ module ActiveSupport alias :assert_not_respond_to :refute_respond_to alias :assert_not_same :refute_same - # Reveals the intention that the block should not raise any exception. + + # Assertion that the block should not raise an exception. + # + # Passes if evaluated code in the yielded block raises no exception. # # assert_nothing_raised do - # ... + # perform_service(param: 'no_exception') # end def assert_nothing_raised(*args) yield diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb index 3f24aa3b4d..8322707495 100644 --- a/guides/bug_report_templates/action_controller_master.rb +++ b/guides/bug_report_templates/action_controller_master.rb @@ -8,11 +8,6 @@ end gemfile(true) do source 'https://rubygems.org' gem 'rails', github: 'rails/rails' - gem 'arel', github: 'rails/arel' - gem 'rack', github: 'rack/rack' - gem 'sprockets', github: 'rails/sprockets' - gem 'sprockets-rails', github: 'rails/sprockets-rails' - gem 'sass-rails', github: 'rails/sass-rails' end require 'action_controller/railtie' diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb index 5b742a9093..a86edd9121 100644 --- a/guides/bug_report_templates/active_record_master.rb +++ b/guides/bug_report_templates/active_record_master.rb @@ -8,11 +8,6 @@ end gemfile(true) do source 'https://rubygems.org' gem 'rails', github: 'rails/rails' - gem 'arel', github: 'rails/arel' - gem 'rack', github: 'rack/rack' - gem 'sprockets', github: 'rails/sprockets' - gem 'sprockets-rails', github: 'rails/sprockets-rails' - gem 'sass-rails', github: 'rails/sass-rails' gem 'sqlite3' end diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb index fcc90fa503..70cf931f34 100644 --- a/guides/bug_report_templates/generic_master.rb +++ b/guides/bug_report_templates/generic_master.rb @@ -8,11 +8,6 @@ end gemfile(true) do source 'https://rubygems.org' gem 'rails', github: 'rails/rails' - gem 'arel', github: 'rails/arel' - gem 'rack', github: 'rack/rack' - gem 'sprockets', github: 'rails/sprockets' - gem 'sprockets-rails', github: 'rails/sprockets-rails' - gem 'sass-rails', github: 'rails/sass-rails' end require 'active_support' diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 5ae78eba04..d380ba8fce 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -768,6 +768,7 @@ framework it is. Kudos to all of them. [action-pack]: https://github.com/rails/rails/blob/5-0-stable/actionpack/CHANGELOG.md [action-view]: https://github.com/rails/rails/blob/5-0-stable/actionview/CHANGELOG.md [action-mailer]: https://github.com/rails/rails/blob/5-0-stable/actionmailer/CHANGELOG.md +[action-cable]: https://github.com/rails/rails/blob/5-0-stable/actioncable/CHANGELOG.md [active-record]: https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md [active-model]: https://github.com/rails/rails/blob/5-0-stable/activemodel/CHANGELOG.md [active-support]: https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 10122629b2..e66b9a4301 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -3078,7 +3078,7 @@ INFO: The following calculation methods have edge cases in October 1582, since d #### `Date.current` -Active Support defines `Date.current` to be today in the current time zone. That's like `Date.today`, except that it honors the user time zone, if defined. It also defines `Date.yesterday` and `Date.tomorrow`, and the instance predicates `past?`, `today?`, and `future?`, all of them relative to `Date.current`. +Active Support defines `Date.current` to be today in the current time zone. That's like `Date.today`, except that it honors the user time zone, if defined. It also defines `Date.yesterday` and `Date.tomorrow`, and the instance predicates `past?`, `today?`, `future?`, `on_weekday?` and `on_weekend?`, all of them relative to `Date.current`. When making Date comparisons using methods which honor the user time zone, make sure to use `Date.current` and not `Date.today`. There are cases where the user time zone might be in the future compared to the system time zone, which `Date.today` uses by default. This means `Date.today` may equal `Date.yesterday`. @@ -3467,6 +3467,8 @@ years_ago years_since prev_year (last_year) next_year +on_weekday? +on_weekend? ``` The following methods are reimplemented so you do **not** need to load `active_support/core_ext/date/calculations.rb` for these ones: @@ -3653,6 +3655,8 @@ years_ago years_since prev_year (last_year) next_year +on_weekday? +on_weekend? ``` They are analogous. Please refer to their documentation above and take into account the following differences: diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 563214896a..0598b9c7fa 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -13,8 +13,8 @@ In this guide you will learn: -------------------------------------------------------------------------------- -What is an API app? -------------------- +What is an API Application? +--------------------------- Traditionally, when people said that they used Rails as an "API", they meant providing a programmatically accessible API alongside their web application. @@ -28,15 +28,14 @@ applications. For example, Twitter uses its [public API](https://dev.twitter.com) in its web application, which is built as a static site that consumes JSON resources. -Instead of using Rails to generate dynamic HTML that will communicate with the -server through forms and links, many developers are treating their web application -as just another client, delivered as static HTML, CSS and JavaScript consuming -a simple JSON API. +Instead of using Rails to generate HTML that communicates with the server +through forms and links, many developers are treating their web application as +just an API client delivered as HTML with JavaScript that consumes a JSON API. This guide covers building a Rails application that serves JSON resources to an -API client **or** a client-side framework. +API client, including client-side frameworks. -Why use Rails for JSON APIs? +Why Use Rails for JSON APIs? ---------------------------- The first question a lot of people have when thinking about building a JSON API @@ -75,7 +74,7 @@ Handled at the middleware layer: URL-encoded String? No problem. Rails will decode the JSON for you and make it available in `params`. Want to use nested URL-encoded parameters? That works too. -- Conditional GETs: Rails handles conditional `GET`, (`ETag` and `Last-Modified`), +- Conditional GETs: Rails handles conditional `GET` (`ETag` and `Last-Modified`) processing request headers and returning the correct response headers and status code. All you need to do is use the [`stale?`](http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F) @@ -104,21 +103,21 @@ Handled at the Action Pack layer: add the response headers, but why? - Caching: Rails provides page, action and fragment caching. Fragment caching is especially helpful when building up a nested JSON object. -- Basic, Digest and Token Authentication: Rails comes with out-of-the-box support +- Basic, Digest, and Token Authentication: Rails comes with out-of-the-box support for three kinds of HTTP authentication. -- Instrumentation: Rails has an instrumentation API that will trigger registered +- Instrumentation: Rails has an instrumentation API that triggers registered handlers for a variety of events, such as action processing, sending a file or data, redirection, and database queries. The payload of each event comes with relevant information (for the action processing event, the payload includes the controller, action, parameters, request format, request method and the request's full path). -- Generators: This may be passé for advanced Rails users, but it can be nice to - generate a resource and get your model, controller, test stubs, and routes - created for you in a single command. +- Generators: It is often handy to generate a resource and get your model, + controller, test stubs, and routes created for you in a single command for + further tweaking. Same for migrations and others. - Plugins: Many third-party libraries come with support for Rails that reduce or eliminate the cost of setting up and gluing together the library and the web framework. This includes things like overriding default generators, adding - rake tasks, and honoring Rails choices (like the logger and cache back-end). + Rake tasks, and honoring Rails choices (like the logger and cache back-end). Of course, the Rails boot process also glues together all registered components. For example, the Rails boot process is what uses your `config/database.yml` file @@ -167,14 +166,6 @@ 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/engines.md b/guides/source/engines.md index db50ad278f..c5fc2f73b4 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -402,8 +402,8 @@ module Blorgh end ``` -NOTE: The `ApplicationController` class being inherited from here is the -`Blorgh::ApplicationController`, not an application's `ApplicationController`. +NOTE: The `ArticlesController` class inherits from +`Blorgh::ApplicationController`, not the application's `ApplicationController`. The helper inside `app/helpers/blorgh/articles_helper.rb` is also namespaced: diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 2506baac16..a3be5356a1 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,11 @@ +* Change fail fast of `bin/rails test` interrupts run on error. + + *Yuji Yaginuma* + +* The application generator supports `--skip-listen` to opt-out of features + that depend on the listen gem. As of this writing they are the evented file + system monitor and the async plugin for spring. + * The Gemfiles of new applications include spring-watcher-listen on Linux and Mac OS X (unless --skip-spring). diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 8f8c2ec9e1..9adfcc6ee7 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -63,6 +63,9 @@ module Rails class_option :skip_spring, type: :boolean, default: false, desc: "Don't install Spring application preloader" + class_option :skip_listen, type: :boolean, default: false, + desc: "Don't generate configuration that depends on the listen gem" + class_option :skip_javascript, type: :boolean, aliases: '-J', default: false, desc: 'Skip JavaScript files' @@ -390,6 +393,10 @@ module Rails !options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin") end + def depend_on_listen? + !options[:skip_listen] && os_supports_listen_out_of_the_box? + end + def os_supports_listen_out_of_the_box? RbConfig::CONFIG['host_os'] =~ /darwin|linux/ end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index c3fad31f23..f3bc9d9734 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -38,13 +38,13 @@ group :development do gem 'web-console', '~> 3.0' <%- end -%> <%- end -%> -<% if os_supports_listen_out_of_the_box? -%> +<% if depend_on_listen? -%> gem 'listen', '~> 3.0.5' <% end -%> <% if spring_install? -%> # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' -<% if os_supports_listen_out_of_the_box? -%> +<% if depend_on_listen? -%> gem 'spring-watcher-listen', '~> 2.0.0' <% 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 3451ade158..d4e2b1c756 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 @@ -58,5 +58,5 @@ Rails.application.configure do # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. - <%= '# ' unless os_supports_listen_out_of_the_box? %>config.file_watcher = ActiveSupport::EventedFileUpdateChecker + <%= '# ' unless depend_on_listen? %>config.file_watcher = ActiveSupport::EventedFileUpdateChecker end diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index b5e836b584..56efd35a95 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -90,6 +90,7 @@ task default: :test opts[:force] = force opts[:skip_bundle] = true opts[:api] = options.api? + opts[:skip_listen] = true invoke Rails::Generators::AppGenerator, [ File.expand_path(dummy_path, destination_root) ], opts @@ -287,10 +288,6 @@ task default: :test protected - def app_templates_dir - "../../app/templates" - end - def create_dummy_app(path = nil) dummy_path(path) if path diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index 29a3d991b8..03c705ffef 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -42,7 +42,7 @@ module Minitest end opts.on("-f", "--fail-fast", - "Abort test run on first failure") do + "Abort test run on first failure or error") do options[:fail_fast] = true end diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb index ce99dbd585..e81bd9df7e 100644 --- a/railties/lib/rails/test_unit/reporter.rb +++ b/railties/lib/rails/test_unit/reporter.rb @@ -24,7 +24,7 @@ module Rails io.puts end - if fail_fast? && result.failure && !result.error? && !result.skipped? + if fail_fast? && result.failure && !result.skipped? raise Interrupt end end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 397c5a939d..383f485db5 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -988,7 +988,7 @@ module ApplicationTests app 'development' post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json" - assert_equal '{"title"=>"foo"}', last_response.body + assert_equal '<ActionController::Parameters {"title"=>"foo"}>', last_response.body end test "config.action_controller.permit_all_parameters = true" do diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb index 3198e12662..210646c7c0 100644 --- a/railties/test/application/per_request_digest_cache_test.rb +++ b/railties/test/application/per_request_digest_cache_test.rb @@ -50,12 +50,13 @@ class PerRequestDigestCacheTest < ActiveSupport::TestCase get '/customers' assert_equal 200, last_response.status - assert_equal [ '8ba099b7749542fe765ff34a6824d548' ], ActionView::Digestor.cache.values + values = ActionView::LookupContext::DetailsKey.digest_caches.first.values + assert_equal [ '8ba099b7749542fe765ff34a6824d548' ], values assert_equal %w(david dingus), last_response.body.split.map(&:strip) end test "template digests are cleared before a request" do - assert_called(ActionView::Digestor.cache, :clear) do + assert_called(ActionView::LookupContext::DetailsKey, :clear) do get '/customers' assert_equal 200, last_response.status end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index be05e779ea..921a5b36b5 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -479,18 +479,20 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_inclusion_of_listen_related_gems + def test_inclusion_of_listen_related_configuration_by_default run_generator if RbConfig::CONFIG['host_os'] =~ /darwin|linux/ - assert_gem 'listen' - assert_gem 'spring-watcher-listen' + assert_listen_related_configuration else - assert_file 'Gemfile' do |content| - assert_no_match(/listen/, content) - end + assert_no_listen_related_configuration end end + def test_non_inclusion_of_listen_related_configuration_if_skip_listen + run_generator [destination_root, '--skip-listen'] + assert_no_listen_related_configuration + end + def test_evented_file_update_checker_config run_generator assert_file 'config/environments/development.rb' do |content| @@ -759,4 +761,23 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile", /^\s*gem\s+["']#{gem}["']$*/ end end + + def assert_listen_related_configuration + assert_gem 'listen' + assert_gem 'spring-watcher-listen' + + assert_file 'config/environments/development.rb' do |content| + assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + end + end + + def assert_no_listen_related_configuration + assert_file 'Gemfile' do |content| + assert_no_match(/listen/, content) + end + + assert_file 'config/environments/development.rb' do |content| + assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + end + end end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 4111a30664..d7d27e6b2e 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -444,6 +444,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end + def test_dummy_appplication_skip_listen_by_default + run_generator + + assert_file 'test/dummy/config/environments/development.rb' do |contents| + assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, contents) + end + end + def test_ensure_that_gitignore_can_be_generated_from_a_template_for_dummy_path FileUtils.cd(Rails.root) run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test"]) diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index e7a261fa1f..66a8cf54af 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -154,8 +154,6 @@ module TestHelpers config.action_controller.allow_forgery_protection = false config.log_level = :info RUBY - - remove_from_env_config('development', 'config.file_watcher.*') end def teardown_app @@ -328,7 +326,6 @@ class ActiveSupport::TestCase include ActiveSupport::Testing::Stream self.test_order = :sorted - end # Create a scope and build a fixture rails app @@ -342,7 +339,7 @@ Module.new do environment = File.expand_path('../../../../load_paths', __FILE__) require_environment = "-r #{environment}" - `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --no-rc` + `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --skip-listen --no-rc` File.open("#{app_template_path}/config/boot.rb", 'w') do |f| f.puts "require '#{environment}'" f.puts "require 'rails/all'" diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb index 7dad2b7779..0d64b48550 100644 --- a/railties/test/test_unit/reporter_test.rb +++ b/railties/test/test_unit/reporter_test.rb @@ -104,11 +104,22 @@ class TestUnitReporterTest < ActiveSupport::TestCase end end - test "fail fast does not interrupt run errors or skips" do + test "fail fast interrupts run on error" do fail_fast = Rails::TestUnitReporter.new @output, fail_fast: true + interrupt_raised = false - fail_fast.record(errored_test) - assert_no_match 'Failed tests:', @output.string + # Minitest passes through Interrupt, catch it manually. + begin + fail_fast.record(errored_test) + rescue Interrupt + interrupt_raised = true + ensure + assert interrupt_raised, 'Expected Interrupt to be raised.' + end + end + + test "fail fast does not interrupt run skips" do + fail_fast = Rails::TestUnitReporter.new @output, fail_fast: true fail_fast.record(skipped_test) assert_no_match 'Failed tests:', @output.string |