diff options
68 files changed, 464 insertions, 238 deletions
@@ -61,7 +61,7 @@ gem "bootsnap", ">= 1.1.0", require: false # Active Job. group :job do gem "resque", require: false - gem "resque-scheduler", require: false + gem "resque-scheduler", github: "jeremy/resque-scheduler", branch: "redis-rb-4.0", require: false gem "sidekiq", require: false gem "sucker_punch", require: false gem "delayed_job", require: false @@ -82,7 +82,10 @@ group :cable do gem "em-hiredis", require: false gem "hiredis", require: false - gem "redis", require: false + gem "redis", "~> 4.0", require: false + + # For Redis 4.0 support. Unreleased 9cb81bf. + gem "redis-namespace", github: "resque/redis-namespace" gem "websocket-client-simple", github: "matthewd/websocket-client-simple", branch: "close-race", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 938b4a71cc..9c3a7e3c82 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,6 +7,17 @@ GIT pg (>= 0.17, < 0.20) GIT + remote: https://github.com/jeremy/resque-scheduler.git + revision: 7cccf7d8db4dbbf54bf549a98b6cbb38106dbed2 + branch: redis-rb-4.0 + specs: + resque-scheduler (4.3.0) + mono_logger (~> 1.0) + redis (>= 3.3, < 5) + resque (~> 1.26) + rufus-scheduler (~> 3.2) + +GIT remote: https://github.com/matthewd/rb-inotify.git revision: 856730aad4b285969e8dd621e44808a7c5af4242 branch: close-handling @@ -42,6 +53,13 @@ GIT tilt (>= 1.1, < 3) GIT + remote: https://github.com/resque/redis-namespace.git + revision: 1403f511f6ae1ec9a8f330298a4cacf73fb10afc + specs: + redis-namespace (1.5.3) + redis (>= 3.0.4) + +GIT remote: https://github.com/robin850/sdoc.git revision: 0e340352f3ab2f196c8a8743f83c2ee286e4f71c branch: upgrade @@ -361,9 +379,7 @@ GEM rb-fsevent (0.10.2) rdoc (5.1.0) redcarpet (3.2.3) - redis (3.3.3) - redis-namespace (1.5.3) - redis (~> 3.0, >= 3.0.4) + redis (4.0.1) representable (3.0.4) declarative (< 0.1.0) declarative-option (< 0.2.0) @@ -374,11 +390,6 @@ GEM redis-namespace (~> 1.3) sinatra (>= 0.9.2) vegas (~> 0.1.2) - resque-scheduler (4.3.0) - mono_logger (~> 1.0) - redis (~> 3.3) - resque (~> 1.26) - rufus-scheduler (~> 3.2) retriable (3.1.1) rubocop (0.49.1) parallel (~> 1.10) @@ -403,11 +414,11 @@ GEM sequel (4.49.0) serverengine (1.5.11) sigdump (~> 0.2.2) - sidekiq (5.0.4) + sidekiq (5.0.5) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) - redis (~> 3.3, >= 3.3.3) + redis (>= 3.3.4, < 5) sigdump (0.2.4) signet (0.7.3) addressable (~> 2.3) @@ -520,9 +531,10 @@ DEPENDENCIES rake (>= 11.1) rb-inotify! redcarpet (~> 3.2.3) - redis + redis (~> 4.0) + redis-namespace! resque - resque-scheduler + resque-scheduler! rubocop (>= 0.47) sass-rails! sdoc! diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index 560ee89846..3952887b61 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,7 @@ +* Support redis-rb 4.0. + + *Jeremy Daer* + * Hash long stream identifiers when using PostgreSQL adapter. PostgreSQL has a limit on identifiers length (63 chars, [docs](https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS)). diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb index faafd6d0a6..bb8d64e27a 100644 --- a/actioncable/lib/action_cable/connection/subscriptions.rb +++ b/actioncable/lib/action_cable/connection/subscriptions.rb @@ -43,7 +43,7 @@ module ActionCable def remove(data) logger.info "Unsubscribing from channel: #{data['identifier']}" - remove_subscription subscriptions[data["identifier"]] + remove_subscription find(data) end def remove_subscription(subscription) diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb index 82fed81a18..26209537df 100644 --- a/actioncable/lib/action_cable/server/configuration.rb +++ b/actioncable/lib/action_cable/server/configuration.rb @@ -25,13 +25,26 @@ module ActionCable # Also makes sure proper dependencies are required. def pubsub_adapter adapter = (cable.fetch("adapter") { "redis" }) + + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems and + # 2. Adapter gems' missing dependencies. path_to_adapter = "action_cable/subscription_adapter/#{adapter}" begin require path_to_adapter - rescue Gem::LoadError => e - raise Gem::LoadError, "Specified '#{adapter}' for Action Cable pubsub adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by Action Cable)." rescue LoadError => e - raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/cable.yml is valid. If you use an adapter other than 'postgresql' or 'redis' add the necessary adapter gem to the Gemfile.", e.backtrace + # We couldn't require the adapter itself. Raise an exception that + # points out config typos and missing gems. + if e.path == path_to_adapter + # We can assume that a non-builtin adapter was specified, so it's + # either misspelled or missing from Gemfile. + raise e.class, "Could not load the '#{adapter}' Action Cable pubsub adapter. Ensure that the adapter is spelled correctly in config/cable.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise e.class, "Error loading the '#{adapter}' Action Cable pubsub adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end end adapter = adapter.camelize diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb index 07774810ce..1227c793a9 100644 --- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb @@ -3,7 +3,7 @@ require "thread" gem "em-hiredis", "~> 0.3.0" -gem "redis", "~> 3.0" +gem "redis", ">= 3", "< 5" require "em-hiredis" require "redis" diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index facea944ff..c28951608f 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -2,7 +2,7 @@ require "thread" -gem "redis", "~> 3.0" +gem "redis", ">= 3", "< 5" require "redis" module ActionCable @@ -76,7 +76,7 @@ module ActionCable def listen(conn) conn.without_reconnect do - original_client = conn.client + original_client = conn.respond_to?(:_client) ? conn._client : conn.client conn.subscribe("_action_cable_internal") do |on| on.subscribe do |chan, count| diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 16090e7946..adb86aad9f 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,11 @@ +* Add ability to enable Early Hints for HTTP/2 + + If supported by the server, and enabled in Puma this allows H2 Early Hints to be used. + + The `javascript_include_tag` and the `stylesheet_link_tag` automatically add Early Hints if requested. + + *Eileen M. Uchitelle*, *Aaron Patterson* + * Simplify cookies middleware with key rotation support Use the `rotate` method for both `MessageEncryptor` and diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index dee7be184a..5c172aecad 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -199,6 +199,23 @@ module ActionDispatch @headers ||= Http::Headers.new(self) end + # Early Hints is an HTTP/2 status code that indicates hints to help a client start + # making preparations for processing the final response. + # + # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers. + # + # The +send_early_hints+ method accepts an hash of links as follows: + # + # send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload") + # + # If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the + # Early Hints headers are included by default if supported. + def send_early_hints(links) + return unless env["rack.early_hints"] + + env["rack.early_hints"].call(links) + end + # Returns a +String+ with the last requested path including their params. # # # get '/foo' diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb index ef680cafed..d64be3b3d9 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb @@ -14,7 +14,7 @@ module ActionDispatch def method_missing(method, *args, &block) if METHODS.include?(method) - raise NoMethodError + raise NoMethodError, "System tests cannot make direct requests via ##{method}; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information." else super end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 68c6d26364..2a18395aac 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -1304,3 +1304,18 @@ class RequestFormData < BaseRequestTest assert !request.form_data? end end + +class EarlyHintsRequestTest < BaseRequestTest + def setup + super + @env["rack.early_hints"] = lambda { |links| links } + @request = stub_request + end + + test "when early hints is set in the env link headers are sent" do + early_hints = @request.send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload") + expected_hints = { "Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload" } + + assert_equal expected_hints, early_hints + end +end diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb index b60ec559ae..771a2a8d6e 100644 --- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb +++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb @@ -36,32 +36,37 @@ end class UndefMethodsTest < DrivenBySeleniumWithChrome test "get" do - assert_raise NoMethodError do + exception = assert_raise NoMethodError do get "http://example.com" end + assert_equal "System tests cannot make direct requests via #get; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message end test "post" do - assert_raise NoMethodError do + exception = assert_raise NoMethodError do post "http://example.com" end + assert_equal "System tests cannot make direct requests via #post; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message end test "put" do - assert_raise NoMethodError do + exception = assert_raise NoMethodError do put "http://example.com" end + assert_equal "System tests cannot make direct requests via #put; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message end test "patch" do - assert_raise NoMethodError do + exception = assert_raise NoMethodError do patch "http://example.com" end + assert_equal "System tests cannot make direct requests via #patch; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message end test "delete" do - assert_raise NoMethodError do + exception = assert_raise NoMethodError do delete "http://example.com" end + assert_equal "System tests cannot make direct requests via #delete; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message end end diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index bc2713d13e..10b366b030 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -37,6 +37,9 @@ module ActionView # When the Asset Pipeline is enabled, you can pass the name of your manifest as # source, and include other JavaScript or CoffeeScript files inside the manifest. # + # If the server supports Early Hints header links for these assets will be + # automatically pushed. + # # ==== Options # # When the last parameter is a hash you can add HTML attributes using that @@ -77,12 +80,20 @@ module ActionView def javascript_include_tag(*sources) options = sources.extract_options!.stringify_keys path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys - sources.uniq.map { |source| + early_hints_links = [] + + sources_tags = sources.uniq.map { |source| + href = path_to_javascript(source, path_options) + early_hints_links << "<#{href}>; rel=preload; as=script" tag_options = { - "src" => path_to_javascript(source, path_options) + "src" => href }.merge!(options) content_tag("script".freeze, "", tag_options) }.join("\n").html_safe + + request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) + + sources_tags end # Returns a stylesheet link tag for the sources specified as arguments. If @@ -92,6 +103,9 @@ module ActionView # to "screen", so you must explicitly set it to "all" for the stylesheet(s) to # apply to all media types. # + # If the server supports Early Hints header links for these assets will be + # automatically pushed. + # # stylesheet_link_tag "style" # # => <link href="/assets/style.css" media="screen" rel="stylesheet" /> # @@ -113,14 +127,22 @@ module ActionView def stylesheet_link_tag(*sources) options = sources.extract_options!.stringify_keys path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys - sources.uniq.map { |source| + early_hints_links = [] + + sources_tags = sources.uniq.map { |source| + href = path_to_stylesheet(source, path_options) + early_hints_links << "<#{href}>; rel=preload; as=stylesheet" tag_options = { "rel" => "stylesheet", "media" => "screen", - "href" => path_to_stylesheet(source, path_options) + "href" => href }.merge!(options) tag(:link, tag_options) }.join("\n").html_safe + + request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) + + sources_tags end # Returns a link tag that browsers and feed readers can use to auto-detect diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 31a1f8be8c..8a08e49e2f 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -394,7 +394,7 @@ module ActionView # # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" /> # # radio_button_tag 'time_slot', "3:00 p.m.", false, disabled: true - # # => <input disabled="disabled" id="time_slot_300_pm" name="time_slot" type="radio" value="3:00 p.m." /> + # # => <input disabled="disabled" id="time_slot_3:00_p.m." name="time_slot" type="radio" value="3:00 p.m." /> # # radio_button_tag 'color', "green", true, class: "color_input" # # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" /> diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 182d8f89f7..7475f5cc3c 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -19,6 +19,7 @@ class AssetTagHelperTest < ActionView::TestCase def ssl?() false end def host_with_port() "localhost" end def base_url() "http://www.example.com" end + def send_early_hints(links) end end.new @controller.request = @request @@ -653,7 +654,9 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase @controller = BasicController.new @controller.config.relative_url_root = "/collaboration/hieraki" - @request = Struct.new(:protocol, :base_url).new("gopher://", "gopher://www.example.com") + @request = Struct.new(:protocol, :base_url) do + def send_early_hints(links); end + end.new("gopher://", "gopher://www.example.com") @controller.request = @request end diff --git a/actionview/test/template/javascript_helper_test.rb b/actionview/test/template/javascript_helper_test.rb index 4478c9f4ab..a72bc6c2fe 100644 --- a/actionview/test/template/javascript_helper_test.rb +++ b/actionview/test/template/javascript_helper_test.rb @@ -6,11 +6,15 @@ class JavaScriptHelperTest < ActionView::TestCase tests ActionView::Helpers::JavaScriptHelper attr_accessor :output_buffer + attr_reader :request setup do @old_escape_html_entities_in_json = ActiveSupport.escape_html_entities_in_json ActiveSupport.escape_html_entities_in_json = true @template = self + @request = Class.new do + def send_early_hints(links) end + end.new end def teardown diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 77dfdefc05..425ab0c789 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,7 @@ +* Support redis-rb 4.0. + + *Jeremy Daer* + * Change logging instrumentation to log errors when a job raises an exception. Fixes #26848. diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb index 484b476567..7d5174b957 100644 --- a/activejob/test/support/integration/adapters/resque.rb +++ b/activejob/test/support/integration/adapters/resque.rb @@ -3,7 +3,7 @@ module ResqueJobsManager def setup ActiveJob::Base.queue_adapter = :resque - Resque.redis = Redis::Namespace.new "active_jobs_int_test", redis: Redis.connect(url: "redis://:password@127.0.0.1:6379/12", thread_safe: true) + Resque.redis = Redis::Namespace.new "active_jobs_int_test", redis: Redis.new(url: "redis://:password@127.0.0.1:6379/12", thread_safe: true) Resque.logger = Rails.logger unless can_run? puts "Cannot run integration tests for resque. To be able to run integration tests for resque you need to install and start redis.\n" @@ -41,11 +41,8 @@ module ResqueJobsManager end def can_run? - begin - Resque.redis.client.connect - rescue - return false - end - true + Resque.redis.ping == "PONG" + rescue + false end end diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 096f016976..a8d8ee268a 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -6,22 +6,16 @@ module ActiveRecord module Associations # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency class AliasTracker # :nodoc: - def self.create(connection, initial_table) - aliases = Hash.new(0) - aliases[initial_table] = 1 - new(connection, aliases) - end - - def self.create_with_joins(connection, initial_table, joins) + def self.create(connection, initial_table, joins) if joins.empty? - create(connection, initial_table) + aliases = Hash.new(0) else aliases = Hash.new { |h, k| h[k] = initial_count_for(connection, k, joins) } - aliases[initial_table] = 1 - new(connection, aliases) end + aliases[initial_table] = 1 + new(connection, aliases) end def self.initial_count_for(connection, name, table_joins) @@ -36,6 +30,8 @@ module ActiveRecord ).size elsif join.respond_to? :left join.left.table_name == name ? 1 : 0 + elsif join.is_a?(Hash) + join[name] else # this branch is reached by two tests: # @@ -79,10 +75,7 @@ module ActiveRecord end end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - attr_reader :aliases + attr_reader :aliases private diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 6118cef913..11967e0571 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -23,8 +23,7 @@ module ActiveRecord reflection = association.reflection scope = klass.unscoped owner = association.owner - alias_tracker = AliasTracker.create(klass.connection, scope.table.name) - chain = get_chain(reflection, association, alias_tracker) + chain = get_chain(reflection, association, scope.alias_tracker) scope.extending! reflection.extensions add_constraints(scope, owner, chain) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index ceedf150e3..ed215fb22c 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -181,8 +181,6 @@ module ActiveRecord # are actually removed from the database, that depends precisely on # +delete_records+. They are in any case removed from the collection. def delete(*records) - return if records.empty? - records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } delete_or_destroy(records, options[:dependent]) end @@ -192,8 +190,6 @@ module ActiveRecord # Note that this method removes records from the database ignoring the # +:dependent+ option. def destroy(*records) - return if records.empty? - records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } delete_or_destroy(records, :destroy) end @@ -376,6 +372,8 @@ module ActiveRecord end def delete_or_destroy(records, method) + return if records.empty? + records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } records = records.flatten records.each { |record| raise_on_type_mismatch!(record) } existing_records = records.reject(&:new_record?) diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 23741b2f6a..df4bf07999 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -43,8 +43,6 @@ module ActiveRecord Column = Struct.new(:name, :alias) end - attr_reader :alias_tracker, :base_klass, :join_root - def self.make_tree(associations) hash = {} walk_tree associations, hash @@ -90,8 +88,8 @@ module ActiveRecord # associations # => [:appointments] # joins # => [] # - def initialize(base, table, associations, joins, eager_loading: true) - @alias_tracker = AliasTracker.create_with_joins(base.connection, table.name, joins) + def initialize(base, table, associations, alias_tracker, eager_loading: true) + @alias_tracker = alias_tracker @eager_loading = eager_loading tree = self.class.make_tree associations @join_root = JoinBase.new(base, table, build(tree, base)) @@ -158,6 +156,9 @@ module ActiveRecord parents.values end + protected + attr_reader :alias_tracker, :base_klass, :join_root + private def make_constraints(parent, child, tables, join_type) @@ -224,7 +225,7 @@ module ActiveRecord raise EagerLoadPolymorphicError.new(reflection) end - JoinAssociation.new reflection, build(right, reflection.klass) + JoinAssociation.new(reflection, build(right, reflection.klass), alias_tracker) end.compact end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index a526468bf6..a007d0cf5f 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -11,11 +11,12 @@ module ActiveRecord attr_accessor :tables - def initialize(reflection, children) + def initialize(reflection, children, alias_tracker) super(reflection.klass, children) - @reflection = reflection - @tables = nil + @alias_tracker = alias_tracker + @reflection = reflection + @tables = nil end def match?(other) @@ -38,11 +39,12 @@ module ActiveRecord joins << table.create_join(table, table.create_on(constraint), join_type) join_scope = reflection.join_scope(table, foreign_klass) + arel = join_scope.arel(alias_tracker.aliases) - if join_scope.arel.constraints.any? - joins.concat join_scope.arel.join_sources + if arel.constraints.any? + joins.concat arel.join_sources right = joins.last.right - right.expr = right.expr.and(join_scope.arel.constraints) + right.expr = right.expr.and(arel.constraints) end # The current table in this iteration becomes the foreign table in the next @@ -55,6 +57,9 @@ module ActiveRecord def table tables.first end + + protected + attr_reader :alias_tracker end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 8c889f98f5..6859feb2f3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -478,6 +478,8 @@ module ActiveRecord m.alias_type %r(number)i, "decimal" m.alias_type %r(double)i, "float" + m.register_type %r(^json)i, Type::Json.new + m.register_type(%r(decimal)i) do |sql_type| scale = extract_scale(sql_type) precision = extract_precision(sql_type) 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 ae991d3d79..add6f8632b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -563,7 +563,6 @@ module ActiveRecord m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) m.register_type %r(^float)i, Type::Float.new(limit: 24) m.register_type %r(^double)i, Type::Float.new(limit: 53) - m.register_type %r(^json)i, Type::Json.new register_integer_type m, %r(^bigint)i, limit: 8 register_integer_type m, %r(^int)i, limit: 4 diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 29542f917e..508132accb 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -183,13 +183,25 @@ module ActiveRecord raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems and + # 2. Adapter gems' missing dependencies. path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter" begin require path_to_adapter - rescue Gem::LoadError => e - raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord)." rescue LoadError => e - raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace + # We couldn't require the adapter itself. Raise an exception that + # points out config typos and missing gems. + if e.path == path_to_adapter + # We can assume that a non-builtin adapter was specified, so it's + # either misspelled or missing from Gemfile. + raise e.class, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise e.class, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end end adapter_method = "#{spec[:adapter]}_connection" diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 502cef2e20..2b247f7a7a 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -52,11 +52,8 @@ module ActiveRecord end if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -65,11 +62,8 @@ module ActiveRecord def change_table(table_name, options = {}) if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -80,11 +74,8 @@ module ActiveRecord column_options.reverse_merge!(type: :integer) if block_given? - super(table_1, table_2, column_options: column_options, **options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -103,6 +94,14 @@ module ActiveRecord super(table_name, ref_name, type: :integer, **options) end alias :add_belongs_to :add_reference + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + t + end end class V4_2 < V5_0 @@ -121,11 +120,8 @@ module ActiveRecord def create_table(table_name, options = {}) if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -134,11 +130,8 @@ module ActiveRecord def change_table(table_name, options = {}) if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -174,6 +167,12 @@ module ActiveRecord end private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + t + end def index_name_for_remove(table_name, options = {}) index_name = index_name(table_name, options) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 3517091a6e..997cfe4b5e 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -551,6 +551,11 @@ module ActiveRecord limit_value || offset_value end + def alias_tracker(joins = [], aliases = nil) # :nodoc: + joins += [aliases] if aliases + ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins) + end + protected def load_records(records) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 0889d61c92..4ef0502893 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -130,7 +130,7 @@ module ActiveRecord # end def calculate(operation, column_name) if has_include?(column_name) - relation = construct_relation_for_association_calculations + relation = apply_join_dependency(construct_join_dependency) relation.distinct! if operation.to_s.downcase == "count" relation.calculate(operation, column_name) @@ -180,7 +180,8 @@ module ActiveRecord end if has_include?(column_names.first) - construct_relation_for_association_calculations.pluck(*column_names) + relation = apply_join_dependency(construct_join_dependency) + relation.pluck(*column_names) else relation = spawn relation.select_values = column_names.map { |cn| diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c92d5a52f4..707245bab2 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -310,12 +310,12 @@ module ActiveRecord return false if !conditions || limit_value == 0 - relation = self unless eager_loading? - relation ||= apply_join_dependency(self, construct_join_dependency(eager_loading: false)) - - return false if ActiveRecord::NullRelation === relation + if eager_loading? + relation = apply_join_dependency(construct_join_dependency(eager_loading: false)) + return relation.exists?(conditions) + end - relation = construct_relation_for_exists(relation, conditions) + relation = construct_relation_for_exists(conditions) skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false rescue ::RangeError @@ -366,17 +366,16 @@ module ActiveRecord # preexisting join in joins_values to categorizations (by way of # the `has_many :through` for categories). # - join_dependency = construct_join_dependency(joins_values) + join_dependency = construct_join_dependency - aliases = join_dependency.aliases - relation = select aliases.columns - relation = apply_join_dependency(relation, join_dependency) + relation = apply_join_dependency(join_dependency) + relation._select!(join_dependency.aliases.columns) yield relation, join_dependency end - def construct_relation_for_exists(relation, conditions) - relation = relation.except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) + def construct_relation_for_exists(conditions) + relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) case conditions when Array, Hash @@ -388,17 +387,15 @@ module ActiveRecord relation end - def construct_join_dependency(joins = [], eager_loading: true) + def construct_join_dependency(eager_loading: true) including = eager_load_values + includes_values - ActiveRecord::Associations::JoinDependency.new(klass, table, including, joins, eager_loading: eager_loading) - end - - def construct_relation_for_association_calculations - apply_join_dependency(self, construct_join_dependency(joins_values)) + ActiveRecord::Associations::JoinDependency.new( + klass, table, including, alias_tracker(joins_values), eager_loading: eager_loading + ) end - def apply_join_dependency(relation, join_dependency) - relation = relation.except(:includes, :eager_load, :preload).joins!(join_dependency) + def apply_join_dependency(join_dependency) + relation = except(:includes, :eager_load, :preload).joins!(join_dependency) if using_limitable_reflections?(join_dependency.reflections) relation diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 03824ffff9..ebc72d28fd 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -122,7 +122,7 @@ module ActiveRecord end join_dependency = ActiveRecord::Associations::JoinDependency.new( - other.klass, other.table, joins_dependency, [] + other.klass, other.table, joins_dependency, other.alias_tracker ) relation.joins! rest diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index c88603fde2..578d8a6eb7 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -441,7 +441,7 @@ module ActiveRecord # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" # def left_outer_joins(*args) - check_if_method_has_arguments!(:left_outer_joins, args) + check_if_method_has_arguments!(__callee__, args) args.compact! args.flatten! @@ -898,8 +898,8 @@ module ActiveRecord end # Returns the Arel object associated with the relation. - def arel # :nodoc: - @arel ||= build_arel + def arel(aliases = nil) # :nodoc: + @arel ||= build_arel(aliases) end protected @@ -921,11 +921,11 @@ module ActiveRecord raise ImmutableRelation if defined?(@arel) && @arel end - def build_arel + def build_arel(aliases) arel = Arel::SelectManager.new(table) - build_joins(arel, joins_values.flatten) unless joins_values.empty? - build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty? + build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty? + build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty? arel.where(where_clause.ast) unless where_clause.empty? arel.having(having_clause.ast) unless having_clause.empty? @@ -970,7 +970,7 @@ module ActiveRecord end end - def build_left_outer_joins(manager, outer_joins) + def build_left_outer_joins(manager, outer_joins, aliases) buckets = outer_joins.group_by do |join| case join when Hash, Symbol, Array @@ -980,10 +980,10 @@ module ActiveRecord end end - build_join_query(manager, buckets, Arel::Nodes::OuterJoin) + build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases) end - def build_joins(manager, joins) + def build_joins(manager, joins, aliases) buckets = joins.group_by do |join| case join when String @@ -999,10 +999,10 @@ module ActiveRecord end end - build_join_query(manager, buckets, Arel::Nodes::InnerJoin) + build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases) end - def build_join_query(manager, buckets, join_type) + def build_join_query(manager, buckets, join_type, aliases) buckets.default = [] association_joins = buckets[:association_join] @@ -1013,7 +1013,7 @@ module ActiveRecord join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( - klass, table, association_joins, join_list + klass, table, association_joins, alias_tracker(join_list, aliases) ) joins = join_dependency.join_constraints(stashed_association_joins, join_type) diff --git a/activerecord/test/cases/adapters/sqlite3/json_test.rb b/activerecord/test/cases/adapters/sqlite3/json_test.rb new file mode 100644 index 0000000000..568a524058 --- /dev/null +++ b/activerecord/test/cases/adapters/sqlite3/json_test.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "cases/helper" +require "cases/json_shared_test_cases" + +class SQLite3JSONTest < ActiveRecord::SQLite3TestCase + include JSONSharedTestCases + + def setup + super + @connection.create_table("json_data_type") do |t| + t.column "payload", :json, default: {} + t.column "settings", :json + end + end + + def test_default + @connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] } + klass.reset_column_information + + assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.column_defaults["permissions"]) + assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.new.permissions) + end + + private + def column_type + :json + end +end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 521b388cee..c6a4ac356f 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1250,6 +1250,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase TenantMembership.current_member = nil end + def test_has_many_trough_with_scope_that_has_joined_same_table_with_parent_relation + assert_equal authors(:david), Author.joins(:comments_for_first_author).take + end + def test_has_many_through_with_unscope_should_affect_to_through_scope assert_equal [comments(:eager_other_comment1)], authors(:mary).unordered_comments end diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 3fa0ca8366..5b80f16a44 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -19,7 +19,7 @@ module ActiveRecord spec "ridiculous://foo?encoding=utf8" end - assert_match "Could not load 'active_record/connection_adapters/ridiculous_adapter'", error.message + assert_match "Could not load the 'ridiculous' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", error.message end # The abstract adapter is used simply to bypass the bit of code that diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 55496147c1..d8bc917e7f 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -9,6 +9,7 @@ require "models/company" require "models/tagging" require "models/topic" require "models/reply" +require "models/rating" require "models/entrant" require "models/project" require "models/developer" @@ -156,6 +157,32 @@ class FinderTest < ActiveRecord::TestCase assert_raise(NoMethodError) { Topic.exists?([1, 2]) } end + def test_exists_with_scope + davids = Author.where(name: "David") + assert_equal true, davids.exists? + assert_equal true, davids.exists?(authors(:david).id) + assert_equal false, davids.exists?(authors(:mary).id) + assert_equal false, davids.exists?("42") + assert_equal false, davids.exists?(42) + assert_equal false, davids.exists?(davids.new.id) + + fake = Author.where(name: "fake author") + assert_equal false, fake.exists? + assert_equal false, fake.exists?(authors(:david).id) + end + + def test_exists_uses_existing_scope + post = authors(:david).posts.first + authors = Author.includes(:posts).where(name: "David", posts: { id: post.id }) + assert_equal true, authors.exists?(authors(:david).id) + end + + def test_any_with_scope_on_hash_includes + post = authors(:david).posts.first + categories = Categorization.includes(author: :posts).where(posts: { id: post.id }) + assert_equal true, categories.exists? + end + def test_exists_with_polymorphic_relation post = Post.create!(title: "Post", body: "default", taggings: [Tagging.new(comment: "tagging comment")]) relation = Post.tagged_with_comment("tagging comment") @@ -244,6 +271,13 @@ class FinderTest < ActiveRecord::TestCase assert_equal true, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(1).exists? end + def test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association + assert_nothing_raised do + developer = developers(:david) + developer.ratings.includes(comment: :post).where(posts: { id: 1 }).exists? + end + end + def test_exists_with_empty_table_and_no_args_given Topic.delete_all assert_equal false, Topic.exists? diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb index 56ec8c8a82..a71485982c 100644 --- a/activerecord/test/cases/json_shared_test_cases.rb +++ b/activerecord/test/cases/json_shared_test_cases.rb @@ -30,6 +30,7 @@ module JSONSharedTestCases end def test_change_table_supports_json + skip unless @connection.supports_json? @connection.change_table("json_data_type") do |t| t.public_send column_type, "users" end @@ -40,6 +41,7 @@ module JSONSharedTestCases end def test_schema_dumping + skip unless @connection.supports_json? output = dump_table_schema("json_data_type") assert_match(/t\.#{column_type}\s+"settings"/, output) end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index e1788be351..72433d1e8e 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -845,32 +845,6 @@ class RelationTest < ActiveRecord::TestCase } end - def test_exists - davids = Author.where(name: "David") - assert davids.exists? - assert davids.exists?(authors(:david).id) - assert ! davids.exists?(authors(:mary).id) - assert ! davids.exists?("42") - assert ! davids.exists?(42) - assert ! davids.exists?(davids.new.id) - - fake = Author.where(name: "fake author") - assert ! fake.exists? - assert ! fake.exists?(authors(:david).id) - end - - def test_exists_uses_existing_scope - post = authors(:david).posts.first - authors = Author.includes(:posts).where(name: "David", posts: { id: post.id }) - assert authors.exists?(authors(:david).id) - end - - def test_any_with_scope_on_hash_includes - post = authors(:david).posts.first - categories = Categorization.includes(author: :posts).where(posts: { id: post.id }) - assert categories.exists? - end - def test_last authors = Author.all assert_equal authors(:bob), authors.last diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 3371fcbfcc..e9eba9be2e 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -22,6 +22,7 @@ class Author < ActiveRecord::Base has_many :comments_containing_the_letter_e, through: :posts, source: :comments has_many :comments_with_order_and_conditions, -> { order("comments.body").where("comments.body like 'Thank%'") }, through: :posts, source: :comments has_many :comments_with_include, -> { includes(:post).where(posts: { type: "Post" }) }, through: :posts, source: :comments + has_many :comments_for_first_author, -> { for_first_author }, through: :posts, source: :comments has_many :first_posts has_many :comments_on_first_posts, -> { order("posts.id desc, comments.id asc") }, through: :first_posts, source: :comments diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb index 54685b4c0e..90a3605331 100644 --- a/activestorage/app/models/active_storage/variant.rb +++ b/activestorage/app/models/active_storage/variant.rb @@ -50,7 +50,7 @@ class ActiveStorage::Variant # Returns a combination key of the blob and the variation that together identifies a specific variant. def key - "variants/#{blob.key}/#{variation.key}" + "variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}" end # Returns the URL of the variant on the service. This URL is intended to be short-lived for security and not used directly diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb index d7cbef4e55..b7d20ab55a 100644 --- a/activestorage/test/models/variant_test.rb +++ b/activestorage/test/models/variant_test.rb @@ -26,4 +26,9 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase assert_equal 67, image.height assert_match(/Gray/, image.colorspace) end + + test "service_url doesn't grow in length despite long variant options" do + variant = @blob.variant(font: "a" * 10_000).processed + assert_operator variant.service_url.length, :<, 500 + end end diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 28df2c066e..f98d343ccd 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -39,9 +39,8 @@ module ActiveSupport def cleanup(options = nil) options = merged_options(options) search_dir(cache_path) do |fname| - key = file_path_key(fname) - entry = read_entry(key, options) - delete_entry(key, options) if entry && entry.expired? + entry = read_entry(fname, options) + delete_entry(fname, options) if entry && entry.expired? end end diff --git a/activesupport/test/cache/stores/file_store_test.rb b/activesupport/test/cache/stores/file_store_test.rb index 391ab60b3a..66231b0a82 100644 --- a/activesupport/test/cache/stores/file_store_test.rb +++ b/activesupport/test/cache/stores/file_store_test.rb @@ -118,6 +118,7 @@ class FileStoreTest < ActiveSupport::TestCase assert_not @cache.exist?("foo") assert @cache.exist?("baz") assert @cache.exist?("quux") + assert_equal 2, Dir.glob(File.join(cache_dir, "**")).size end end diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 3f5a3c7ade..1020f4a8e7 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -231,7 +231,7 @@ Rails chooses between file, template, and action depending on whether there is a ### Application Controller Renamed -If you're one of the people who has always been bothered by the special-case naming of `application.rb`, rejoice! It's been reworked to be application_controller.rb in Rails 2.3. In addition, there's a new rake task, `rake rails:update:application_controller` to do this automatically for you - and it will be run as part of the normal `rake rails:update` process. +If you're one of the people who has always been bothered by the special-case naming of `application.rb`, rejoice! It's been reworked to be `application_controller.rb` in Rails 2.3. In addition, there's a new rake task, `rake rails:update:application_controller` to do this automatically for you - and it will be run as part of the normal `rake rails:update` process. * More Information: * [The Death of Application.rb](http://afreshcup.com/2008/11/17/rails-2x-the-death-of-applicationrb/) @@ -304,7 +304,7 @@ Rails now keeps a per-request local cache of read from the remote cache stores, Rails can now provide localized views, depending on the locale that you have set. For example, suppose you have a `Posts` controller with a `show` action. By default, this will render `app/views/posts/show.html.erb`. But if you set `I18n.locale = :da`, it will render `app/views/posts/show.da.html.erb`. If the localized template isn't present, the undecorated version will be used. Rails also includes `I18n#available_locales` and `I18n::SimpleBackend#available_locales`, which return an array of the translations that are available in the current Rails project. -In addition, you can use the same scheme to localize the rescue files in the `public` directory: `public/500.da.html` or `public/404.en.html` work, for example. +In addition, you can use the same scheme to localize the rescue files in the public directory: `public/500.da.html` or `public/404.en.html` work, for example. ### Partial Scoping for Translations diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md index 6570b19f97..f6571544f9 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -30,7 +30,7 @@ TIP: Note that Ruby 1.8.7 p248 and p249 have marshalling bugs that crash Rails. ### What to update in your apps -* Update your Gemfile to depend on +* Update your `Gemfile` to depend on * `rails = 3.2.0` * `sass-rails ~> 3.2.3` * `coffee-rails ~> 3.2.1` diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 6f1b75a42b..0921cd1979 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -66,7 +66,7 @@ Major Features * **ActiveRecord session store** ([commit](https://github.com/rails/rails/commit/0ffe19056c8e8b2f9ae9d487b896cad2ce9387ad)) - The ActiveRecord session store is extracted to a separate gem. Storing sessions in SQL is costly. Instead, use cookie sessions, memcache sessions, or a custom session store. * **ActiveModel mass assignment protection** ([commit](https://github.com/rails/rails/commit/f8c9a4d3e88181cee644f91e1342bfe896ca64c6)) - Rails 3 mass assignment protection is deprecated. Instead, use strong parameters. * **ActiveResource** ([commit](https://github.com/rails/rails/commit/f1637bf2bb00490203503fbd943b73406e043d1d)) - ActiveResource is extracted to a separate gem. ActiveResource was not widely used. -* **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a Gemfile to manage installed gems. +* **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a `Gemfile` to manage installed gems. ### ActionPack diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index a30bfc458a..036a310ac8 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -179,7 +179,7 @@ change your code to use the explicit form (`render file: "foo/bar"`) instead. `respond_with` and the corresponding class-level `respond_to` have been moved to the [responders](https://github.com/plataformatec/responders) gem. Add -`gem 'responders', '~> 2.0'` to your Gemfile to use it: +`gem 'responders', '~> 2.0'` to your `Gemfile` to use it: ```ruby # app/controllers/users_controller.rb diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 3805fd2a63..656838c6b8 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -775,7 +775,7 @@ Please refer to the [Changelog][active-record] for detailed changes. * Added prepared statements support to `mysql2` adapter, for mysql2 0.4.4+, Previously this was only supported on the deprecated `mysql` legacy adapter. - To enable, set `prepared_statements: true` in config/database.yml. + To enable, set `prepared_statements: true` in `config/database.yml`. ([Pull Request](https://github.com/rails/rails/pull/23461)) * Added ability to call `ActionRecord::Relation#update` on relation objects diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index 8afec00018..6959f992aa 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -16,6 +16,7 @@ <% end %> <p> The guides for earlier releases: +<a href="http://guides.rubyonrails.org/v5.1/">Rails 5.1</a>, <a href="http://guides.rubyonrails.org/v5.0/">Rails 5.0</a>, <a href="http://guides.rubyonrails.org/v4.2/">Rails 4.2</a>, <a href="http://guides.rubyonrails.org/v4.1/">Rails 4.1</a>, diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index 31151e0329..dd16ba3932 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -557,7 +557,7 @@ The async adapter is intended for development/testing and should not be used in Action Cable contains two Redis adapters: "normal" Redis and Evented Redis. Both of the adapters require users to provide a URL pointing to the Redis server. -Additionally, a channel_prefix may be provided to avoid channel name collisions +Additionally, a `channel_prefix` may be provided to avoid channel name collisions when using the same Redis server for multiple applications. See the [Redis PubSub documentation](https://redis.io/topics/pubsub#database-amp-scoping) for more details. ##### PostgreSQL Adapter diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 5fb8e300de..d53c4dedf9 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -784,9 +784,9 @@ The way this is done is to add a non-guessable token which is only known to your If you generate a form like this: ```erb -<%= form_for @user do |f| %> - <%= f.text_field :username %> - <%= f.text_field :password %> +<%= form_with model: @user, local: true do |form| %> + <%= form.text_field :username %> + <%= form.text_field :password %> <% end %> ``` @@ -1116,7 +1116,7 @@ Rails default exception handling displays a "500 Server Error" message for all e ### The Default 500 and 404 Templates -By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them. +By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the public folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them. ### `rescue_from` diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index a57623428f..349108c207 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -490,7 +490,7 @@ stylesheet_link_tag "application" # => <link href="/assets/application.css" medi #### stylesheet_path -Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path. +Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by `stylesheet_link_tag` to build the stylesheet path. ```ruby stylesheet_path "application" # => /assets/application.css diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md index b8f076a27b..ee0472621b 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -464,7 +464,7 @@ a `password` accessor with certain validations on it. #### Requirements `ActiveModel::SecurePassword` depends on [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'), -so include this gem in your Gemfile to use `ActiveModel::SecurePassword` correctly. +so include this gem in your `Gemfile` to use `ActiveModel::SecurePassword` correctly. In order to make this work, the model must have an accessor named `password_digest`. The `has_secure_password` will add the following validations on the `password` accessor: diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index ae573cc77c..067a7b7cb6 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1752,7 +1752,7 @@ The methods `to_date`, `to_time`, and `to_datetime` are basically convenience wr "2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200 ``` -Default is `:utc`. +Default is `:local`. Please refer to the documentation of `Date._parse` for further details. diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index ff4288a7f5..3b31557f41 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -450,6 +450,53 @@ Active Job | `:adapter` | QueueAdapter object processing the job | | `:job` | Job object | +Active Storage +-------------- + +### service_upload.active_storage + +| Key | Value | +| ------------ | ---------------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:checksum` | Checksum to ensure integrity | + +### service_streaming_download.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | + +### service_download.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | + +### service_delete.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | + +### service_exist.active_storage + +| Key | Value | +| ------------ | --------------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:exist` | File or blob exists or not | + +### service_url.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:url` | Generated url | Railties -------- @@ -549,4 +596,4 @@ end ``` You should follow Rails conventions when defining your own events. The format is: `event.library`. -If you application is sending Tweets, you should create an event named `tweet.twitter`. +If your application is sending Tweets, you should create an event named `tweet.twitter`. diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 8bd1f91304..805b0f0d62 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -65,7 +65,7 @@ config.assets.js_compressor = :uglifier ``` NOTE: The `sass-rails` gem is automatically used for CSS compression if included -in the Gemfile and no `config.assets.css_compressor` option is set. +in the `Gemfile` and no `config.assets.css_compressor` option is set. ### Main Features @@ -181,7 +181,7 @@ When you generate a scaffold or a controller, Rails also generates a JavaScript file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`) for that controller. Additionally, when generating a scaffold, Rails generates -the file scaffolds.css (or scaffolds.scss if `sass-rails` is in the +the file `scaffolds.css` (or `scaffolds.scss` if `sass-rails` is in the `Gemfile`.) For example, if you generate a `ProjectsController`, Rails will also add a new @@ -202,7 +202,7 @@ will result in your assets being included more than once. WARNING: When using asset precompilation, you will need to ensure that your controller assets will be precompiled when loading them on a per page basis. By -default .coffee and .scss files will not be precompiled on their own. See +default `.coffee` and `.scss` files will not be precompiled on their own. See [Precompiling Assets](#precompiling-assets) for more information on how precompiling works. @@ -726,7 +726,7 @@ include, you can add them to the `precompile` array in `config/initializers/asse Rails.application.config.assets.precompile += %w( admin.js admin.css ) ``` -NOTE. Always specify an expected compiled filename that ends with .js or .css, +NOTE. Always specify an expected compiled filename that ends with `.js` or `.css`, even if you want to add Sass or CoffeeScript files to the precompile array. The task also generates a `.sprockets-manifest-md5hash.json` (where `md5hash` is @@ -1090,7 +1090,7 @@ Possible options for JavaScript compression are `:closure`, `:uglifier` and `:yui`. These require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems, respectively. -The default Gemfile includes [uglifier](https://github.com/lautis/uglifier). +The default `Gemfile` includes [uglifier](https://github.com/lautis/uglifier). This gem wraps [UglifyJS](https://github.com/mishoo/UglifyJS) (written for NodeJS) in Ruby. It compresses your code by removing white space and comments, shortening local variable names, and performing other micro-optimizations such @@ -1219,7 +1219,7 @@ Sprockets uses Processors, Transformers, Compressors, and Exporters to extend Sprockets functionality. Have a look at [Extending Sprockets](https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md) to learn more. Here we registered a preprocessor to add a comment to the end -of text/css (.css) files. +of text/css (`.css`) files. ```ruby module AddComment diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 2cd8e02a77..88c559921c 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -659,6 +659,6 @@ development: ... ``` -It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. +It also generated some lines in our `database.yml` configuration corresponding to our choice of PostgreSQL for database. NOTE. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the `rails new` command to generate the basis of your app. diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 0f87d73d6e..7a32607eb7 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -391,7 +391,7 @@ by setting up a Rake task which runs ``` for all models and all boolean columns, after which the flag must be set to true -by adding the following to your application.rb file: +by adding the following to your `application.rb` file: ```ruby Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true @@ -572,7 +572,7 @@ Defaults to `'signed cookie'`. error should be raised for missing translations. * `config.action_view.automatically_disable_submit_tag` determines whether - submit_tag should automatically disable on click, this defaults to `true`. + `submit_tag` should automatically disable on click, this defaults to `true`. * `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `<span>` tag or not. This defaults to `true`. @@ -1317,7 +1317,7 @@ know which pages it is allowed to index. Rails creates this file for you inside the `/public` folder. By default, it allows search engines to index all pages of your application. If you want to block -indexing on all pages of you application, use this: +indexing on all pages of your application, use this: ``` User-agent: * diff --git a/guides/source/engines.md b/guides/source/engines.md index 188620a683..9f39832a7e 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -537,12 +537,12 @@ directory at `app/views/blorgh/comments` and in it a new file called ```html+erb <h3>New comment</h3> -<%= form_for [@article, @article.comments.build] do |f| %> +<%= form_with(model: [@article, @article.comments.build], local: true) do |form| %> <p> - <%= f.label :text %><br> - <%= f.text_area :text %> + <%= form.label :text %><br> + <%= form.text_area :text %> </p> - <%= f.submit %> + <%= form.submit %> <% end %> ``` @@ -653,7 +653,7 @@ there isn't an application handy to test this out in, generate one using the $ rails new unicorn ``` -Usually, specifying the engine inside the Gemfile would be done by specifying it +Usually, specifying the engine inside the `Gemfile` would be done by specifying it as a normal, everyday gem. ```ruby @@ -783,8 +783,8 @@ added above the `title` field with this code: ```html+erb <div class="field"> - <%= f.label :author_name %><br> - <%= f.text_field :author_name %> + <%= form.label :author_name %><br> + <%= form.text_field :author_name %> </div> ``` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 8965e74fe2..b007baea87 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -179,7 +179,7 @@ of the files and folders that Rails created by default: |log/|Application log files.| |package.json|This file allows you to specify what npm dependencies are needed for your Rails application. This file is used by Yarn. For more information about Yarn, see the [Yarn website](https://yarnpkg.com/lang/en/).| |public/|The only folder seen by the world as-is. Contains static files and compiled assets.| -|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the `lib/tasks` directory of your application.| +|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing `Rakefile`, you should add your own tasks by adding files to the `lib/tasks` directory of your application.| |README.md|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).| |tmp/|Temporary files (like cache and pid files).| @@ -372,16 +372,17 @@ singular form `article` and makes meaningful use of the distinction. ```bash $ bin/rails routes - Prefix Verb URI Pattern Controller#Action - articles GET /articles(.:format) articles#index - POST /articles(.:format) articles#create - new_article GET /articles/new(.:format) articles#new -edit_article GET /articles/:id/edit(.:format) articles#edit - article GET /articles/:id(.:format) articles#show - PATCH /articles/:id(.:format) articles#update - PUT /articles/:id(.:format) articles#update - DELETE /articles/:id(.:format) articles#destroy - root GET / welcome#index + Prefix Verb URI Pattern Controller#Action +welcome_index GET /welcome/index(.:format) welcome#index + articles GET /articles(.:format) articles#index + POST /articles(.:format) articles#create + new_article GET /articles/new(.:format) articles#new + edit_article GET /articles/:id/edit(.:format) articles#edit + article GET /articles/:id(.:format) articles#show + PATCH /articles/:id(.:format) articles#update + PUT /articles/:id(.:format) articles#update + DELETE /articles/:id(.:format) articles#destroy + root GET / welcome#index ``` In the next section, you will add the ability to create new articles in your @@ -567,15 +568,16 @@ To see what Rails will do with this, we look back at the output of ```bash $ bin/rails routes Prefix Verb URI Pattern Controller#Action - articles GET /articles(.:format) articles#index - POST /articles(.:format) articles#create - new_article GET /articles/new(.:format) articles#new -edit_article GET /articles/:id/edit(.:format) articles#edit - article GET /articles/:id(.:format) articles#show - PATCH /articles/:id(.:format) articles#update - PUT /articles/:id(.:format) articles#update - DELETE /articles/:id(.:format) articles#destroy - root GET / welcome#index +welcome_index GET /welcome/index(.:format) welcome#index + articles GET /articles(.:format) articles#index + POST /articles(.:format) articles#create + new_article GET /articles/new(.:format) articles#new + edit_article GET /articles/:id/edit(.:format) articles#edit + article GET /articles/:id(.:format) articles#show + PATCH /articles/:id(.:format) articles#update + PUT /articles/:id(.:format) articles#update + DELETE /articles/:id(.:format) articles#destroy + root GET / welcome#index ``` The `articles_path` helper tells Rails to point the form to the URI Pattern diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index fe2477f2ae..b9b327252f 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -232,14 +232,14 @@ You can send an HTML string back to the browser by using the `:html` option to `render`: ```ruby -render html: "<strong>Not Found</strong>".html_safe +render html: helpers.tag.strong('Not Found') ``` TIP: This is useful when you're rendering a small snippet of HTML code. However, you might want to consider moving it to a template file if the markup is complex. -NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method. +NOTE: When using `html:` option, HTML entities will be escaped if the string is not composed with `html_safe`-aware APIs. #### Rendering JSON diff --git a/guides/source/plugins.md b/guides/source/plugins.md index 5048444cb2..15073af6be 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -446,7 +446,7 @@ Publishing Your Gem ------------------- Gem plugins currently in development can easily be shared from any Git repository. To share the Yaffle gem with others, simply -commit the code to a Git repository (like GitHub) and add a line to the Gemfile of the application in question: +commit the code to a Git repository (like GitHub) and add a line to the `Gemfile` of the application in question: ```ruby gem "yaffle", git: "https://github.com/rails/yaffle.git" diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index d932fc8d8f..9bc87e4bf0 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -27,7 +27,7 @@ The process should go as follows: 3. Fix tests and deprecated features. 4. Move to the latest patch version of the next minor version. -Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the Gemfile (and possibly other gem versions) and run `bundle update`. Then run the Update task mentioned below to update configuration files, then run your tests. +Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the `Gemfile` (and possibly other gem versions) and run `bundle update`. Then run the Update task mentioned below to update configuration files, then run your tests. You can find a list of all released Rails versions [here](https://rubygems.org/gems/rails/versions). @@ -411,7 +411,7 @@ Upgrading from Rails 4.1 to Rails 4.2 ### Web Console -First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your Gemfile and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment. +First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your `Gemfile` and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment. ### Responders @@ -1136,7 +1136,7 @@ full support for the last few changes in the specification. ### Gemfile Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that -line from your Gemfile when upgrading. You should also update your application +line from your `Gemfile` when upgrading. You should also update your application file (in `config/application.rb`): ```ruby diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index ff440b7939..20603dedaf 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Gemfile for new apps: upgrade redis-rb from ~> 3.0 to 4.0. + + *Jeremy Daer* + * Add `mini_magick` to default `Gemfile` as comment. *Yoshiyuki Hirano* diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index 785265d766..5b5037d3de 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -127,6 +127,7 @@ module Rails class_option "dev-caching", aliases: "-C", type: :boolean, default: nil, desc: "Specifies whether to perform caching in development." class_option "restart", type: :boolean, default: nil, hide: true + class_option "early_hints", type: :boolean, default: nil, desc: "Enables HTTP/2 early hints." def initialize(args = [], local_options = {}, config = {}) @original_options = local_options @@ -161,7 +162,8 @@ module Rails daemonize: options[:daemon], pid: pid, caching: options["dev-caching"], - restart_cmd: restart_command + restart_cmd: restart_command, + early_hints: early_hints } end end @@ -227,6 +229,10 @@ module Rails "bin/rails server #{@server} #{@original_options.join(" ")} --restart" end + def early_hints + options[:early_hints] + end + def pid File.expand_path(options[:pid]) end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index c8688fb7f3..6fb4ea52b3 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -369,7 +369,7 @@ module Rails return [] if options[:skip_action_cable] comment = "Use Redis adapter to run Action Cable in production" gems = [] - gems << GemfileEntry.new("redis", "~> 3.0", comment, {}, true) + gems << GemfileEntry.new("redis", "~> 4.0", comment, {}, true) gems end diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index 556c2289e7..a6201e4f04 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -81,6 +81,18 @@ class Rails::ServerTest < ActiveSupport::TestCase assert_equal false, options[:caching] end + def test_early_hints_with_option + args = ["--early-hints"] + options = parse_arguments(args) + assert_equal true, options[:early_hints] + end + + def test_early_hints_is_nil_by_default + args = [] + options = parse_arguments(args) + assert_nil options[:early_hints] + end + def test_log_stdout with_rack_env nil do with_rails_env nil do diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index 227a739b71..43b60b9144 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -2,20 +2,7 @@ require "abstract_unit" -unless defined?(Rails) && defined?(Rails::Info) - module Rails - class Info; end - end -end - -require "active_support/core_ext/kernel/reporting" - class InfoTest < ActiveSupport::TestCase - def setup - Rails.send :remove_const, :Info - silence_warnings { load "rails/info.rb" } - end - def test_property_with_block_swallows_exceptions_and_ignores_property assert_nothing_raised do Rails::Info.module_eval do |
