From acdba1c6a653bf5c787d3457af95b37708be1e2b Mon Sep 17 00:00:00 2001
From: Jack McCracken <jack.mccracken@shopify.com>
Date: Mon, 2 Oct 2017 16:35:13 -0400
Subject: Add a better error message when a "null" Origin header occurs

---
 .../action_controller/metal/request_forgery_protection.rb   | 10 ++++++++++
 .../test/controller/request_forgery_protection_test.rb      | 13 +++++++++++++
 2 files changed, 23 insertions(+)

diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index d6cd5fd9e0..b2e6f86eeb 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -414,11 +414,21 @@ module ActionController #:nodoc:
         allow_forgery_protection
       end
 
+      NULL_ORIGIN_MESSAGE = <<-MSG.strip_heredoc
+        The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually
+        means you have the 'no-referrer' Referrer-Policy header enabled, or that you the request came from a site that
+        refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the
+        best solution is to change your referrer policy to something less strict like same-origin or strict-same-origin.
+        If you cannot change the referrer policy, you can disable origin checking with the
+        Rails.application.config.action_controller.forgery_protection_origin_check setting.
+      MSG
+
       # Checks if the request originated from the same origin by looking at the
       # Origin header.
       def valid_request_origin? # :doc:
         if forgery_protection_origin_check
           # We accept blank origin headers because some user agents don't send it.
+          raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
           request.origin.nil? || request.origin == request.base_url
         else
           true
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index eb3d2f34a8..4822d85bcb 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -446,6 +446,19 @@ module RequestForgeryProtectionTests
     end
   end
 
+  def test_should_raise_for_post_with_null_origin
+    forgery_protection_origin_check do
+      session[:_csrf_token] = @token
+      @controller.stub :form_authenticity_token, @token do
+        exception = assert_raises(ActionController::InvalidAuthenticityToken) do
+          @request.set_header "HTTP_ORIGIN", "null"
+          post :index, params: { custom_authenticity_token: @token }
+        end
+        assert_match "The browser returned a 'null' origin for a request", exception.message
+      end
+    end
+  end
+
   def test_should_block_post_with_origin_checking_and_wrong_origin
     old_logger = ActionController::Base.logger
     logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
-- 
cgit v1.2.3


From 84cad15213ea5447ea0890a4f017b44ad85b901a Mon Sep 17 00:00:00 2001
From: Matthew Draper <matthew@trebex.net>
Date: Mon, 27 Nov 2017 23:39:30 +1030
Subject: Drop the before_fork/on_worker_boot advice

It's no longer required for Active Record, and other common libraries
(dalli, redis-rb) all seem to be fork-proof too.
---
 .../rails/app/templates/config/puma.rb.tt          | 24 +---------------------
 1 file changed, 1 insertion(+), 23 deletions(-)

diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
index 1e19380dcb..a5eccf816b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
@@ -26,31 +26,9 @@ environment ENV.fetch("RAILS_ENV") { "development" }
 # Use the `preload_app!` method when specifying a `workers` number.
 # This directive tells Puma to first boot the application and load code
 # before forking the application. This takes advantage of Copy On Write
-# process behavior so workers use less memory. If you use this option
-# you need to make sure to reconnect any threads in the `on_worker_boot`
-# block.
+# process behavior so workers use less memory.
 #
 # preload_app!
 
-# If you are preloading your application and using Active Record, it's
-# recommended that you close any connections to the database before workers
-# are forked to prevent connection leakage.
-#
-# before_fork do
-#   ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
-# end
-
-# The code in the `on_worker_boot` will be called if you are using
-# clustered mode by specifying a number of `workers`. After each worker
-# process is booted, this block will be run. If you are using the `preload_app!`
-# option, you will want to use this block to reconnect to any threads
-# or connections that may have been created at application boot, as Ruby
-# cannot share connections between processes.
-#
-# on_worker_boot do
-#   ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
-# end
-#
-
 # Allow puma to be restarted by `rails restart` command.
 plugin :tmp_restart
-- 
cgit v1.2.3


From 68b2573e33668a4f391c7cf1924c4183e75906e6 Mon Sep 17 00:00:00 2001
From: Michael Coyne <mikeycgto@gmail.com>
Date: Sun, 12 Nov 2017 15:19:33 -0500
Subject: Update cookie_store_test to use encrypted cookies

This now modernizes these tests to use encrypted cookies instead of
using secret_token HMACs. This commit also adds a tests to ensure
session cookies with :expires_after set are invalidated and no longer
accepted when the time has elapsed.
---
 .../test/dispatch/session/cookie_store_test.rb     | 124 ++++++++++++++-------
 1 file changed, 83 insertions(+), 41 deletions(-)

diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 2c43a8c29f..7f70961465 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -8,11 +8,13 @@ require "active_support/messages/rotation_configuration"
 class CookieStoreTest < ActionDispatch::IntegrationTest
   SessionKey = "_myapp_session"
   SessionSecret = "b3c631c314c0bbca50c1b2843150fe33"
-  Generator = ActiveSupport::LegacyKeyGenerator.new(SessionSecret)
+  SessionSalt   = "authenticated encrypted cookie"
+
+  Generator = ActiveSupport::KeyGenerator.new(SessionSecret, iterations: 1000)
   Rotations = ActiveSupport::Messages::RotationConfiguration.new
 
-  Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, digest: "SHA1")
-  SignedBar = Verifier.generate(foo: "bar", session_id: SecureRandom.hex(16))
+  Encryptor = ActiveSupport::MessageEncryptor.new \
+    Generator.generate_key(SessionSalt, 32), cipher: "aes-256-gcm", serializer: Marshal
 
   class TestController < ActionController::Base
     def no_session_access
@@ -25,12 +27,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
 
     def set_session_value
       session[:foo] = "bar"
-      render plain: Rack::Utils.escape(Verifier.generate(session.to_hash))
-    end
-
-    def set_session_value_expires_in_five_hours
-      session[:foo] = "bar"
-      render plain: Rack::Utils.escape(Verifier.generate(session.to_hash, expires_in: 5.hours))
+      render body: nil
     end
 
     def get_session_value
@@ -72,19 +69,35 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
     end
   end
 
+  def parse_cookie_from_header
+    cookie_matches = headers["Set-Cookie"].match(/#{SessionKey}=([^;]+)/)
+    cookie_matches && cookie_matches[1]
+  end
+
+  def assert_session_cookie(cookie_string, contents)
+    assert_includes headers["Set-Cookie"], cookie_string
+
+    session_value = parse_cookie_from_header
+    session_data = Encryptor.decrypt_and_verify(Rack::Utils.unescape(session_value)) rescue nil
+
+    assert_not_nil session_data, "session failed to decrypt"
+    assert_equal session_data.slice(*contents.keys), contents
+  end
+
   def test_setting_session_value
     with_test_route_set do
       get "/set_session_value"
+
       assert_response :success
-      assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
-        headers["Set-Cookie"]
+      assert_session_cookie "path=/; HttpOnly", "foo" => "bar"
     end
   end
 
   def test_getting_session_value
     with_test_route_set do
-      cookies[SessionKey] = SignedBar
+      get "/set_session_value"
       get "/get_session_value"
+
       assert_response :success
       assert_equal 'foo: "bar"', response.body
     end
@@ -92,8 +105,9 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
 
   def test_getting_session_id
     with_test_route_set do
-      cookies[SessionKey] = SignedBar
+      get "/set_session_value"
       get "/persistent_session_id"
+
       assert_response :success
       assert_equal 32, response.body.size
       session_id = response.body
@@ -106,8 +120,12 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
 
   def test_disregards_tampered_sessions
     with_test_route_set do
-      cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--123456780"
+      encryptor = ActiveSupport::MessageEncryptor.new("A" * 32, cipher: "aes-256-gcm", serializer: Marshal)
+
+      cookies[SessionKey] = encryptor.encrypt_and_sign("foo" => "bar", "session_id" => "abc")
+
       get "/get_session_value"
+
       assert_response :success
       assert_equal "foo: nil", response.body
     end
@@ -135,19 +153,19 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
   def test_does_set_secure_cookies_over_https
     with_test_route_set(secure: true) do
       get "/set_session_value", headers: { "HTTPS" => "on" }
+
       assert_response :success
-      assert_equal "_myapp_session=#{response.body}; path=/; secure; HttpOnly",
-        headers["Set-Cookie"]
+      assert_session_cookie "path=/; secure; HttpOnly", "foo" => "bar"
     end
   end
 
   # {:foo=>#<SessionAutoloadTest::Foo bar:"baz">, :session_id=>"ce8b0752a6ab7c7af3cdb8a80e6b9e46"}
-  SignedSerializedCookie = "BAh7BzoIZm9vbzodU2Vzc2lvbkF1dG9sb2FkVGVzdDo6Rm9vBjoJQGJhciIIYmF6Og9zZXNzaW9uX2lkIiVjZThiMDc1MmE2YWI3YzdhZjNjZGI4YTgwZTZiOWU0Ng==--2bf3af1ae8bd4e52b9ac2099258ace0c380e601c"
+  EncryptedSerializedCookie = "9RZ2Fij0qLveUwM4s+CCjGqhpjyUC8jiBIf/AiBr9M3TB8xh2vQZtvSOMfN3uf6oYbbpIDHAcOFIEl69FcW1ozQYeSrCLonYCazoh34ZdYskIQfGwCiSYleVXG1OD9Z4jFqeVArw4Ewm0paOOPLbN1rc6A==--I359v/KWdZ1ok0ey--JFFhuPOY7WUo6tB/eP05Aw=="
 
   def test_deserializes_unloaded_classes_on_get_id
     with_test_route_set do
       with_autoload_path "session_autoload_test" do
-        cookies[SessionKey] = SignedSerializedCookie
+        cookies[SessionKey] = EncryptedSerializedCookie
         get "/get_session_id"
         assert_response :success
         assert_equal "id: ce8b0752a6ab7c7af3cdb8a80e6b9e46", response.body, "should auto-load unloaded class"
@@ -158,7 +176,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
   def test_deserializes_unloaded_classes_on_get_value
     with_test_route_set do
       with_autoload_path "session_autoload_test" do
-        cookies[SessionKey] = SignedSerializedCookie
+        cookies[SessionKey] = EncryptedSerializedCookie
         get "/get_session_value"
         assert_response :success
         assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
@@ -197,8 +215,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
       get "/set_session_value"
       assert_response :success
       session_payload = response.body
-      assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
-        headers["Set-Cookie"]
+      assert_session_cookie "path=/; HttpOnly", "foo" => "bar"
 
       get "/call_reset_session"
       assert_response :success
@@ -216,8 +233,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
     with_test_route_set do
       get "/set_session_value"
       assert_response :success
-      assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
-        headers["Set-Cookie"]
+      assert_session_cookie "path=/; HttpOnly", "foo" => "bar"
 
       get "/get_class_after_reset_session"
       assert_response :success
@@ -239,8 +255,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
     with_test_route_set do
       get "/set_session_value"
       assert_response :success
-      assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
-        headers["Set-Cookie"]
+      assert_session_cookie "path=/; HttpOnly", "foo" => "bar"
 
       get "/call_session_clear"
       assert_response :success
@@ -253,7 +268,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
 
   def test_persistent_session_id
     with_test_route_set do
-      cookies[SessionKey] = SignedBar
+      get "/set_session_value"
       get "/persistent_session_id"
       assert_response :success
       assert_equal 32, response.body.size
@@ -268,8 +283,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
 
   def test_setting_session_id_to_nil_is_respected
     with_test_route_set do
-      cookies[SessionKey] = SignedBar
-
+      get "/set_session_value"
       get "/get_session_id"
       sid = response.body
       assert_equal 36, sid.size
@@ -283,31 +297,53 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
     with_test_route_set(expire_after: 5.hours) do
       # First request accesses the session
       time = Time.local(2008, 4, 24)
+
       Time.stub :now, time do
         expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000")
 
-        cookies[SessionKey] = SignedBar
+        get "/set_session_value"
 
-        get "/set_session_value_expires_in_five_hours"
         assert_response :success
-
-        cookie_body = response.body
-        assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly",
-          headers["Set-Cookie"]
+        assert_session_cookie "path=/; expires=#{expected_expiry}; HttpOnly", "foo" => "bar"
       end
 
       # Second request does not access the session
-      time = Time.local(2008, 4, 25)
+      time = time + 3.hours
       Time.stub :now, time do
         expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000")
 
-        cookies[SessionKey] = SignedBar
-
         get "/no_session_access"
+
         assert_response :success
+        assert_session_cookie "path=/; expires=#{expected_expiry}; HttpOnly", "foo" => "bar"
+      end
+    end
+  end
+
+  def test_session_store_with_expire_after_does_not_accept_expired_session
+    with_test_route_set(expire_after: 5.hours) do
+      # First request accesses the session
+      time = Time.local(2017, 11, 12)
+
+      Time.stub :now, time do
+        expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000")
 
-        assert_equal "_myapp_session=#{cookies[SessionKey]}; path=/; expires=#{expected_expiry}; HttpOnly",
-          headers["Set-Cookie"]
+        get "/set_session_value"
+        get "/get_session_value"
+
+        assert_response :success
+        assert_equal 'foo: "bar"', response.body
+        assert_session_cookie "path=/; expires=#{expected_expiry}; HttpOnly", "foo" => "bar"
+      end
+
+      # Second request is beyond the expiry time and the session is invalidated
+      time += 5.hours + 1.minute
+
+      Time.stub :now, time do
+        get "/get_session_value"
+
+        assert_response :success
+        assert_equal "foo: nil", response.body
       end
     end
   end
@@ -347,8 +383,14 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
     def get(path, *args)
       args[0] ||= {}
       args[0][:headers] ||= {}
-      args[0][:headers]["action_dispatch.key_generator"] ||= Generator
-      args[0][:headers]["action_dispatch.cookies_rotations"] ||= Rotations
+      args[0][:headers].tap do |config|
+        config["action_dispatch.secret_key_base"] = SessionSecret
+        config["action_dispatch.authenticated_encrypted_cookie_salt"] = SessionSalt
+        config["action_dispatch.use_authenticated_cookie_encryption"] = true
+
+        config["action_dispatch.key_generator"] ||= Generator
+        config["action_dispatch.cookies_rotations"] ||= Rotations
+      end
 
       super(path, *args)
     end
-- 
cgit v1.2.3


From 5b2e826e6d5a9031133669be03e3b04861c17e4b Mon Sep 17 00:00:00 2001
From: Noah Davis <noah@codeclimate.com>
Date: Wed, 29 Nov 2017 23:16:04 -0500
Subject: Keep current Code Climate behavior before upgrade

If you merge these changes now (*before Monday, December 4th*), Code Climate will run the same analysis on Rails as it always has on your pull requests.

*If you do not merge these changes*, when Code Climate migrates Rails to our new analysis, Code Climate will continue to run Rubocop, but it will ALSO run Code Climate's new maintainability checks (https://codeclimate.com/blog/10-point-technical-debt-assessment).

You may want this ... you may not.

Just want to make sure you knew the quick option to disable them if it's a problem!
---
 .codeclimate.yml | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/.codeclimate.yml b/.codeclimate.yml
index 1f0f067c5b..d59a0780d1 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,3 +1,25 @@
+checks:
+  argument-count:
+    enabled: false
+  complex-logic:
+    enabled: false
+  file-lines:
+    enabled: false
+  method-complexity:
+    enabled: false
+  method-count:
+    enabled: false
+  method-lines:
+    enabled: false
+  nested-control-flow:
+    enabled: false
+  return-statements:
+    enabled: false
+  similar-code:
+    enabled: false
+  identical-code:
+    enabled: false
+
 engines:
   rubocop:
     enabled: true
-- 
cgit v1.2.3


From b6baf0c88411824ce99f1ad4b9de64fa37ad96ea Mon Sep 17 00:00:00 2001
From: Yauheni Dakuka <yauheni.dakuka@gmail.com>
Date: Thu, 30 Nov 2017 09:25:50 +0300
Subject: Cosmetic changes [ci skip]

---
 guides/source/3_2_release_notes.md                |  4 ++--
 guides/source/4_1_release_notes.md                |  2 +-
 guides/source/4_2_release_notes.md                |  2 +-
 guides/source/action_view_overview.md             |  4 ++--
 guides/source/asset_pipeline.md                   |  8 +++----
 guides/source/caching_with_rails.md               |  2 +-
 guides/source/i18n.md                             |  2 +-
 guides/source/initialization.md                   |  2 +-
 guides/source/testing.md                          |  2 +-
 guides/source/upgrading_ruby_on_rails.md          | 26 +++++++++++------------
 guides/source/working_with_javascript_in_rails.md |  2 +-
 11 files changed, 28 insertions(+), 28 deletions(-)

diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index f6571544f9..ae6eb27f35 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -36,7 +36,7 @@ TIP: Note that Ruby 1.8.7 p248 and p249 have marshalling bugs that crash Rails.
     * `coffee-rails ~> 3.2.1`
     * `uglifier >= 1.0.3`
 
-* Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. You can start replacing these plugins by extracting them as gems and adding them in your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
+* Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. You can start replacing these plugins by extracting them as gems and adding them in your `Gemfile`. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
 
 * There are a couple of new configuration changes you'd want to add in `config/environments/development.rb`:
 
@@ -156,7 +156,7 @@ Railties
 
     will create indexes for `title` and `author` with the latter being a unique index. Some types such as decimal accept custom options. In the example, `price` will be a decimal column with precision and scale set to 7 and 2 respectively.
 
-* Turn gem has been removed from default Gemfile.
+* Turn gem has been removed from default `Gemfile`.
 
 * Remove old plugin generator `rails generate plugin` in favor of `rails plugin new` command.
 
diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md
index 6bf65757ec..2c5e665e33 100644
--- a/guides/source/4_1_release_notes.md
+++ b/guides/source/4_1_release_notes.md
@@ -274,7 +274,7 @@ for detailed changes.
 * The [Spring application
   preloader](https://github.com/rails/spring) is now installed
   by default for new applications. It uses the development group of
-  the Gemfile, so will not be installed in
+  the `Gemfile`, so will not be installed in
   production. ([Pull Request](https://github.com/rails/rails/pull/12958))
 
 * `BACKTRACE` environment variable to show unfiltered backtraces for test
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index 036a310ac8..7105df5634 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -368,7 +368,7 @@ Please refer to the [Changelog][railties] for detailed changes.
 
 ### Notable changes
 
-*   Introduced `web-console` in the default application Gemfile.
+*   Introduced `web-console` in the default application `Gemfile`.
     ([Pull Request](https://github.com/rails/rails/pull/11667))
 
 *   Added a `required` option to the model generator for associations.
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index c1e02745de..fde2040173 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -149,10 +149,10 @@ end
 
 #### Jbuilder
 [Jbuilder](https://github.com/rails/jbuilder) is a gem that's
-maintained by the Rails team and included in the default Rails Gemfile.
+maintained by the Rails team and included in the default Rails `Gemfile`.
 It's similar to Builder, but is used to generate JSON, instead of XML.
 
-If you don't have it, you can add the following to your Gemfile:
+If you don't have it, you can add the following to your `Gemfile`:
 
 ```ruby
 gem 'jbuilder'
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 805b0f0d62..e6d5aed135 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -35,7 +35,7 @@ rails new appname --skip-sprockets
 ```
 
 Rails automatically adds the `sass-rails`, `coffee-rails` and `uglifier`
-gems to your Gemfile, which are used by Sprockets for asset compression:
+gems to your `Gemfile`, which are used by Sprockets for asset compression:
 
 ```ruby
 gem 'sass-rails'
@@ -44,8 +44,8 @@ gem 'coffee-rails'
 ```
 
 Using the `--skip-sprockets` option will prevent Rails from adding
-them to your Gemfile, so if you later want to enable
-the asset pipeline you will have to add those gems to your Gemfile. Also,
+them to your `Gemfile`, so if you later want to enable
+the asset pipeline you will have to add those gems to your `Gemfile`. Also,
 creating an application with the `--skip-sprockets` option will generate
 a slightly different `config/application.rb` file, with a require statement
 for the sprockets railtie that is commented-out. You will have to remove
@@ -850,7 +850,7 @@ This mode uses more memory, performs more poorly than the default and is not
 recommended.
 
 If you are deploying a production application to a system without any
-pre-existing JavaScript runtimes, you may want to add one to your Gemfile:
+pre-existing JavaScript runtimes, you may want to add one to your `Gemfile`:
 
 ```ruby
 group :production do
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 31bc478015..780e69c146 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -32,7 +32,7 @@ Basic Caching
 This is an introduction to three types of caching techniques: page, action and
 fragment caching. By default Rails provides fragment caching. In order to use
 page and action caching you will need to add `actionpack-page_caching` and
-`actionpack-action_caching` to your Gemfile.
+`actionpack-action_caching` to your `Gemfile`.
 
 By default, caching is only enabled in your production environment. To play
 around with caching locally you'll want to enable caching in your local
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index e6aa6181cc..2b545e6b82 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -977,7 +977,7 @@ en:
 ```
 
 NOTE: In order to use this helper, you need to install [DynamicForm](https://github.com/joelmoss/dynamic_form)
-gem by adding this line to your Gemfile: `gem 'dynamic_form'`.
+gem by adding this line to your `Gemfile`: `gem 'dynamic_form'`.
 
 ### Translations for Action Mailer E-Mail Subjects
 
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 1541ea38cd..c4f1df487b 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -93,7 +93,7 @@ require 'bundler/setup' # Set up gems listed in the Gemfile.
 
 In a standard Rails application, there's a `Gemfile` which declares all
 dependencies of the application. `config/boot.rb` sets
-`ENV['BUNDLE_GEMFILE']` to the location of this file. If the Gemfile
+`ENV['BUNDLE_GEMFILE']` to the location of this file. If the `Gemfile`
 exists, then `bundler/setup` is required. The require is used by Bundler to
 configure the load path for your Gemfile's dependencies.
 
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 8416fd163d..e0a2d281d9 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -645,7 +645,7 @@ system tests should live.
 
 If you want to change the default settings you can change what the system
 tests are "driven by". Say you want to change the driver from Selenium to
-Poltergeist. First add the `poltergeist` gem to your Gemfile. Then in your
+Poltergeist. First add the `poltergeist` gem to your `Gemfile`. Then in your
 `application_system_test_case.rb` file do the following:
 
 ```ruby
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 9bc87e4bf0..eae73c3e1b 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -45,7 +45,7 @@ TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterp
 ### The Update Task
 
 Rails provides the `app:update` task (`rake rails:update` on 4.2 and earlier). After updating the Rails version
-in the Gemfile, run this task.
+in the `Gemfile`, run this task.
 This will help you with the creation of new files and changes of old files in an
 interactive session.
 
@@ -179,7 +179,7 @@ See [#19034](https://github.com/rails/rails/pull/19034) for more details.
 
 `assigns` and `assert_template` have been extracted to the `rails-controller-testing` gem. To
 continue using these methods in your controller tests, add `gem 'rails-controller-testing'` to
-your Gemfile.
+your `Gemfile`.
 
 If you are using Rspec for testing, please see the extra configuration required in the gem's
 documentation.
@@ -212,7 +212,7 @@ true.
 
 `ActiveModel::Serializers::Xml` has been extracted from Rails to the `activemodel-serializers-xml`
 gem. To continue using XML serialization in your application, add `gem 'activemodel-serializers-xml'`
-to your Gemfile.
+to your `Gemfile`.
 
 ### Removed Support for Legacy `mysql` Database Adapter
 
@@ -278,7 +278,7 @@ You can now just call the dependency once with a wildcard.
 
 ### `ActionView::Helpers::RecordTagHelper` moved to external gem (record_tag_helper)
 
-`content_tag_for` and `div_for` have been removed in favor of just using `content_tag`. To continue using the older methods, add the `record_tag_helper` gem to your Gemfile:
+`content_tag_for` and `div_for` have been removed in favor of just using `content_tag`. To continue using the older methods, add the `record_tag_helper` gem to your `Gemfile`:
 
 ```ruby
 gem 'record_tag_helper', '~> 1.0'
@@ -415,7 +415,7 @@ First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your `Ge
 
 ### Responders
 
-`respond_with` and the class-level `respond_to` methods have been extracted to the `responders` gem. To use them, simply add `gem 'responders', '~> 2.0'` to your Gemfile. Calls to `respond_with` and `respond_to` (again, at the class level) will no longer work without having included the `responders` gem in your dependencies:
+`respond_with` and the class-level `respond_to` methods have been extracted to the `responders` gem. To use them, simply add `gem 'responders', '~> 2.0'` to your `Gemfile`. Calls to `respond_with` and `respond_to` (again, at the class level) will no longer work without having included the `responders` gem in your dependencies:
 
 ```ruby
 # app/controllers/users_controller.rb
@@ -559,7 +559,7 @@ Read the [gem's readme](https://github.com/rails/rails-html-sanitizer) for more
 The documentation for `PermitScrubber` and `TargetScrubber` explains how you
 can gain complete control over when and how elements should be stripped.
 
-If your application needs to use the old sanitizer implementation, include `rails-deprecated_sanitizer` in your Gemfile:
+If your application needs to use the old sanitizer implementation, include `rails-deprecated_sanitizer` in your `Gemfile`:
 
 ```ruby
 gem 'rails-deprecated_sanitizer'
@@ -617,7 +617,7 @@ migration DSL counterpart.
 
 The migration procedure is as follows:
 
-1. remove `gem "foreigner"` from the Gemfile.
+1. remove `gem "foreigner"` from the `Gemfile`.
 2. run `bundle install`.
 3. run `bin/rake db:schema:dump`.
 4. make sure that `db/schema.rb` contains every foreign key definition with
@@ -769,7 +769,7 @@ and has been removed from Rails.
 
 If your application currently depends on MultiJSON directly, you have a few options:
 
-1. Add 'multi_json' to your Gemfile. Note that this might cease to work in the future
+1. Add 'multi_json' to your `Gemfile`. Note that this might cease to work in the future
 
 2. Migrate away from MultiJSON by using `obj.to_json`, and `JSON.parse(str)` instead.
 
@@ -810,7 +810,7 @@ part of the rewrite, the following features have been removed from the encoder:
 
 If your application depends on one of these features, you can get them back by
 adding the [`activesupport-json_encoder`](https://github.com/rails/activesupport-json_encoder)
-gem to your Gemfile.
+gem to your `Gemfile`.
 
 #### JSON representation of Time objects
 
@@ -1135,7 +1135,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
+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
 file (in `config/application.rb`):
 
@@ -1147,7 +1147,7 @@ Bundler.require(*Rails.groups)
 
 ### vendor/plugins
 
-Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must replace any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
+Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must replace any plugins by extracting them to gems and adding them to your `Gemfile`. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
 
 ### Active Record
 
@@ -1214,7 +1214,7 @@ end
 
 ### Active Resource
 
-Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile.
+Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your `Gemfile`.
 
 ### Active Model
 
@@ -1414,7 +1414,7 @@ config.active_record.mass_assignment_sanitizer = :strict
 
 ### vendor/plugins
 
-Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. While it's not strictly necessary as part of a Rails 3.2 upgrade, you can start replacing any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
+Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. While it's not strictly necessary as part of a Rails 3.2 upgrade, you can start replacing any plugins by extracting them to gems and adding them to your `Gemfile`. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
 
 ### Active Record
 
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 86746a5ae0..c3dff1772c 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -492,7 +492,7 @@ replace the entire `<body>` of the page with the `<body>` of the response. It
 will then use PushState to change the URL to the correct one, preserving
 refresh semantics and giving you pretty URLs.
 
-The only thing you have to do to enable Turbolinks is have it in your Gemfile,
+The only thing you have to do to enable Turbolinks is have it in your `Gemfile`,
 and put `//= require turbolinks` in your JavaScript manifest, which is usually
 `app/assets/javascripts/application.js`.
 
-- 
cgit v1.2.3


From 1dca75c2c8f930b58d86cd2216af5e14307b3e53 Mon Sep 17 00:00:00 2001
From: Greg Navis <contact@gregnavis.com>
Date: Sat, 13 May 2017 03:09:58 +0200
Subject: Add support for PostgreSQL operator classes to add_index

Add support for specifying non-default operator classes in PostgreSQL
indexes. An example CREATE INDEX query that becomes possible is:

    CREATE INDEX users_name ON users USING gist (name gist_trgm_ops);

Previously it was possible to specify the `gist` index but not the
custom operator class. The `add_index` call for the above query is:

    add_index :users, :name, using: :gist, opclasses: {name: :gist_trgm_ops}
---
 activerecord/CHANGELOG.md                          |  8 ++++++
 .../abstract/schema_definitions.rb                 |  4 ++-
 .../abstract/schema_statements.rb                  | 28 +++++++++++++++++--
 .../postgresql/schema_statements.rb                | 32 ++++++++++++++--------
 .../connection_adapters/postgresql_adapter.rb      | 17 ++++++++++++
 activerecord/lib/active_record/schema_dumper.rb    |  1 +
 .../adapters/postgresql/active_schema_test.rb      |  3 ++
 .../adapters/postgresql/postgresql_adapter_test.rb | 10 +++----
 .../test/cases/adapters/postgresql/schema_test.rb  | 32 ++++++++++++++++++++++
 9 files changed, 115 insertions(+), 20 deletions(-)

diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 60ceffac5e..048a1eeb2f 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,11 @@
+*   Add support for PostgreSQL operator classes to `add_index`.
+
+    Example:
+
+        add_index :users, :name, using: :gist, opclass: name: :gist_trgm_ops
+
+    *Greg Navis*
+
 *   Don't allow scopes to be defined which conflict with instance methods on `Relation`.
 
     Fixes #31120.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index be2f625d74..b66009bdc6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -6,7 +6,7 @@ module ActiveRecord
     # this type are typically created and returned by methods in database
     # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
     class IndexDefinition # :nodoc:
-      attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment
+      attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :opclass, :comment
 
       def initialize(
         table, name,
@@ -17,6 +17,7 @@ module ActiveRecord
         where: nil,
         type: nil,
         using: nil,
+        opclass: {},
         comment: nil
       )
         @table = table
@@ -28,6 +29,7 @@ module ActiveRecord
         @where = where
         @type = type
         @using = using
+        @opclass = opclass
         @comment = comment
       end
     end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 9b7345f7c3..2a335e8f2b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -738,6 +738,29 @@ module ActiveRecord
       #
       # Note: only supported by PostgreSQL and MySQL
       #
+      # ====== Creating an index with a specific operator class
+      #
+      #   add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
+      #
+      # generates:
+      #
+      #   CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
+      #
+      #   add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
+      #
+      # generates:
+      #
+      #   CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
+      #
+      #   add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops })
+      #
+      # generates:
+      #
+      #   CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
+      #
+      #
+      # Note: only supported by PostgreSQL
+      #
       # ====== Creating an index with a specific type
       #
       #   add_index(:developers, :name, type: :fulltext)
@@ -1120,7 +1143,7 @@ module ActiveRecord
       def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
         column_names = index_column_names(column_name)
 
-        options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
+        options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass)
 
         index_type = options[:type].to_s if options.key?(:type)
         index_type ||= options[:unique] ? "UNIQUE" : ""
@@ -1186,7 +1209,8 @@ module ActiveRecord
           quoted_columns
         end
 
-        # Overridden by the MySQL adapter for supporting index lengths
+        # Overridden by the MySQL adapter for supporting index lengths and by
+        # the PostgreSQL adapter for supporting operator classes.
         def add_options_for_index_columns(quoted_columns, **options)
           if supports_index_sort_order?
             quoted_columns = add_index_sort_order(quoted_columns, options)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 846e721983..15a1dfd102 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -87,10 +87,7 @@ module ActiveRecord
 
           result = query(<<-SQL, "SCHEMA")
             SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
-                            pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
-            (SELECT COUNT(*) FROM pg_opclass o
-               JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
-                 ON o.oid = c.oid WHERE o.opcdefault = 'f')
+                            pg_catalog.obj_description(i.oid, 'pg_class') AS comment
             FROM pg_class t
             INNER JOIN pg_index d ON t.oid = d.indrelid
             INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -109,11 +106,10 @@ module ActiveRecord
             inddef = row[3]
             oid = row[4]
             comment = row[5]
-            opclass = row[6]
 
             using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
 
-            if indkey.include?(0) || opclass > 0
+            if indkey.include?(0)
               columns = expressions
             else
               columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
@@ -123,10 +119,21 @@ module ActiveRecord
                 AND a.attnum IN (#{indkey.join(",")})
               SQL
 
-              # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
-              orders = Hash[
-                expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
-              ]
+              orders = {}
+              opclasses = {}
+
+              # add info on sort order (only desc order is explicitly specified, asc is the default)
+              # and non-default opclasses
+              expressions.scan(/(\w+)(?: (?!DESC)(\w+))?(?: (DESC))?/).each do |column, opclass, desc|
+                opclasses[column] = opclass if opclass
+                orders[column] = :desc if desc
+              end
+
+              # Use a string for the opclass description (instead of a hash) when all columns
+              # have the same opclass specified.
+              if columns.count == opclasses.count && opclasses.values.uniq.count == 1
+                opclasses = opclasses.values.first
+              end
             end
 
             IndexDefinition.new(
@@ -137,6 +144,7 @@ module ActiveRecord
               orders: orders,
               where: where,
               using: using.to_sym,
+              opclass: opclasses,
               comment: comment.presence
             )
           end
@@ -458,8 +466,8 @@ module ActiveRecord
         end
 
         def add_index(table_name, column_name, options = {}) #:nodoc:
-          index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
-          execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do
+          index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+          execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
             execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
           end
         end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 7b27f6b7a0..eee61780b7 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -388,6 +388,23 @@ module ActiveRecord
 
       private
 
+        def add_index_opclass(column_names, options = {})
+          opclass =
+            if options[:opclass].is_a?(Hash)
+              options[:opclass].symbolize_keys
+            else
+              Hash.new { |hash, column| hash[column] = options[:opclass].to_s }
+            end
+          column_names.each do |name, column|
+            column << " #{opclass[name]}" if opclass[name].present?
+          end
+        end
+
+        def add_options_for_index_columns(quoted_columns, **options)
+          quoted_columns = add_index_opclass(quoted_columns, options)
+          super
+        end
+
         # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
         VALUE_LIMIT_VIOLATION = "22001"
         NUMERIC_VALUE_OUT_OF_RANGE = "22003"
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 66f7d29886..8cb2851557 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -189,6 +189,7 @@ HEADER
         index_parts << "where: #{index.where.inspect}" if index.where
         index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
         index_parts << "type: #{index.type.inspect}" if index.type
+        index_parts << "opclass: #{index.opclass.inspect}" if index.opclass.present?
         index_parts << "comment: #{index.comment.inspect}" if index.comment
         index_parts
       end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 9929237546..99c53dadeb 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -59,6 +59,9 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
       assert_equal expected, add_index(:people, "lower(last_name)", using: type, unique: true)
     end
 
+    expected = %(CREATE  INDEX  "index_people_on_last_name" ON "people" USING gist ("last_name" bpchar_pattern_ops))
+    assert_equal expected, add_index(:people, :last_name, using: :gist, opclass: { last_name: :bpchar_pattern_ops })
+
     assert_raise ArgumentError do
       add_index(:people, :last_name, algorithm: :copy)
     end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index f199519d86..1951230c8a 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -248,12 +248,12 @@ module ActiveRecord
 
       def test_index_with_opclass
         with_example_table do
-          @connection.add_index "ex", "data varchar_pattern_ops"
-          index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
-          assert_equal "data varchar_pattern_ops", index.columns
+          @connection.add_index "ex", "data", opclass: "varchar_pattern_ops"
+          index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data" }
+          assert_equal ["data"], index.columns
 
-          @connection.remove_index "ex", "data varchar_pattern_ops"
-          assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
+          @connection.remove_index "ex", "data"
+          assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data" }
         end
       end
 
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 5a64da028b..a6ae18a826 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -500,6 +500,38 @@ class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase
   end
 end
 
+class SchemaIndexOpclassTest < ActiveRecord::PostgreSQLTestCase
+  include SchemaDumpingHelper
+
+  setup do
+    @connection = ActiveRecord::Base.connection
+    @connection.create_table "trains" do |t|
+      t.string :name
+      t.text :description
+    end
+  end
+
+  teardown do
+    @connection.drop_table "trains", if_exists: true
+  end
+
+  def test_string_opclass_is_dumped
+    @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name text_pattern_ops, description text_pattern_ops)"
+
+    output = dump_table_schema "trains"
+
+    assert_match(/opclass: "text_pattern_ops"/, output)
+  end
+
+  def test_non_default_opclass_is_dumped
+    @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name, description text_pattern_ops)"
+
+    output = dump_table_schema "trains"
+
+    assert_match(/opclass: \{"description"=>"text_pattern_ops"\}/, output)
+  end
+end
+
 class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCase
   setup do
     @connection = ActiveRecord::Base.connection
-- 
cgit v1.2.3


From 96d17ecba8e374cd1ce4db0cee8c0dca057e3ff7 Mon Sep 17 00:00:00 2001
From: Dixit Patel <dixitpatel012@aol.com>
Date: Thu, 30 Nov 2017 17:05:48 +0530
Subject: [ci skip] Updated links for uncorn which redirect & added https for
 nginx link

---
 guides/source/configuring.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 4bfcc1e21a..fee644d4d4 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -1005,11 +1005,11 @@ Deploying your application using a reverse proxy has definite advantages over tr
 
 Many modern web servers can be used as a proxy server to balance third-party elements such as caching servers or application servers.
 
-One such application server you can use is [Unicorn](http://unicorn.bogomips.org/) to run behind a reverse proxy.
+One such application server you can use is [Unicorn](https://bogomips.org/unicorn/) to run behind a reverse proxy.
 
 In this case, you would need to configure the proxy server (NGINX, Apache, etc) to accept connections from your application server (Unicorn). By default Unicorn will listen for TCP connections on port 8080, but you can change the port or configure it to use sockets instead.
 
-You can find more information in the [Unicorn readme](http://unicorn.bogomips.org/README.html) and understand the [philosophy](http://unicorn.bogomips.org/PHILOSOPHY.html) behind it.
+You can find more information in the [Unicorn readme](https://bogomips.org/unicorn/README.html) and understand the [philosophy](https://bogomips.org/unicorn/PHILOSOPHY.html) behind it.
 
 Once you've configured the application server, you must proxy requests to it by configuring your web server appropriately. For example your NGINX config may include:
 
@@ -1037,7 +1037,7 @@ server {
 }
 ```
 
-Be sure to read the [NGINX documentation](http://nginx.org/en/docs/) for the most up-to-date information.
+Be sure to read the [NGINX documentation](https://nginx.org/en/docs/) for the most up-to-date information.
 
 
 Rails Environment Settings
-- 
cgit v1.2.3


From eb9ff5fd39ca3af20cb95b723ee622ceef10310d Mon Sep 17 00:00:00 2001
From: Anton Rieder <aried3r@gmail.com>
Date: Thu, 30 Nov 2017 16:34:06 +0100
Subject: Move system test dependencies to test group

---
 .../lib/rails/generators/rails/app/templates/Gemfile.tt | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
index 61026f5182..e3ed3e7c11 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
@@ -41,13 +41,6 @@ gem 'bootsnap', '>= 1.1.0', require: false
 group :development, :test do
   # Call 'byebug' anywhere in the code to stop execution and get a debugger console
   gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
-  <%- if depends_on_system_test? -%>
-  # Adds support for Capybara system testing and selenium driver
-  gem 'capybara', '~> 2.15'
-  gem 'selenium-webdriver'
-  # Easy installation and use of chromedriver to run system tests with Chrome
-  gem 'chromedriver-helper'
-  <%- end -%>
 end
 
 group :development do
@@ -70,6 +63,16 @@ group :development do
 <% end -%>
 <% end -%>
 end
+
+<%- if depends_on_system_test? -%>
+group :test do
+  # Adds support for Capybara system testing and selenium driver
+  gem 'capybara', '~> 2.15'
+  gem 'selenium-webdriver'
+  # Easy installation and use of chromedriver to run system tests with Chrome
+  gem 'chromedriver-helper'
+end
+<%- end -%>
 <% end -%>
 
 # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
-- 
cgit v1.2.3


From 0185aae747676e636a52eb079a0a10a6f053fa2c Mon Sep 17 00:00:00 2001
From: eileencodes <eileencodes@gmail.com>
Date: Thu, 30 Nov 2017 12:26:33 -0500
Subject: Add changelog entry for 9d6e28

Since this changes a default setting a changelog entry is important.
---
 actionpack/CHANGELOG.md | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index c8fb34ed52..d120d15770 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,15 @@
+*   Changed the default system test screenshot output from `inline` to `simple`.
+
+    `inline` works well for iTerm2 but not everyone uses iTerm2. Some terminals like
+    Terminal.app ignore the `inline` and output the path to the file since it can't
+    render the image. Other terminals, like those on Ubuntu, cannot handle the image
+    inline, but also don't handle it gracefully and instead of outputting the file
+    path, it dumps binary into the terminal.
+
+    Commit 9d6e28 fixes this by changing the default for screenshot to be `simple`.
+
+    *Eileen M. Uchitelle*
+
 *   Register most popular audio/video/font mime types supported by modern browsers.
 
     *Guillermo Iguaran*
-- 
cgit v1.2.3


From 0dc3b6535dfb8127f568bb4abc2fb09118ce4b96 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Fri, 1 Dec 2017 11:49:36 +0900
Subject: Maintain raising `RecordNotFound` for class level `update` and`
 destroy`

In 35836019, class level `update` and `destroy` suppressed
`RecordNotFound` to ensure returning affected objects. But
`RecordNotFound` is a common exception caught by a `rescue_from`
handler. So changing the behavior when a typical `params[:id]` is passed
has a compatibility problem. The previous behavior should not be
changed.

Fixes #31301.
---
 activerecord/lib/active_record/persistence.rb | 13 +++++++++----
 activerecord/test/cases/persistence_test.rb   | 14 ++++++++++++--
 2 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 4e1b05dbf6..17459f2505 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -99,7 +99,11 @@ module ActiveRecord
       # for updating all records in a single query.
       def update(id = :all, attributes)
         if id.is_a?(Array)
-          id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }.compact
+          id.map.with_index { |one_id, idx|
+            object = find_by(primary_key => one_id)
+            object.update(attributes[idx]) if object
+            object
+          }.compact
         elsif id == :all
           all.each { |record| record.update(attributes) }
         else
@@ -112,7 +116,6 @@ module ActiveRecord
           object.update(attributes)
           object
         end
-      rescue RecordNotFound
       end
 
       # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
@@ -136,11 +139,13 @@ module ActiveRecord
       #   Todo.destroy(todos)
       def destroy(id)
         if id.is_a?(Array)
-          id.map { |one_id| destroy(one_id) }.compact
+          id.map { |one_id|
+            object = find_by(primary_key => one_id)
+            object.destroy if object
+          }.compact
         else
           find(id).destroy
         end
-      rescue RecordNotFound
       end
 
       # Deletes the row with a primary key matching the +id+ argument, using a
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index f088c064f5..643032e7cf 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -473,10 +473,18 @@ class PersistenceTest < ActiveRecord::TestCase
     assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
   end
 
-  def test_record_not_found_exception
+  def test_find_raises_record_not_found_exception
     assert_raise(ActiveRecord::RecordNotFound) { Topic.find(99999) }
   end
 
+  def test_update_raises_record_not_found_exception
+    assert_raise(ActiveRecord::RecordNotFound) { Topic.update(99999, approved: true) }
+  end
+
+  def test_destroy_raises_record_not_found_exception
+    assert_raise(ActiveRecord::RecordNotFound) { Topic.destroy(99999) }
+  end
+
   def test_update_all
     assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
     assert_equal "bulk updated!", Topic.find(1).content
@@ -938,7 +946,9 @@ class PersistenceTest < ActiveRecord::TestCase
     should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
     Topic.find(1).replies << should_not_be_destroyed_reply
 
-    assert_nil Topic.where("1=0").scoping { Topic.destroy(1) }
+    assert_raise(ActiveRecord::RecordNotFound) do
+      Topic.where("1=0").scoping { Topic.destroy(1) }
+    end
 
     assert_nothing_raised { Topic.find(1) }
     assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) }
-- 
cgit v1.2.3


From d041a1dcbaced9f44ed976a48312c9f492fffe5e Mon Sep 17 00:00:00 2001
From: George Claghorn <george@basecamp.com>
Date: Thu, 30 Nov 2017 23:54:03 -0500
Subject: Add ActiveStorage::Previewer#logger to match
 ActiveStorage::Analyzer#logger

---
 activestorage/lib/active_storage/previewer.rb | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/activestorage/lib/active_storage/previewer.rb b/activestorage/lib/active_storage/previewer.rb
index ed75bae3b5..3d485988e9 100644
--- a/activestorage/lib/active_storage/previewer.rb
+++ b/activestorage/lib/active_storage/previewer.rb
@@ -54,5 +54,9 @@ module ActiveStorage
         IO.popen(argv) { |out| IO.copy_stream(out, to) }
         to.rewind
       end
+
+      def logger
+        ActiveStorage.logger
+      end
   end
 end
-- 
cgit v1.2.3


From 695ec1fef0b7fdb3125a3e2e4364a7f449265c1b Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Fri, 1 Dec 2017 18:21:21 +0900
Subject: Class level `update` and `destroy` checks all the records exist
 before making changes (#31306)

It makes more sense than ignoring invalid IDs.
---
 activerecord/lib/active_record/persistence.rb | 13 +++------
 activerecord/test/cases/persistence_test.rb   | 41 ++++++++++++++++++++++++---
 2 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 17459f2505..a13b0d0181 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -99,11 +99,9 @@ module ActiveRecord
       # for updating all records in a single query.
       def update(id = :all, attributes)
         if id.is_a?(Array)
-          id.map.with_index { |one_id, idx|
-            object = find_by(primary_key => one_id)
-            object.update(attributes[idx]) if object
-            object
-          }.compact
+          id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
+            object.update(attributes[idx])
+          }
         elsif id == :all
           all.each { |record| record.update(attributes) }
         else
@@ -139,10 +137,7 @@ module ActiveRecord
       #   Todo.destroy(todos)
       def destroy(id)
         if id.is_a?(Array)
-          id.map { |one_id|
-            object = find_by(primary_key => one_id)
-            object.destroy if object
-          }.compact
+          find(id).each(&:destroy)
         else
           find(id).destroy
         end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 643032e7cf..4aea275d51 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -70,7 +70,7 @@ class PersistenceTest < ActiveRecord::TestCase
   end
 
   def test_update_many
-    topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" }, nil => {} }
+    topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
     updated = Topic.update(topic_data.keys, topic_data.values)
 
     assert_equal [1, 2], updated.map(&:id)
@@ -78,10 +78,33 @@ class PersistenceTest < ActiveRecord::TestCase
     assert_equal "2 updated", Topic.find(2).content
   end
 
+  def test_update_many_with_duplicated_ids
+    updated = Topic.update([1, 1, 2], [
+      { "content" => "1 duplicated" }, { "content" => "1 updated" }, { "content" => "2 updated" }
+    ])
+
+    assert_equal [1, 1, 2], updated.map(&:id)
+    assert_equal "1 updated", Topic.find(1).content
+    assert_equal "2 updated", Topic.find(2).content
+  end
+
+  def test_update_many_with_invalid_id
+    topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" }, 99999 => {} }
+
+    assert_raise(ActiveRecord::RecordNotFound) do
+      Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) }
+    end
+
+    assert_not_equal "1 updated", Topic.find(1).content
+    assert_not_equal "2 updated", Topic.find(2).content
+  end
+
   def test_class_level_update_is_affected_by_scoping
     topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
 
-    assert_equal [], Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) }
+    assert_raise(ActiveRecord::RecordNotFound) do
+      Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) }
+    end
 
     assert_not_equal "1 updated", Topic.find(1).content
     assert_not_equal "2 updated", Topic.find(2).content
@@ -175,15 +198,25 @@ class PersistenceTest < ActiveRecord::TestCase
   end
 
   def test_destroy_many
-    clients = Client.all.merge!(order: "id").find([2, 3])
+    clients = Client.find([2, 3])
 
     assert_difference("Client.count", -2) do
-      destroyed = Client.destroy([2, 3, nil]).sort_by(&:id)
+      destroyed = Client.destroy([2, 3])
       assert_equal clients, destroyed
       assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
     end
   end
 
+  def test_destroy_many_with_invalid_id
+    clients = Client.find([2, 3])
+
+    assert_raise(ActiveRecord::RecordNotFound) do
+      Client.destroy([2, 3, 99999])
+    end
+
+    assert_equal clients, Client.find([2, 3])
+  end
+
   def test_becomes
     assert_kind_of Reply, topics(:first).becomes(Reply)
     assert_equal "The First Topic", topics(:first).becomes(Reply).title
-- 
cgit v1.2.3


From 05d1e9e413a87bee5b0c1c200fa9f84dba0e1d15 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Fri, 1 Dec 2017 18:28:31 +0900
Subject: Remove unnecessary scoping

---
 activerecord/test/cases/persistence_test.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 4aea275d51..4cc66a2e49 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -92,7 +92,7 @@ class PersistenceTest < ActiveRecord::TestCase
     topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" }, 99999 => {} }
 
     assert_raise(ActiveRecord::RecordNotFound) do
-      Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) }
+      Topic.update(topic_data.keys, topic_data.values)
     end
 
     assert_not_equal "1 updated", Topic.find(1).content
-- 
cgit v1.2.3


From 6b16f6db279bdbd95e8b295c9bab6760a6ff6af0 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Fri, 1 Dec 2017 20:42:04 +0900
Subject: Tweaks CHANGELOGs [ci skip]

---
 actionview/CHANGELOG.md   | 2 +-
 activerecord/CHANGELOG.md | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 566e30993b..f42ada0baa 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,7 +1,7 @@
 *   Add `preload_link_tag` helper
 
     This helper that allows to the browser to initiate early fetch of resources
-    (different to the specified in javascript_include_tag and stylesheet_link_tag).
+    (different to the specified in `javascript_include_tag` and `stylesheet_link_tag`).
     Additionally, this sends Early Hints if supported by browser.
 
     *Guillermo Iguaran*
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 048a1eeb2f..89a12d4223 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -2,7 +2,7 @@
 
     Example:
 
-        add_index :users, :name, using: :gist, opclass: name: :gist_trgm_ops
+        add_index :users, :name, using: :gist, opclass: { name: :gist_trgm_ops }
 
     *Greg Navis*
 
@@ -80,7 +80,7 @@
     *bogdanvlviv*
 
 *   Fixed a bug where column orders for an index weren't written to
-    db/schema.rb when using the sqlite adapter.
+    `db/schema.rb` when using the sqlite adapter.
 
     Fixes #30902.
 
-- 
cgit v1.2.3


From 32516e8862946da963e4ba9256553156ca321596 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Fri, 1 Dec 2017 20:48:29 +0900
Subject: Remove unpaired `}` [ci skip]

---
 .../active_record/connection_adapters/abstract/schema_statements.rb    | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 2a335e8f2b..7ae32d0ced 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -752,13 +752,12 @@ module ActiveRecord
       #
       #   CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
       #
-      #   add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops })
+      #   add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
       #
       # generates:
       #
       #   CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
       #
-      #
       # Note: only supported by PostgreSQL
       #
       # ====== Creating an index with a specific type
-- 
cgit v1.2.3


From 8203482a9ef9bd60d5014745fd7af868d9954b1d Mon Sep 17 00:00:00 2001
From: Travis Hunter <travis.c.hunter@gmail.com>
Date: Fri, 20 Jan 2017 16:18:46 -0500
Subject: Add support for invalid foreign keys in Postgres

Add validate_constraint and update naming
---
 .../abstract/schema_definitions.rb                 |  5 ++
 .../abstract/schema_statements.rb                  |  2 +
 .../connection_adapters/abstract_adapter.rb        |  5 ++
 .../postgresql/schema_creation.rb                  | 12 ++++
 .../postgresql/schema_definitions.rb               | 13 +++++
 .../postgresql/schema_statements.rb                | 44 +++++++++++++-
 .../connection_adapters/postgresql_adapter.rb      |  4 ++
 .../test/cases/migration/foreign_key_test.rb       | 68 ++++++++++++++++++++++
 8 files changed, 152 insertions(+), 1 deletion(-)

diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index b66009bdc6..88ef8e0819 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -87,6 +87,11 @@ module ActiveRecord
         options[:primary_key] != default_primary_key
       end
 
+      def validate?
+        options.fetch(:validate, true)
+      end
+      alias validated? validate?
+
       def defined_for?(to_table_ord = nil, to_table: nil, **options)
         if to_table_ord
           self.to_table == to_table_ord.to_s
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 7ae32d0ced..09ccf0c193 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -964,6 +964,8 @@ module ActiveRecord
       #   Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
       # [<tt>:on_update</tt>]
       #   Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
+      # [<tt>:validate</tt>]
+      #   (Postgres only) Specify whether or not the constraint should be validated. Defaults to +true+.
       def add_foreign_key(from_table, to_table, options = {})
         return unless supports_foreign_keys?
 
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 8993c517a6..fc80d332f9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -272,6 +272,11 @@ module ActiveRecord
         false
       end
 
+      # Does this adapter support creating invalid constraints?
+      def supports_validate_constraints?
+        false
+      end
+
       # Does this adapter support creating foreign key constraints
       # in the same statement as creating the table?
       def supports_foreign_keys_in_create?
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
index 59f661da25..8e381a92cf 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
@@ -5,6 +5,18 @@ module ActiveRecord
     module PostgreSQL
       class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
         private
+          def visit_AlterTable(o)
+            super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
+          end
+
+          def visit_AddForeignKey(o)
+            super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
+          end
+
+          def visit_ValidateConstraint(name)
+            "VALIDATE CONSTRAINT #{quote_column_name(name)}"
+          end
+
           def add_column_options!(sql, options)
             if options[:collation]
               sql << " COLLATE \"#{options[:collation]}\""
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index 75622eb304..a3bb66bf3e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -192,6 +192,19 @@ module ActiveRecord
       class Table < ActiveRecord::ConnectionAdapters::Table
         include ColumnMethods
       end
+
+      class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
+        attr_reader :constraint_validations
+
+        def initialize(td)
+          super
+          @constraint_validations = []
+        end
+
+        def validate_constraint(name)
+          @constraint_validations << name
+        end
+      end
     end
   end
 end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 15a1dfd102..38102b2e7a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -507,7 +507,7 @@ module ActiveRecord
         def foreign_keys(table_name)
           scope = quoted_scope(table_name)
           fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
-            SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
+            SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
             FROM pg_constraint c
             JOIN pg_class t1 ON c.conrelid = t1.oid
             JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -529,6 +529,7 @@ module ActiveRecord
 
             options[:on_delete] = extract_foreign_key_action(row["on_delete"])
             options[:on_update] = extract_foreign_key_action(row["on_update"])
+            options[:validate] = row["valid"]
 
             ForeignKeyDefinition.new(table_name, row["to_table"], options)
           end
@@ -589,6 +590,43 @@ module ActiveRecord
           PostgreSQL::SchemaDumper.create(self, options)
         end
 
+        # Validates the given constraint.
+        #
+        # Validates the constraint named +constraint_name+ on +accounts+.
+        #
+        #   validate_foreign_key :accounts, :constraint_name
+        def validate_constraint(table_name, constraint_name)
+          return unless supports_validate_constraints?
+
+          at = create_alter_table table_name
+          at.validate_constraint constraint_name
+
+          execute schema_creation.accept(at)
+        end
+
+        # Validates the given foreign key.
+        #
+        # Validates the foreign key on +accounts.branch_id+.
+        #
+        #   validate_foreign_key :accounts, :branches
+        #
+        # Validates the foreign key on +accounts.owner_id+.
+        #
+        #   validate_foreign_key :accounts, column: :owner_id
+        #
+        # Validates the foreign key named +special_fk_name+ on the +accounts+ table.
+        #
+        #   validate_foreign_key :accounts, name: :special_fk_name
+        #
+        # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
+        def validate_foreign_key(from_table, options_or_to_table = {})
+          return unless supports_validate_constraints?
+
+          fk_name_to_validate = foreign_key_for!(from_table, options_or_to_table).name
+
+          validate_constraint from_table, fk_name_to_validate
+        end
+
         private
           def schema_creation
             PostgreSQL::SchemaCreation.new(self)
@@ -598,6 +636,10 @@ module ActiveRecord
             PostgreSQL::TableDefinition.new(*args)
           end
 
+          def create_alter_table(name)
+            PostgreSQL::AlterTable.new create_table_definition(name)
+          end
+
           def new_column_from_field(table_name, field)
             column_name, type, default, notnull, oid, fmod, collation, comment = field
             type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 9397481c4c..68b79f777e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -142,6 +142,10 @@ module ActiveRecord
         true
       end
 
+      def supports_validate_constraints?
+        true
+      end
+
       def supports_views?
         true
       end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 499d072de5..079be04946 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -227,6 +227,74 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
           end
         end
 
+        if ActiveRecord::Base.connection.supports_validate_constraints?
+          def test_add_invalid_foreign_key
+            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false
+
+            foreign_keys = @connection.foreign_keys("astronauts")
+            assert_equal 1, foreign_keys.size
+
+            fk = foreign_keys.first
+            refute fk.validated?
+          end
+
+          def test_validate_foreign_key_infers_column
+            @connection.add_foreign_key :astronauts, :rockets, validate: false
+            refute @connection.foreign_keys("astronauts").first.validated?
+
+            @connection.validate_foreign_key :astronauts, :rockets
+            assert @connection.foreign_keys("astronauts").first.validated?
+          end
+
+          def test_validate_foreign_key_by_column
+            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false
+            refute @connection.foreign_keys("astronauts").first.validated?
+
+            @connection.validate_foreign_key :astronauts, column: "rocket_id"
+            assert @connection.foreign_keys("astronauts").first.validated?
+          end
+
+          def test_validate_foreign_key_by_symbol_column
+            @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id, validate: false
+            refute @connection.foreign_keys("astronauts").first.validated?
+
+            @connection.validate_foreign_key :astronauts, column: :rocket_id
+            assert @connection.foreign_keys("astronauts").first.validated?
+          end
+
+          def test_validate_foreign_key_by_name
+            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk", validate: false
+            refute @connection.foreign_keys("astronauts").first.validated?
+
+            @connection.validate_foreign_key :astronauts, name: "fancy_named_fk"
+            assert @connection.foreign_keys("astronauts").first.validated?
+          end
+
+          def test_validate_foreign_non_existing_foreign_key_raises
+            assert_raises ArgumentError do
+              @connection.validate_foreign_key :astronauts, :rockets
+            end
+          end
+
+          def test_validate_constraint_by_name
+            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk", validate: false
+
+            @connection.validate_constraint :astronauts, "fancy_named_fk"
+            assert @connection.foreign_keys("astronauts").first.validated?
+          end
+        else
+          # Foreign key should still be created, but should not be invalid
+          def test_add_invalid_foreign_key
+            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false
+
+            foreign_keys = @connection.foreign_keys("astronauts")
+            assert_equal 1, foreign_keys.size
+
+            fk = foreign_keys.first
+            assert fk.validated?
+          end
+        end
+
         def test_schema_dumping
           @connection.add_foreign_key :astronauts, :rockets
           output = dump_table_schema "astronauts"
-- 
cgit v1.2.3


From b852ef2660dac36e348865b455fab7fbcc0d2a7f Mon Sep 17 00:00:00 2001
From: George Claghorn <george.claghorn@gmail.com>
Date: Fri, 1 Dec 2017 11:07:30 -0500
Subject: Make ASt previewer/analyzer binary paths configurable

---
 .../lib/active_storage/analyzer/video_analyzer.rb     |  4 +++-
 activestorage/lib/active_storage/engine.rb            | 19 ++++++++++++++++++-
 .../lib/active_storage/previewer/pdf_previewer.rb     |  9 ++++++++-
 .../lib/active_storage/previewer/video_previewer.rb   |  4 +++-
 4 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/activestorage/lib/active_storage/analyzer/video_analyzer.rb b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
index 408b5e58e9..b6fc54d917 100644
--- a/activestorage/lib/active_storage/analyzer/video_analyzer.rb
+++ b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
@@ -19,6 +19,8 @@ module ActiveStorage
   # This analyzer requires the {ffmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails. You must
   # install ffmpeg yourself to use this analyzer.
   class Analyzer::VideoAnalyzer < Analyzer
+    class_attribute :ffprobe_path, default: "ffprobe"
+
     def self.accept?(blob)
       blob.video?
     end
@@ -68,7 +70,7 @@ module ActiveStorage
       end
 
       def probe_from(file)
-        IO.popen([ "ffprobe", "-print_format", "json", "-show_streams", "-v", "error", file.path ]) do |output|
+        IO.popen([ ffprobe_path, "-print_format", "json", "-show_streams", "-v", "error", file.path ]) do |output|
           JSON.parse(output.read)
         end
       rescue Errno::ENOENT
diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb
index 6cf6635c4f..b870e6d4d6 100644
--- a/activestorage/lib/active_storage/engine.rb
+++ b/activestorage/lib/active_storage/engine.rb
@@ -15,7 +15,8 @@ module ActiveStorage
 
     config.active_storage = ActiveSupport::OrderedOptions.new
     config.active_storage.previewers = [ ActiveStorage::Previewer::PDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
-    config.active_storage.analyzers  = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
+    config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
+    config.active_storage.paths = ActiveSupport::OrderedOptions.new
 
     config.eager_load_namespaces << ActiveStorage
 
@@ -68,5 +69,21 @@ module ActiveStorage
         end
       end
     end
+
+    initializer "active_storage.paths" do
+      config.after_initialize do |app|
+        if ffprobe_path = app.config.active_storage.paths.ffprobe
+          ActiveStorage::Analyzer::VideoAnalyzer.ffprobe_path = ffprobe_path
+        end
+
+        if ffmpeg_path = app.config.active_storage.paths.ffmpeg
+          ActiveStorage::Previewer::VideoPreviewer.ffmpeg_path = ffmpeg_path
+        end
+
+        if mutool_path = app.config.active_storage.paths.mutool
+          ActiveStorage::Previewer::PDFPreviewer.mutool_path = mutool_path
+        end
+      end
+    end
   end
 end
diff --git a/activestorage/lib/active_storage/previewer/pdf_previewer.rb b/activestorage/lib/active_storage/previewer/pdf_previewer.rb
index a2f05c74a6..b84aefcc9c 100644
--- a/activestorage/lib/active_storage/previewer/pdf_previewer.rb
+++ b/activestorage/lib/active_storage/previewer/pdf_previewer.rb
@@ -2,16 +2,23 @@
 
 module ActiveStorage
   class Previewer::PDFPreviewer < Previewer
+    class_attribute :mutool_path, default: "mutool"
+
     def self.accept?(blob)
       blob.content_type == "application/pdf"
     end
 
     def preview
       download_blob_to_tempfile do |input|
-        draw "mutool", "draw", "-F", "png", "-o", "-", input.path, "1" do |output|
+        draw_first_page_from input do |output|
           yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
         end
       end
     end
+
+    private
+      def draw_first_page_from(file, &block)
+        draw mutool_path, "draw", "-F", "png", "-o", "-", file.path, "1", &block
+      end
   end
 end
diff --git a/activestorage/lib/active_storage/previewer/video_previewer.rb b/activestorage/lib/active_storage/previewer/video_previewer.rb
index 49f128d142..5d06e33f44 100644
--- a/activestorage/lib/active_storage/previewer/video_previewer.rb
+++ b/activestorage/lib/active_storage/previewer/video_previewer.rb
@@ -2,6 +2,8 @@
 
 module ActiveStorage
   class Previewer::VideoPreviewer < Previewer
+    class_attribute :ffmpeg_path, default: "ffmpeg"
+
     def self.accept?(blob)
       blob.video?
     end
@@ -16,7 +18,7 @@ module ActiveStorage
 
     private
       def draw_relevant_frame_from(file, &block)
-        draw "ffmpeg", "-i", file.path, "-y", "-vcodec", "png",
+        draw ffmpeg_path, "-i", file.path, "-y", "-vcodec", "png",
           "-vf", "thumbnail", "-vframes", "1", "-f", "image2", "-", &block
       end
   end
-- 
cgit v1.2.3


From 70c96b4423abacecb1116184c8ea4d4662b809f8 Mon Sep 17 00:00:00 2001
From: "yuuji.yaginuma" <yuuji.yaginuma@gmail.com>
Date: Sat, 2 Dec 2017 11:18:38 +0900
Subject: Fix method name in `validate_constraint` doc [ci skip]

---
 .../active_record/connection_adapters/postgresql/schema_statements.rb   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 38102b2e7a..af13a9e16f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -594,7 +594,7 @@ module ActiveRecord
         #
         # Validates the constraint named +constraint_name+ on +accounts+.
         #
-        #   validate_foreign_key :accounts, :constraint_name
+        #   validate_constraint :accounts, :constraint_name
         def validate_constraint(table_name, constraint_name)
           return unless supports_validate_constraints?
 
-- 
cgit v1.2.3


From dd6338a0699f2301d4b2fc8653688b4c4183cee5 Mon Sep 17 00:00:00 2001
From: Dinah Shi <dinahshi28@gmail.com>
Date: Sat, 2 Dec 2017 14:30:10 +0800
Subject: Extract sql fragment generators for alter table from PostgreSQL
 adapter

---
 .../abstract/schema_statements.rb                  | 16 +++-
 .../connection_adapters/abstract_mysql_adapter.rb  | 36 +++------
 .../postgresql/schema_statements.rb                | 86 +++++++++++++---------
 .../lib/active_record/migration/compatibility.rb   |  9 +++
 .../test/cases/adapters/mysql2/connection_test.rb  |  4 +-
 .../test/cases/migration/compatibility_test.rb     | 17 +++++
 6 files changed, 107 insertions(+), 61 deletions(-)

diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 9b7345f7c3..f0c6ad63f9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -600,7 +600,7 @@ module ActiveRecord
       # to provide these in a migration's +change+ method so it can be reverted.
       # In that case, +type+ and +options+ will be used by #add_column.
       def remove_column(table_name, column_name, type = nil, options = {})
-        execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
+        execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}"
       end
 
       # Changes the column's definition according to the new options.
@@ -1340,6 +1340,20 @@ module ActiveRecord
           options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
         end
 
+        def add_column_for_alter(table_name, column_name, type, options = {})
+          td = create_table_definition(table_name)
+          cd = td.new_column_definition(column_name, type, options)
+          schema_creation.accept(AddColumnDefinition.new(cd))
+        end
+
+        def remove_column_for_alter(table_name, column_name, type = nil, options = {})
+          "DROP COLUMN #{quote_column_name(column_name)}"
+        end
+
+        def remove_columns_for_alter(table_name, *column_names)
+          column_names.map { |column_name| remove_column_for_alter(table_name, column_name) }
+        end
+
         def insert_versions_sql(versions)
           sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
 
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 0e552dba95..2926366e10 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -299,7 +299,7 @@ module ActiveRecord
       def bulk_change_table(table_name, operations) #:nodoc:
         sqls = operations.flat_map do |command, args|
           table, arguments = args.shift, args
-          method = :"#{command}_sql"
+          method = :"#{command}_for_alter"
 
           if respond_to?(method, true)
             send(method, table, *arguments)
@@ -372,11 +372,11 @@ module ActiveRecord
       end
 
       def change_column(table_name, column_name, type, options = {}) #:nodoc:
-        execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
+        execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
       end
 
       def rename_column(table_name, column_name, new_column_name) #:nodoc:
-        execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
+        execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
         rename_column_indexes(table_name, column_name, new_column_name)
       end
 
@@ -668,13 +668,7 @@ module ActiveRecord
           end
         end
 
-        def add_column_sql(table_name, column_name, type, options = {})
-          td = create_table_definition(table_name)
-          cd = td.new_column_definition(column_name, type, options)
-          schema_creation.accept(AddColumnDefinition.new(cd))
-        end
-
-        def change_column_sql(table_name, column_name, type, options = {})
+        def change_column_for_alter(table_name, column_name, type, options = {})
           column = column_for(table_name, column_name)
           type ||= column.sql_type
 
@@ -695,7 +689,7 @@ module ActiveRecord
           schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
         end
 
-        def rename_column_sql(table_name, column_name, new_column_name)
+        def rename_column_for_alter(table_name, column_name, new_column_name)
           column  = column_for(table_name, column_name)
           options = {
             default: column.default,
@@ -709,31 +703,23 @@ module ActiveRecord
           schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
         end
 
-        def remove_column_sql(table_name, column_name, type = nil, options = {})
-          "DROP #{quote_column_name(column_name)}"
-        end
-
-        def remove_columns_sql(table_name, *column_names)
-          column_names.map { |column_name| remove_column_sql(table_name, column_name) }
-        end
-
-        def add_index_sql(table_name, column_name, options = {})
+        def add_index_for_alter(table_name, column_name, options = {})
           index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
           index_algorithm[0, 0] = ", " if index_algorithm.present?
           "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
         end
 
-        def remove_index_sql(table_name, options = {})
+        def remove_index_for_alter(table_name, options = {})
           index_name = index_name_for_remove(table_name, options)
           "DROP INDEX #{quote_column_name(index_name)}"
         end
 
-        def add_timestamps_sql(table_name, options = {})
-          [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
+        def add_timestamps_for_alter(table_name, options = {})
+          [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
         end
 
-        def remove_timestamps_sql(table_name, options = {})
-          [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
+        def remove_timestamps_for_alter(table_name, options = {})
+          [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
         end
 
         # MySQL is too stupid to create a temporary table for use subquery, so we have
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 846e721983..371597031c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -391,51 +391,24 @@ module ActiveRecord
         end
 
         def change_column(table_name, column_name, type, options = {}) #:nodoc:
-          clear_cache!
           quoted_table_name = quote_table_name(table_name)
-          quoted_column_name = quote_column_name(column_name)
-          sql_type = type_to_sql(type, options)
-          sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
-          if options[:collation]
-            sql << " COLLATE \"#{options[:collation]}\""
-          end
-          if options[:using]
-            sql << " USING #{options[:using]}"
-          elsif options[:cast_as]
-            cast_as_type = type_to_sql(options[:cast_as], options)
-            sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
-          end
-          execute sql
-
-          change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
-          change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
-          change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
+          sqls, procs = change_column_for_alter(table_name, column_name, type, options)
+          execute "ALTER TABLE #{quoted_table_name} #{sqls.join(", ")}"
+          procs.each(&:call)
         end
 
         # Changes the default value of a table column.
         def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
-          clear_cache!
-          column = column_for(table_name, column_name)
-          return unless column
-
-          default = extract_new_default_value(default_or_changes)
-          alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
-          if default.nil?
-            # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
-            # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
-            execute alter_column_query % "DROP DEFAULT"
-          else
-            execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
-          end
+          execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
         end
 
         def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
           clear_cache!
           unless null || default.nil?
             column = column_for(table_name, column_name)
-            execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
+            execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
           end
-          execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
+          execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
         end
 
         # Adds comment for given table column or drops it if +comment+ is a +nil+
@@ -629,6 +602,53 @@ module ActiveRecord
             end
           end
 
+          def change_column_for_alter(table_name, column_name, type, options = {})
+            clear_cache!
+            quoted_column_name = quote_column_name(column_name)
+            sql_type = type_to_sql(type, options)
+            sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
+            if options[:collation]
+              sql << " COLLATE \"#{options[:collation]}\""
+            end
+            if options[:using]
+              sql << " USING #{options[:using]}"
+            elsif options[:cast_as]
+              cast_as_type = type_to_sql(options[:cast_as], options)
+              sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
+            end
+
+            sqls = [sql]
+            procs = []
+            sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
+            sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+            procs << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
+
+            [sqls, procs]
+          end
+
+
+          # Changes the default value of a table column.
+          def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
+            clear_cache!
+            column = column_for(table_name, column_name)
+            return unless column
+
+            default = extract_new_default_value(default_or_changes)
+            alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
+            if default.nil?
+              # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
+              # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
+              alter_column_query % "DROP DEFAULT"
+            else
+              alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
+            end
+          end
+
+          def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc:
+            clear_cache!
+            "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
+          end
+
           def data_source_sql(name = nil, type: nil)
             scope = quoted_scope(name, type: type)
             scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index c979aaf0a0..da307c1723 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -16,6 +16,15 @@ module ActiveRecord
       V5_2 = Current
 
       class V5_1 < V5_2
+        if adapter_name == "PostgreSQL"
+          def change_column(table_name, column_name, type, options = {})
+            unless options[:null] || options[:default].nil?
+              column = connection.send(:column_for, table_name, column_name)
+              connection.execute("UPDATE #{connection.quote_table_name(table_name)} SET #{connection.quote_column_name(column_name)}=#{connection.quote_default_expression(options[:default], column)} WHERE #{connection.quote_column_name(column_name)} IS NULL") if column
+            end
+            connection.change_column(table_name, column_name, type, options)
+          end
+        end
       end
 
       class V5_0 < V5_1
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index e61c70848a..13b4096671 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -174,10 +174,10 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
     assert_equal "SCHEMA", @subscriber.logged[0][1]
   end
 
-  def test_logs_name_rename_column_sql
+  def test_logs_name_rename_column_for_alter
     @connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))"
     @subscriber.logged.clear
-    @connection.send(:rename_column_sql, "bar_baz", "foo", "foo2")
+    @connection.send(:rename_column_for_alter, "bar_baz", "foo", "foo2")
     assert_equal "SCHEMA", @subscriber.logged[0][1]
   ensure
     @connection.execute "DROP TABLE `bar_baz`"
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 2fef2f796e..0fd55e6ae4 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -126,6 +126,23 @@ module ActiveRecord
         end
         assert_match(/LegacyMigration < ActiveRecord::Migration\[4\.2\]/, e.message)
       end
+
+      if current_adapter?(:PostgreSQLAdapter)
+        class Testing < ActiveRecord::Base
+        end
+
+        def test_legacy_change_column_with_null_executes_update
+          migration = Class.new(ActiveRecord::Migration[5.1]) {
+            def migrate(x)
+              change_column :testings, :foo, :string, null: false, default: "foobar"
+            end
+          }.new
+
+          t = Testing.create!
+          ActiveRecord::Migrator.new(:up, [migration]).migrate
+          assert_equal ["foobar"], Testing.all.map(&:foo)
+        end
+      end
     end
   end
 end
-- 
cgit v1.2.3


From 99f6722e86c5451e1334db31a18ae2cbaccb551d Mon Sep 17 00:00:00 2001
From: willnet <netwillnet@gmail.com>
Date: Sat, 2 Dec 2017 16:35:38 +0900
Subject: [ci skip] Add a missing space before closing curly braces

---
 guides/source/form_helpers.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 4ce67df93a..f8ec389b01 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -920,7 +920,7 @@ When an association accepts nested attributes `fields_for` renders its block onc
 ```ruby
 def new
   @person = Person.new
-  2.times { @person.addresses.build}
+  2.times { @person.addresses.build }
 end
 ```
 
-- 
cgit v1.2.3


From a71bbed76aab9e8a9a6b2da18bcacd8ee32a0dd0 Mon Sep 17 00:00:00 2001
From: claudiob <claudiob@users.noreply.github.com>
Date: Sat, 2 Dec 2017 12:00:09 -0800
Subject: Fix typo in test error message

With the current code, a failing test shows this error, which is missing
the number of times called and has two periods at the end.

```
/railties$ be ruby -Itest test/generators/app_generator_test.rb -n test_active_storage_install

Failure:
AppGeneratorTest#test_active_storage_install [test/generators/app_generator_test.rb:313]:
active_storage:install expected to be called once, but was called  times..
Expected: 1
  Actual: 2
```

After the fix, the error message looks correct:

```
/railties$ be ruby -Itest test/generators/app_generator_test.rb -n test_active_storage_install

Failure:
AppGeneratorTest#test_active_storage_install [test/generators/app_generator_test.rb:313]:
active_storage:install expected to be called once, but was called 2 times.
Expected: 1
  Actual: 2
```
---
 railties/test/generators/app_generator_test.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 774fd0f315..8d12f4e5a7 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -310,7 +310,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
       case command
       when "active_storage:install"
         @binstub_called += 1
-        assert_equal 1, @binstub_called, "active_storage:install expected to be called once, but was called #{@install_called} times."
+        assert_equal 1, @binstub_called, "active_storage:install expected to be called once, but was called #{@binstub_called} times"
       end
     end
 
-- 
cgit v1.2.3


From 01fb0907b2dafcecf63eedb80408d4cbff5f4d23 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Sun, 3 Dec 2017 04:57:04 +0900
Subject: Refactor `length`, `order`, and `opclass` index options dumping

---
 .../abstract/schema_definitions.rb                 | 19 ++++++++++++-----
 .../abstract/schema_statements.rb                  |  2 +-
 .../connection_adapters/abstract_mysql_adapter.rb  |  2 +-
 .../connection_adapters/mysql/schema_statements.rb | 15 ++++++++------
 .../postgresql/schema_statements.rb                | 16 +++++----------
 activerecord/lib/active_record/schema_dumper.rb    | 14 ++++++++++---
 .../test/cases/adapters/postgresql/schema_test.rb  |  6 +++---
 activerecord/test/cases/schema_dumper_test.rb      | 24 +++++++++++++++++++---
 activerecord/test/schema/schema.rb                 |  2 ++
 9 files changed, 67 insertions(+), 33 deletions(-)

diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 88ef8e0819..86406d95ad 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -6,7 +6,7 @@ module ActiveRecord
     # this type are typically created and returned by methods in database
     # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
     class IndexDefinition # :nodoc:
-      attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :opclass, :comment
+      attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :comment
 
       def initialize(
         table, name,
@@ -14,24 +14,33 @@ module ActiveRecord
         columns = [],
         lengths: {},
         orders: {},
+        opclasses: {},
         where: nil,
         type: nil,
         using: nil,
-        opclass: {},
         comment: nil
       )
         @table = table
         @name = name
         @unique = unique
         @columns = columns
-        @lengths = lengths
-        @orders = orders
+        @lengths = concise_options(lengths)
+        @orders = concise_options(orders)
+        @opclasses = concise_options(opclasses)
         @where = where
         @type = type
         @using = using
-        @opclass = opclass
         @comment = comment
       end
+
+      private
+        def concise_options(options)
+          if columns.size == options.size && options.values.uniq.size == 1
+            options.values.first
+          else
+            options
+          end
+        end
     end
 
     # Abstract representation of a column definition. Instances of this type
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index fac9e1cca2..efc934c34c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1202,7 +1202,7 @@ module ActiveRecord
             when Hash
               order = order.symbolize_keys
               quoted_columns.each { |name, column| column << " #{order[name].upcase}" if order[name].present? }
-            when String
+            else
               quoted_columns.each { |name, column| column << " #{order.upcase}" if order.present? }
             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 143dc6d08d..41c628302d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -611,7 +611,7 @@ module ActiveRecord
             when Hash
               length = length.symbolize_keys
               quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? }
-            when Integer
+            else
               quoted_columns.each { |name, column| column << "(#{length})" }
             end
           end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index a15c7d1787..d5b2c18ee6 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -22,23 +22,26 @@ module ActiveRecord
                   index_using = mysql_index_type
                 end
 
-                indexes << IndexDefinition.new(
+                indexes << [
                   row[:Table],
                   row[:Key_name],
                   row[:Non_unique].to_i == 0,
+                  [],
+                  lengths: {},
+                  orders: {},
                   type: index_type,
                   using: index_using,
                   comment: row[:Index_comment].presence
-                )
+                ]
               end
 
-              indexes.last.columns << row[:Column_name]
-              indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
-              indexes.last.orders.merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
+              indexes.last[-2] << row[:Column_name]
+              indexes.last[-1][:lengths].merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
+              indexes.last[-1][:orders].merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
             end
           end
 
-          indexes
+          indexes.map { |index| IndexDefinition.new(*index) }
         end
 
         def remove_column(table_name, column_name, type = nil, options = {})
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 64f08e0ee1..7b4d5711cb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -109,6 +109,9 @@ module ActiveRecord
 
             using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
 
+            orders = {}
+            opclasses = {}
+
             if indkey.include?(0)
               columns = expressions
             else
@@ -119,21 +122,12 @@ module ActiveRecord
                 AND a.attnum IN (#{indkey.join(",")})
               SQL
 
-              orders = {}
-              opclasses = {}
-
               # add info on sort order (only desc order is explicitly specified, asc is the default)
               # and non-default opclasses
               expressions.scan(/(\w+)(?: (?!DESC)(\w+))?(?: (DESC))?/).each do |column, opclass, desc|
-                opclasses[column] = opclass if opclass
+                opclasses[column] = opclass.to_sym if opclass
                 orders[column] = :desc if desc
               end
-
-              # Use a string for the opclass description (instead of a hash) when all columns
-              # have the same opclass specified.
-              if columns.count == opclasses.count && opclasses.values.uniq.count == 1
-                opclasses = opclasses.values.first
-              end
             end
 
             IndexDefinition.new(
@@ -142,9 +136,9 @@ module ActiveRecord
               unique,
               columns,
               orders: orders,
+              opclasses: opclasses,
               where: where,
               using: using.to_sym,
-              opclass: opclasses,
               comment: comment.presence
             )
           end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 8cb2851557..16ccba6b6c 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -184,12 +184,12 @@ HEADER
           "name: #{index.name.inspect}",
         ]
         index_parts << "unique: true" if index.unique
-        index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present?
-        index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present?
+        index_parts << "length: #{format_index_parts(index.lengths)}" if index.lengths.present?
+        index_parts << "order: #{format_index_parts(index.orders)}" if index.orders.present?
+        index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present?
         index_parts << "where: #{index.where.inspect}" if index.where
         index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
         index_parts << "type: #{index.type.inspect}" if index.type
-        index_parts << "opclass: #{index.opclass.inspect}" if index.opclass.present?
         index_parts << "comment: #{index.comment.inspect}" if index.comment
         index_parts
       end
@@ -232,6 +232,14 @@ HEADER
         options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
       end
 
+      def format_index_parts(options)
+        if options.is_a?(Hash)
+          "{ #{format_options(options)} }"
+        else
+          options.inspect
+        end
+      end
+
       def remove_prefix_and_suffix(table)
         prefix = Regexp.escape(@options[:table_name_prefix].to_s)
         suffix = Regexp.escape(@options[:table_name_suffix].to_s)
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index a6ae18a826..1126908761 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -459,7 +459,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
         assert_equal :btree, index_d.using
         assert_equal :gin,   index_e.using
 
-        assert_equal :desc,  index_d.orders[INDEX_D_COLUMN]
+        assert_equal :desc,  index_d.orders
       end
     end
 
@@ -520,7 +520,7 @@ class SchemaIndexOpclassTest < ActiveRecord::PostgreSQLTestCase
 
     output = dump_table_schema "trains"
 
-    assert_match(/opclass: "text_pattern_ops"/, output)
+    assert_match(/opclass: :text_pattern_ops/, output)
   end
 
   def test_non_default_opclass_is_dumped
@@ -528,7 +528,7 @@ class SchemaIndexOpclassTest < ActiveRecord::PostgreSQLTestCase
 
     output = dump_table_schema "trains"
 
-    assert_match(/opclass: \{"description"=>"text_pattern_ops"\}/, output)
+    assert_match(/opclass: \{ description: :text_pattern_ops \}/, output)
   end
 end
 
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index ac5092f1c1..a612ce9bb2 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -177,14 +177,14 @@ class SchemaDumperTest < ActiveRecord::TestCase
 
   def test_schema_dumps_index_columns_in_right_order
     index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_index/).first.strip
-    if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter)
-      assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }', index_definition
-    elsif current_adapter?(:Mysql2Adapter)
+    if current_adapter?(:Mysql2Adapter)
       if ActiveRecord::Base.connection.supports_index_sort_order?
         assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, order: { rating: :desc }', index_definition
       else
         assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }', index_definition
       end
+    elsif ActiveRecord::Base.connection.supports_index_sort_order?
+      assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }', index_definition
     else
       assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
     end
@@ -199,6 +199,24 @@ class SchemaDumperTest < ActiveRecord::TestCase
     end
   end
 
+  def test_schema_dumps_index_sort_order
+    index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*_name_and_rating/).first.strip
+    if ActiveRecord::Base.connection.supports_index_sort_order?
+      assert_equal 't.index ["name", "rating"], name: "index_companies_on_name_and_rating", order: :desc', index_definition
+    else
+      assert_equal 't.index ["name", "rating"], name: "index_companies_on_name_and_rating"', index_definition
+    end
+  end
+
+  def test_schema_dumps_index_length
+    index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*_name_and_description/).first.strip
+    if current_adapter?(:Mysql2Adapter)
+      assert_equal 't.index ["name", "description"], name: "index_companies_on_name_and_description", length: 10', index_definition
+    else
+      assert_equal 't.index ["name", "description"], name: "index_companies_on_name_and_description"', index_definition
+    end
+  end
+
   def test_schema_dump_should_honor_nonstandard_primary_keys
     output = standard_dump
     match = output.match(%r{create_table "movies"(.*)do})
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index a4505a4892..bf66846840 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -205,6 +205,8 @@ ActiveRecord::Schema.define do
     t.bigint :rating, default: 1
     t.integer :account_id
     t.string :description, default: ""
+    t.index [:name, :rating], order: :desc
+    t.index [:name, :description], length: 10
     t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc }
     t.index [:firm_id, :type], name: "company_partial_index", where: "(rating > 10)"
     t.index :name, name: "company_name_index", using: :btree
-- 
cgit v1.2.3


From 3040446cece8e7a6d9e29219e636e13f180a1e03 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Sun, 3 Dec 2017 06:33:56 +0900
Subject: Fix warning: assigned but unused variable - t

---
 activerecord/test/cases/migration/compatibility_test.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 0fd55e6ae4..f1defe1a8a 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -138,7 +138,7 @@ module ActiveRecord
             end
           }.new
 
-          t = Testing.create!
+          Testing.create!
           ActiveRecord::Migrator.new(:up, [migration]).migrate
           assert_equal ["foobar"], Testing.all.map(&:foo)
         end
-- 
cgit v1.2.3


From 33a3f7123bc4cc49b99b01a40bbfd463b2e73f76 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Sun, 3 Dec 2017 07:08:30 +0900
Subject: Extract duplicated index column options normalization as
 `options_for_index_columns`

And placed `add_options_for_index_columns` in `schema_statements.rb`
consistently to ease to find related code.
---
 .../connection_adapters/abstract/schema_statements.rb | 19 ++++++++++---------
 .../connection_adapters/abstract_mysql_adapter.rb     | 19 -------------------
 .../connection_adapters/mysql/schema_statements.rb    | 12 ++++++++++++
 .../postgresql/schema_statements.rb                   | 12 ++++++++++++
 .../connection_adapters/postgresql_adapter.rb         | 18 ------------------
 5 files changed, 34 insertions(+), 46 deletions(-)

diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index efc934c34c..4f58b0242c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1197,17 +1197,18 @@ module ActiveRecord
         end
 
         def add_index_sort_order(quoted_columns, **options)
-          if order = options[:order]
-            case order
-            when Hash
-              order = order.symbolize_keys
-              quoted_columns.each { |name, column| column << " #{order[name].upcase}" if order[name].present? }
-            else
-              quoted_columns.each { |name, column| column << " #{order.upcase}" if order.present? }
-            end
+          orders = options_for_index_columns(options[:order])
+          quoted_columns.each do |name, column|
+            column << " #{orders[name].upcase}" if orders[name].present?
           end
+        end
 
-          quoted_columns
+        def options_for_index_columns(options)
+          if options.is_a?(Hash)
+            options.symbolize_keys
+          else
+            Hash.new { |hash, column| hash[column] = options }
+          end
         end
 
         # Overridden by the MySQL adapter for supporting index lengths and by
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 41c628302d..479131caad 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -605,25 +605,6 @@ module ActiveRecord
           end
         end
 
-        def add_index_length(quoted_columns, **options)
-          if length = options[:length]
-            case length
-            when Hash
-              length = length.symbolize_keys
-              quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? }
-            else
-              quoted_columns.each { |name, column| column << "(#{length})" }
-            end
-          end
-
-          quoted_columns
-        end
-
-        def add_options_for_index_columns(quoted_columns, **options)
-          quoted_columns = add_index_length(quoted_columns, options)
-          super
-        end
-
         # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
         ER_DUP_ENTRY            = 1062
         ER_NOT_NULL_VIOLATION   = 1048
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index d5b2c18ee6..ce50590651 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -106,6 +106,18 @@ module ActiveRecord
             super unless specifier == "RESTRICT"
           end
 
+          def add_index_length(quoted_columns, **options)
+            lengths = options_for_index_columns(options[:length])
+            quoted_columns.each do |name, column|
+              column << "(#{lengths[name]})" if lengths[name].present?
+            end
+          end
+
+          def add_options_for_index_columns(quoted_columns, **options)
+            quoted_columns = add_index_length(quoted_columns, options)
+            super
+          end
+
           def data_source_sql(name = nil, type: nil)
             scope = quoted_scope(name, type: type)
 
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 7b4d5711cb..2e9e74403b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -693,6 +693,18 @@ module ActiveRecord
             "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
           end
 
+          def add_index_opclass(quoted_columns, **options)
+            opclasses = options_for_index_columns(options[:opclass])
+            quoted_columns.each do |name, column|
+              column << " #{opclasses[name]}" if opclasses[name].present?
+            end
+          end
+
+          def add_options_for_index_columns(quoted_columns, **options)
+            quoted_columns = add_index_opclass(quoted_columns, options)
+            super
+          end
+
           def data_source_sql(name = nil, type: nil)
             scope = quoted_scope(name, type: type)
             scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 68b79f777e..23fc69d649 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -390,24 +390,6 @@ module ActiveRecord
       end
 
       private
-
-        def add_index_opclass(column_names, options = {})
-          opclass =
-            if options[:opclass].is_a?(Hash)
-              options[:opclass].symbolize_keys
-            else
-              Hash.new { |hash, column| hash[column] = options[:opclass].to_s }
-            end
-          column_names.each do |name, column|
-            column << " #{opclass[name]}" if opclass[name].present?
-          end
-        end
-
-        def add_options_for_index_columns(quoted_columns, **options)
-          quoted_columns = add_index_opclass(quoted_columns, options)
-          super
-        end
-
         # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
         VALUE_LIMIT_VIOLATION = "22001"
         NUMERIC_VALUE_OUT_OF_RANGE = "22003"
-- 
cgit v1.2.3


From 5358f2b67bd6fb12d708527a4a70dcab65513c5e Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Sun, 3 Dec 2017 07:55:34 +0900
Subject: Fix `test_add_column_with_timestamp_type` failure

This test failed due to dirty schema cache. It is needed to call
`clear_cache!` when using same named table with different definition.

https://travis-ci.org/rails/rails/jobs/310627767#L769-L772
---
 activerecord/test/cases/migration/change_schema_test.rb | 13 ++++++-------
 activerecord/test/cases/migration/compatibility_test.rb |  2 ++
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 7b0644e9c0..43aac5da75 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -264,19 +264,18 @@ module ActiveRecord
           t.column :foo, :timestamp
         end
 
-        klass = Class.new(ActiveRecord::Base)
-        klass.table_name = "testings"
+        column = connection.columns(:testings).find { |c| c.name == "foo" }
 
-        assert_equal :datetime, klass.columns_hash["foo"].type
+        assert_equal :datetime, column.type
 
         if current_adapter?(:PostgreSQLAdapter)
-          assert_equal "timestamp without time zone", klass.columns_hash["foo"].sql_type
+          assert_equal "timestamp without time zone", column.sql_type
         elsif current_adapter?(:Mysql2Adapter)
-          assert_equal "timestamp", klass.columns_hash["foo"].sql_type
+          assert_equal "timestamp", column.sql_type
         elsif current_adapter?(:OracleAdapter)
-          assert_equal "TIMESTAMP(6)", klass.columns_hash["foo"].sql_type
+          assert_equal "TIMESTAMP(6)", column.sql_type
         else
-          assert_equal klass.connection.type_to_sql("datetime"), klass.columns_hash["foo"].sql_type
+          assert_equal klass.connection.type_to_sql("datetime"), column.sql_type
         end
       end
 
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index f1defe1a8a..e6ca252369 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -141,6 +141,8 @@ module ActiveRecord
           Testing.create!
           ActiveRecord::Migrator.new(:up, [migration]).migrate
           assert_equal ["foobar"], Testing.all.map(&:foo)
+        ensure
+          ActiveRecord::Base.clear_cache!
         end
       end
     end
-- 
cgit v1.2.3


From 32516983a67f10d513370edd67b41ef0e011b175 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Sun, 3 Dec 2017 08:05:32 +0900
Subject: Fix `s/klass.connection/connection/`

`klass` has removed in 5358f2b67bd6fb12d708527a4a70dcab65513c5e.
---
 activerecord/test/cases/migration/change_schema_test.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 43aac5da75..38a906c8f5 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -275,7 +275,7 @@ module ActiveRecord
         elsif current_adapter?(:OracleAdapter)
           assert_equal "TIMESTAMP(6)", column.sql_type
         else
-          assert_equal klass.connection.type_to_sql("datetime"), column.sql_type
+          assert_equal connection.type_to_sql("datetime"), column.sql_type
         end
       end
 
-- 
cgit v1.2.3


From dbee80bca0ef504120219e6c7686437456511060 Mon Sep 17 00:00:00 2001
From: "yuuji.yaginuma" <yuuji.yaginuma@gmail.com>
Date: Fri, 1 Dec 2017 18:16:06 +0900
Subject: Make `Migrator.current_version` work without a current database

This is necessary in order to make the processing dependent on
`Migrator.current_version` work even without database.

Context: https://github.com/rails/rails/pull/31135#issuecomment-348404326
---
 activerecord/lib/active_record/migration.rb | 10 +++++++++-
 activerecord/lib/active_record/railtie.rb   |  7 +++++--
 railties/test/application/rake/dbs_test.rb  | 14 ++++++++++++++
 railties/test/isolation/abstract_unit.rb    | 15 +++++++++++++++
 4 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 15e9c09ffb..4d4b0dc67a 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1053,7 +1053,15 @@ module ActiveRecord
         end
       end
 
-      def current_version(connection = Base.connection)
+      def current_version(connection = nil)
+        if connection.nil?
+          begin
+            connection = Base.connection
+          rescue ActiveRecord::NoDatabaseError
+            return nil
+          end
+        end
+
         get_all_versions(connection).max || 0
       end
 
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 9ee8425e1b..4538ed6a5f 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -90,12 +90,15 @@ module ActiveRecord
             filename = File.join(app.config.paths["db"].first, "schema_cache.yml")
 
             if File.file?(filename)
+              current_version = ActiveRecord::Migrator.current_version
+              next if current_version.nil?
+
               cache = YAML.load(File.read(filename))
-              if cache.version == ActiveRecord::Migrator.current_version
+              if cache.version == current_version
                 connection.schema_cache = cache
                 connection_pool.schema_cache = cache.dup
               else
-                warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."
+                warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}."
               end
             end
           end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 0235210fdd..2082e9fa9f 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -98,6 +98,20 @@ module ApplicationTests
         end
       end
 
+      test "db:create works when schema cache exists and database does not exist" do
+        use_postgresql
+
+        begin
+          rails %w(db:create db:migrate db:schema:cache:dump)
+
+          rails "db:drop"
+          rails "db:create"
+          assert_equal 0, $?.exitstatus
+        ensure
+          rails "db:drop" rescue nil
+        end
+      end
+
       test "db:drop failure because database does not exist" do
         output = rails("db:drop:_unsafe", "--trace")
         assert_match(/does not exist/, output)
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 7522237a38..5b1c06d4e5 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -381,6 +381,21 @@ module TestHelpers
 
       $:.reject! { |path| path =~ %r'/(#{to_remove.join('|')})/' }
     end
+
+    def use_postgresql
+      File.open("#{app_path}/config/database.yml", "w") do |f|
+        f.puts <<-YAML
+        default: &default
+          adapter: postgresql
+          pool: 5
+          database: railties_test
+        development:
+          <<: *default
+        test:
+          <<: *default
+        YAML
+      end
+    end
   end
 end
 
-- 
cgit v1.2.3


From 8c5a7fbefd3cad403e7594d0b6a5488d80d4c98e Mon Sep 17 00:00:00 2001
From: George Claghorn <george.claghorn@gmail.com>
Date: Sat, 2 Dec 2017 22:43:28 -0500
Subject: Purge variants with their blobs

---
 activestorage/app/models/active_storage/blob.rb    |  3 ++-
 activestorage/lib/active_storage/log_subscriber.rb |  4 +++
 activestorage/lib/active_storage/service.rb        |  9 +++++--
 .../service/azure_storage_service.rb               | 30 +++++++++++++++++-----
 .../lib/active_storage/service/disk_service.rb     | 22 +++++++++++-----
 .../lib/active_storage/service/gcs_service.rb      | 18 ++++++++-----
 .../lib/active_storage/service/mirror_service.rb   |  5 ++++
 .../lib/active_storage/service/s3_service.rb       | 20 ++++++++++-----
 activestorage/test/models/blob_test.rb             | 10 +++++++-
 activestorage/test/service/shared_service_tests.rb | 17 ++++++++++++
 10 files changed, 107 insertions(+), 31 deletions(-)

diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index 2aa05d665e..acaf22fac1 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -270,7 +270,8 @@ class ActiveStorage::Blob < ActiveRecord::Base
   # deleted as well or you will essentially have a dead reference. It's recommended to use the +#purge+ and +#purge_later+
   # methods in most circumstances.
   def delete
-    service.delete key
+    service.delete(key)
+    service.delete_prefixed("variants/#{key}/") if image?
   end
 
   # Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted
diff --git a/activestorage/lib/active_storage/log_subscriber.rb b/activestorage/lib/active_storage/log_subscriber.rb
index 5cbf4bd1a5..a4e148c1a5 100644
--- a/activestorage/lib/active_storage/log_subscriber.rb
+++ b/activestorage/lib/active_storage/log_subscriber.rb
@@ -18,6 +18,10 @@ module ActiveStorage
       info event, color("Deleted file from key: #{key_in(event)}", RED)
     end
 
+    def service_delete_prefixed(event)
+      info event, color("Deleted files by key prefix: #{event.payload[:prefix]}", RED)
+    end
+
     def service_exist(event)
       debug event, color("Checked if file exists at key: #{key_in(event)} (#{event.payload[:exist] ? "yes" : "no"})", BLUE)
     end
diff --git a/activestorage/lib/active_storage/service.rb b/activestorage/lib/active_storage/service.rb
index aa150e4d8a..c8f675db86 100644
--- a/activestorage/lib/active_storage/service.rb
+++ b/activestorage/lib/active_storage/service.rb
@@ -78,6 +78,11 @@ module ActiveStorage
       raise NotImplementedError
     end
 
+    # Delete files at keys starting with the +prefix+.
+    def delete_prefixed(prefix)
+      raise NotImplementedError
+    end
+
     # Return +true+ if a file exists at the +key+.
     def exist?(key)
       raise NotImplementedError
@@ -104,10 +109,10 @@ module ActiveStorage
     end
 
     private
-      def instrument(operation, key, payload = {}, &block)
+      def instrument(operation, payload = {}, &block)
         ActiveSupport::Notifications.instrument(
           "service_#{operation}.active_storage",
-          payload.merge(key: key, service: service_name), &block)
+          payload.merge(service: service_name), &block)
       end
 
       def service_name
diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb
index f3877ad9c9..98de0836de 100644
--- a/activestorage/lib/active_storage/service/azure_storage_service.rb
+++ b/activestorage/lib/active_storage/service/azure_storage_service.rb
@@ -19,7 +19,7 @@ module ActiveStorage
     end
 
     def upload(key, io, checksum: nil)
-      instrument :upload, key, checksum: checksum do
+      instrument :upload, key: key, checksum: checksum do
         begin
           blobs.create_block_blob(container, key, io, content_md5: checksum)
         rescue Azure::Core::Http::HTTPError
@@ -30,11 +30,11 @@ module ActiveStorage
 
     def download(key, &block)
       if block_given?
-        instrument :streaming_download, key do
+        instrument :streaming_download, key: key do
           stream(key, &block)
         end
       else
-        instrument :download, key do
+        instrument :download, key: key do
           _, io = blobs.get_blob(container, key)
           io.force_encoding(Encoding::BINARY)
         end
@@ -42,7 +42,7 @@ module ActiveStorage
     end
 
     def delete(key)
-      instrument :delete, key do
+      instrument :delete, key: key do
         begin
           blobs.delete_blob(container, key)
         rescue Azure::Core::Http::HTTPError
@@ -51,8 +51,24 @@ module ActiveStorage
       end
     end
 
+    def delete_prefixed(prefix)
+      instrument :delete_all, prefix: prefix do
+        marker = nil
+
+        loop do
+          results = blobs.list_blobs(container, prefix: prefix, marker: marker)
+
+          results.each do |blob|
+            blobs.delete_blob(container, blob.name)
+          end
+
+          break unless marker = results.continuation_token.presence
+        end
+      end
+    end
+
     def exist?(key)
-      instrument :exist, key do |payload|
+      instrument :exist, key: key do |payload|
         answer = blob_for(key).present?
         payload[:exist] = answer
         answer
@@ -60,7 +76,7 @@ module ActiveStorage
     end
 
     def url(key, expires_in:, filename:, disposition:, content_type:)
-      instrument :url, key do |payload|
+      instrument :url, key: key do |payload|
         base_url = url_for(key)
         generated_url = signer.signed_uri(
           URI(base_url), false,
@@ -77,7 +93,7 @@ module ActiveStorage
     end
 
     def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
-      instrument :url, key do |payload|
+      instrument :url, key: key do |payload|
         base_url = url_for(key)
         generated_url = signer.signed_uri(URI(base_url), false, permissions: "rw",
           expiry: format_expiry(expires_in)).to_s
diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb
index 52eaba4e7b..a8728c5bc3 100644
--- a/activestorage/lib/active_storage/service/disk_service.rb
+++ b/activestorage/lib/active_storage/service/disk_service.rb
@@ -16,7 +16,7 @@ module ActiveStorage
     end
 
     def upload(key, io, checksum: nil)
-      instrument :upload, key, checksum: checksum do
+      instrument :upload, key: key, checksum: checksum do
         IO.copy_stream(io, make_path_for(key))
         ensure_integrity_of(key, checksum) if checksum
       end
@@ -24,7 +24,7 @@ module ActiveStorage
 
     def download(key)
       if block_given?
-        instrument :streaming_download, key do
+        instrument :streaming_download, key: key do
           File.open(path_for(key), "rb") do |file|
             while data = file.read(64.kilobytes)
               yield data
@@ -32,14 +32,14 @@ module ActiveStorage
           end
         end
       else
-        instrument :download, key do
+        instrument :download, key: key do
           File.binread path_for(key)
         end
       end
     end
 
     def delete(key)
-      instrument :delete, key do
+      instrument :delete, key: key do
         begin
           File.delete path_for(key)
         rescue Errno::ENOENT
@@ -48,8 +48,16 @@ module ActiveStorage
       end
     end
 
+    def delete_prefixed(prefix)
+      instrument :delete_prefixed, prefix: prefix do
+        Dir.glob(path_for("#{prefix}*")).each do |path|
+          FileUtils.rm_rf(path)
+        end
+      end
+    end
+
     def exist?(key)
-      instrument :exist, key do |payload|
+      instrument :exist, key: key do |payload|
         answer = File.exist? path_for(key)
         payload[:exist] = answer
         answer
@@ -57,7 +65,7 @@ module ActiveStorage
     end
 
     def url(key, expires_in:, filename:, disposition:, content_type:)
-      instrument :url, key do |payload|
+      instrument :url, key: key do |payload|
         verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key)
 
         generated_url =
@@ -77,7 +85,7 @@ module ActiveStorage
     end
 
     def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
-      instrument :url, key do |payload|
+      instrument :url, key: key do |payload|
         verified_token_with_expiration = ActiveStorage.verifier.generate(
           {
             key: key,
diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb
index fd9916634a..c13ce4786d 100644
--- a/activestorage/lib/active_storage/service/gcs_service.rb
+++ b/activestorage/lib/active_storage/service/gcs_service.rb
@@ -14,7 +14,7 @@ module ActiveStorage
     end
 
     def upload(key, io, checksum: nil)
-      instrument :upload, key, checksum: checksum do
+      instrument :upload, key: key, checksum: checksum do
         begin
           bucket.create_file(io, key, md5: checksum)
         rescue Google::Cloud::InvalidArgumentError
@@ -25,7 +25,7 @@ module ActiveStorage
 
     # FIXME: Download in chunks when given a block.
     def download(key)
-      instrument :download, key do
+      instrument :download, key: key do
         io = file_for(key).download
         io.rewind
 
@@ -38,7 +38,7 @@ module ActiveStorage
     end
 
     def delete(key)
-      instrument :delete, key do
+      instrument :delete, key: key do
         begin
           file_for(key).delete
         rescue Google::Cloud::NotFoundError
@@ -47,8 +47,14 @@ module ActiveStorage
       end
     end
 
+    def delete_prefixed(prefix)
+      instrument :delete_prefixed, prefix: prefix do
+        bucket.files(prefix: prefix).all(&:delete)
+      end
+    end
+
     def exist?(key)
-      instrument :exist, key do |payload|
+      instrument :exist, key: key do |payload|
         answer = file_for(key).exists?
         payload[:exist] = answer
         answer
@@ -56,7 +62,7 @@ module ActiveStorage
     end
 
     def url(key, expires_in:, filename:, content_type:, disposition:)
-      instrument :url, key do |payload|
+      instrument :url, key: key do |payload|
         generated_url = file_for(key).signed_url expires: expires_in, query: {
           "response-content-disposition" => content_disposition_with(type: disposition, filename: filename),
           "response-content-type" => content_type
@@ -69,7 +75,7 @@ module ActiveStorage
     end
 
     def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
-      instrument :url, key do |payload|
+      instrument :url, key: key do |payload|
         generated_url = bucket.signed_url key, method: "PUT", expires: expires_in,
           content_type: content_type, content_md5: checksum
 
diff --git a/activestorage/lib/active_storage/service/mirror_service.rb b/activestorage/lib/active_storage/service/mirror_service.rb
index 39e922f7ab..7eca8ce7f4 100644
--- a/activestorage/lib/active_storage/service/mirror_service.rb
+++ b/activestorage/lib/active_storage/service/mirror_service.rb
@@ -35,6 +35,11 @@ module ActiveStorage
       perform_across_services :delete, key
     end
 
+    # Delete files at keys starting with the +prefix+ on all services.
+    def delete_prefixed(prefix)
+      perform_across_services :delete_prefixed, prefix
+    end
+
     private
       def each_service(&block)
         [ primary, *mirrors ].each(&block)
diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb
index 6957119780..c95672f338 100644
--- a/activestorage/lib/active_storage/service/s3_service.rb
+++ b/activestorage/lib/active_storage/service/s3_service.rb
@@ -17,7 +17,7 @@ module ActiveStorage
     end
 
     def upload(key, io, checksum: nil)
-      instrument :upload, key, checksum: checksum do
+      instrument :upload, key: key, checksum: checksum do
         begin
           object_for(key).put(upload_options.merge(body: io, content_md5: checksum))
         rescue Aws::S3::Errors::BadDigest
@@ -28,24 +28,30 @@ module ActiveStorage
 
     def download(key, &block)
       if block_given?
-        instrument :streaming_download, key do
+        instrument :streaming_download, key: key do
           stream(key, &block)
         end
       else
-        instrument :download, key do
+        instrument :download, key: key do
           object_for(key).get.body.read.force_encoding(Encoding::BINARY)
         end
       end
     end
 
     def delete(key)
-      instrument :delete, key do
+      instrument :delete, key: key do
         object_for(key).delete
       end
     end
 
+    def delete_prefixed(prefix)
+      instrument :delete_prefixed, prefix: prefix do
+        bucket.objects(prefix: prefix).batch_delete!
+      end
+    end
+
     def exist?(key)
-      instrument :exist, key do |payload|
+      instrument :exist, key: key do |payload|
         answer = object_for(key).exists?
         payload[:exist] = answer
         answer
@@ -53,7 +59,7 @@ module ActiveStorage
     end
 
     def url(key, expires_in:, filename:, disposition:, content_type:)
-      instrument :url, key do |payload|
+      instrument :url, key: key do |payload|
         generated_url = object_for(key).presigned_url :get, expires_in: expires_in.to_i,
           response_content_disposition: content_disposition_with(type: disposition, filename: filename),
           response_content_type: content_type
@@ -65,7 +71,7 @@ module ActiveStorage
     end
 
     def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
-      instrument :url, key do |payload|
+      instrument :url, key: key do |payload|
         generated_url = object_for(key).presigned_url :put, expires_in: expires_in.to_i,
           content_type: content_type, content_length: content_length, content_md5: checksum
 
diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb
index 6e815997ba..f94e65ed77 100644
--- a/activestorage/test/models/blob_test.rb
+++ b/activestorage/test/models/blob_test.rb
@@ -41,13 +41,21 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
     end
   end
 
-  test "purge removes from external service" do
+  test "purge deletes file from external service" do
     blob = create_blob
 
     blob.purge
     assert_not ActiveStorage::Blob.service.exist?(blob.key)
   end
 
+  test "purge deletes variants from external service" do
+    blob = create_file_blob
+    variant = blob.variant(resize: "100>").processed
+
+    blob.purge
+    assert_not ActiveStorage::Blob.service.exist?(variant.key)
+  end
+
   private
     def expected_url_for(blob, disposition: :inline)
       query_string = { content_type: blob.content_type, disposition: "#{disposition}; #{blob.filename.parameters}" }.to_param
diff --git a/activestorage/test/service/shared_service_tests.rb b/activestorage/test/service/shared_service_tests.rb
index ade91ab89a..ce28c4393a 100644
--- a/activestorage/test/service/shared_service_tests.rb
+++ b/activestorage/test/service/shared_service_tests.rb
@@ -75,5 +75,22 @@ module ActiveStorage::Service::SharedServiceTests
         @service.delete SecureRandom.base58(24)
       end
     end
+
+    test "deleting by prefix" do
+      begin
+        @service.upload("a/a/a", StringIO.new(FIXTURE_DATA))
+        @service.upload("a/a/b", StringIO.new(FIXTURE_DATA))
+        @service.upload("a/b/a", StringIO.new(FIXTURE_DATA))
+
+        @service.delete_prefixed("a/a/")
+        assert_not @service.exist?("a/a/a")
+        assert_not @service.exist?("a/a/b")
+        assert @service.exist?("a/b/a")
+      ensure
+        @service.delete("a/a/a")
+        @service.delete("a/a/b")
+        @service.delete("a/b/a")
+      end
+    end
   end
 end
-- 
cgit v1.2.3


From d0f5dce492696019ddf409892829f89bee5f45ef Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Sun, 3 Dec 2017 15:00:05 +0900
Subject: `change_column_default` should be executed after type changing

If do not execute a type changing first, filling in default value may be
failed.

```
% ARCONN=postgresql be ruby -w -Itest test/cases/migration/compatibility_test.rb -n test_legacy_change_column_with_null_executes_update
Using postgresql
Run options: -n test_legacy_change_column_with_null_executes_update --seed 20459

E

Error:
ActiveRecord::Migration::CompatibilityTest#test_legacy_change_column_with_null_executes_update:
StandardError: An error has occurred, this and all later migrations canceled:

PG::StringDataRightTruncation: ERROR:  value too long for type character varying(5)
: UPDATE "testings" SET "foo"='foobar' WHERE "foo" IS NULL
```
---
 .../connection_adapters/postgresql/schema_statements.rb | 15 ++++++++-------
 .../lib/active_record/migration/compatibility.rb        | 17 ++++++++++-------
 activerecord/test/cases/migration/compatibility_test.rb |  4 ++--
 3 files changed, 20 insertions(+), 16 deletions(-)

diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 2e9e74403b..bf5fbb30e1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -393,9 +393,9 @@ module ActiveRecord
         end
 
         def change_column(table_name, column_name, type, options = {}) #:nodoc:
-          quoted_table_name = quote_table_name(table_name)
+          clear_cache!
           sqls, procs = change_column_for_alter(table_name, column_name, type, options)
-          execute "ALTER TABLE #{quoted_table_name} #{sqls.join(", ")}"
+          execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
           procs.each(&:call)
         end
 
@@ -646,8 +646,7 @@ module ActiveRecord
             end
           end
 
-          def change_column_for_alter(table_name, column_name, type, options = {})
-            clear_cache!
+          def change_column_sql(table_name, column_name, type, options = {})
             quoted_column_name = quote_column_name(column_name)
             sql_type = type_to_sql(type, options)
             sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
@@ -661,7 +660,11 @@ module ActiveRecord
               sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
             end
 
-            sqls = [sql]
+            sql
+          end
+
+          def change_column_for_alter(table_name, column_name, type, options = {})
+            sqls = [change_column_sql(table_name, column_name, type, options)]
             procs = []
             sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
             sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
@@ -673,7 +676,6 @@ module ActiveRecord
 
           # Changes the default value of a table column.
           def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
-            clear_cache!
             column = column_for(table_name, column_name)
             return unless column
 
@@ -689,7 +691,6 @@ module ActiveRecord
           end
 
           def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc:
-            clear_cache!
             "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
           end
 
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index da307c1723..bd8c054c28 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -16,13 +16,16 @@ module ActiveRecord
       V5_2 = Current
 
       class V5_1 < V5_2
-        if adapter_name == "PostgreSQL"
-          def change_column(table_name, column_name, type, options = {})
-            unless options[:null] || options[:default].nil?
-              column = connection.send(:column_for, table_name, column_name)
-              connection.execute("UPDATE #{connection.quote_table_name(table_name)} SET #{connection.quote_column_name(column_name)}=#{connection.quote_default_expression(options[:default], column)} WHERE #{connection.quote_column_name(column_name)} IS NULL") if column
-            end
-            connection.change_column(table_name, column_name, type, options)
+        def change_column(table_name, column_name, type, options = {})
+          if adapter_name == "PostgreSQL"
+            clear_cache!
+            sql = connection.send(:change_column_sql, table_name, column_name, type, options)
+            execute "ALTER TABLE #{quote_table_name(table_name)} #{sql}"
+            change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
+            change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+            change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
+          else
+            super
           end
         end
       end
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index e6ca252369..cc2391f349 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -16,7 +16,7 @@ module ActiveRecord
         ActiveRecord::Migration.verbose = false
 
         connection.create_table :testings do |t|
-          t.column :foo, :string, limit: 100
+          t.column :foo, :string, limit: 5
           t.column :bar, :string, limit: 100
         end
       end
@@ -134,7 +134,7 @@ module ActiveRecord
         def test_legacy_change_column_with_null_executes_update
           migration = Class.new(ActiveRecord::Migration[5.1]) {
             def migrate(x)
-              change_column :testings, :foo, :string, null: false, default: "foobar"
+              change_column :testings, :foo, :string, limit: 10, null: false, default: "foobar"
             end
           }.new
 
-- 
cgit v1.2.3


From 70fa9e9ab7fd89589664ecd7ee367448ef45f9d8 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Sun, 3 Dec 2017 15:28:10 +0900
Subject: Emulate JSON types for SQLite3 adapter (#29664)

Actually SQLite3 doesn't have JSON storage class (so it is stored as a
TEXT like Date and Time). But emulating JSON types is convinient for
making database agnostic migrations.
---
 .../connection_adapters/abstract/schema_definitions.rb             | 1 +
 .../active_record/connection_adapters/mysql/schema_definitions.rb  | 4 ----
 .../connection_adapters/postgresql/schema_definitions.rb           | 4 ----
 .../lib/active_record/connection_adapters/sqlite3_adapter.rb       | 7 ++++++-
 activerecord/test/cases/adapters/sqlite3/json_test.rb              | 4 ++--
 activerecord/test/cases/json_shared_test_cases.rb                  | 2 --
 6 files changed, 9 insertions(+), 13 deletions(-)

diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 86406d95ad..0594b4b485 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -220,6 +220,7 @@ module ActiveRecord
         :decimal,
         :float,
         :integer,
+        :json,
         :string,
         :text,
         :time,
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
index da25e4863c..2ed4ad16ae 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -32,10 +32,6 @@ module ActiveRecord
           args.each { |name| column(name, :longtext, options) }
         end
 
-        def json(*args, **options)
-          args.each { |name| column(name, :json, options) }
-        end
-
         def unsigned_integer(*args, **options)
           args.each { |name| column(name, :unsigned_integer, options) }
         end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index a3bb66bf3e..6047217fcd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -95,10 +95,6 @@ module ActiveRecord
           args.each { |name| column(name, :int8range, options) }
         end
 
-        def json(*args, **options)
-          args.each { |name| column(name, :json, options) }
-        end
-
         def jsonb(*args, **options)
           args.each { |name| column(name, :jsonb, options) }
         end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index daece2bffd..ff63f63bf7 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -70,7 +70,8 @@ module ActiveRecord
         time:         { name: "time" },
         date:         { name: "date" },
         binary:       { name: "blob" },
-        boolean:      { name: "boolean" }
+        boolean:      { name: "boolean" },
+        json:         { name: "json" },
       }
 
       ##
@@ -134,6 +135,10 @@ module ActiveRecord
         true
       end
 
+      def supports_json?
+        true
+      end
+
       def supports_multi_insert?
         sqlite_version >= "3.7.11"
       end
diff --git a/activerecord/test/cases/adapters/sqlite3/json_test.rb b/activerecord/test/cases/adapters/sqlite3/json_test.rb
index 568a524058..6f247fcd22 100644
--- a/activerecord/test/cases/adapters/sqlite3/json_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/json_test.rb
@@ -9,8 +9,8 @@ class SQLite3JSONTest < ActiveRecord::SQLite3TestCase
   def setup
     super
     @connection.create_table("json_data_type") do |t|
-      t.column "payload", :json, default: {}
-      t.column "settings", :json
+      t.json "payload", default: {}
+      t.json "settings"
     end
   end
 
diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
index a71485982c..56ec8c8a82 100644
--- a/activerecord/test/cases/json_shared_test_cases.rb
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -30,7 +30,6 @@ 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
@@ -41,7 +40,6 @@ 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
-- 
cgit v1.2.3


From 9b823c02b62a91216ef1ad0c9d4fca095377afb6 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Sun, 3 Dec 2017 15:45:40 +0900
Subject: SQLite3 valid integer value should be 8 bytes (64-bit signed integer)
 (#28379)

This is a regression since Rails 4.2.

SQLite3 integer is stored in 1, 2, 3, 4, 6, or 8 bytes depending on the
magnitude of the value. Assuming default valid value as 4 bytes caused
that actual valid value in INTEGER storage class cannot be stored and
existing value cannot be found.

https://www.sqlite.org/datatype3.html

We should allow valid value in INTEGER storage class in SQLite3 to fix
the regression.

Fixes #22594.
---
 .../active_record/connection_adapters/sqlite3_adapter.rb  | 15 +++++++++++++++
 activerecord/test/cases/base_test.rb                      |  8 +++-----
 2 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index ff63f63bf7..574a7b4572 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -374,6 +374,10 @@ module ActiveRecord
       end
 
       private
+        def initialize_type_map(m = type_map)
+          super
+          register_class_with_limit m, %r(int)i, SQLite3Integer
+        end
 
         def table_structure(table_name)
           structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
@@ -529,6 +533,17 @@ module ActiveRecord
         def configure_connection
           execute("PRAGMA foreign_keys = ON", "SCHEMA")
         end
+
+        class SQLite3Integer < Type::Integer # :nodoc:
+          private
+            def _limit
+              # INTEGER storage class can be stored 8 bytes value.
+              # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
+              limit || 8
+            end
+        end
+
+        ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
     end
     ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
   end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index d79afa2ee9..3497f6aae4 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -891,11 +891,9 @@ class BasicsTest < ActiveRecord::TestCase
     assert_equal 2147483648, company.rating
   end
 
-  unless current_adapter?(:SQLite3Adapter)
-    def test_bignum_pk
-      company = Company.create!(id: 2147483648, name: "foo")
-      assert_equal company, Company.find(company.id)
-    end
+  def test_bignum_pk
+    company = Company.create!(id: 2147483648, name: "foo")
+    assert_equal company, Company.find(company.id)
   end
 
   # TODO: extend defaults tests to other databases!
-- 
cgit v1.2.3


From 7609ca08ce5000689838eeb04ed37084bf364f78 Mon Sep 17 00:00:00 2001
From: Kasper Timm Hansen <kaspth@gmail.com>
Date: Sun, 3 Dec 2017 18:06:29 +0100
Subject: Fix instrumention name: delete_prefixed like the others.

---
 activestorage/lib/active_storage/service/azure_storage_service.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb
index 98de0836de..19b09991b3 100644
--- a/activestorage/lib/active_storage/service/azure_storage_service.rb
+++ b/activestorage/lib/active_storage/service/azure_storage_service.rb
@@ -52,7 +52,7 @@ module ActiveStorage
     end
 
     def delete_prefixed(prefix)
-      instrument :delete_all, prefix: prefix do
+      instrument :delete_prefixed, prefix: prefix do
         marker = nil
 
         loop do
-- 
cgit v1.2.3


From c8f014ff1f68bca8deb22f57dd8c7fde9deb7030 Mon Sep 17 00:00:00 2001
From: Kasper Timm Hansen <kaspth@gmail.com>
Date: Sun, 3 Dec 2017 19:12:06 +0100
Subject: Embrace the instantiation in loving parens <3

---
 actionpack/test/dispatch/session/cookie_store_test.rb | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 7f70961465..e34426a471 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -13,8 +13,9 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
   Generator = ActiveSupport::KeyGenerator.new(SessionSecret, iterations: 1000)
   Rotations = ActiveSupport::Messages::RotationConfiguration.new
 
-  Encryptor = ActiveSupport::MessageEncryptor.new \
+  Encryptor = ActiveSupport::MessageEncryptor.new(
     Generator.generate_key(SessionSalt, 32), cipher: "aes-256-gcm", serializer: Marshal
+  )
 
   class TestController < ActionController::Base
     def no_session_access
-- 
cgit v1.2.3


From 20bb26a428b84ae4f7b09bec284152325b0334f6 Mon Sep 17 00:00:00 2001
From: bogdanvlviv <bogdanvlviv@gmail.com>
Date: Mon, 4 Dec 2017 01:18:59 +0200
Subject: Update "Active Record Query Interface" guide [ci skip]

  - Add missing `LIMIT 1` for some queries
  - Make some examples of query more readable
---
 guides/source/active_record_querying.md | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 3786343fc3..4e28e31a53 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -801,7 +801,7 @@ The SQL that would be executed:
 SELECT * FROM articles WHERE id > 10 ORDER BY id DESC
 
 # Original query without `only`
-SELECT "articles".* FROM "articles" WHERE (id > 10) ORDER BY id desc LIMIT 20
+SELECT * FROM articles WHERE id > 10 ORDER BY id DESC LIMIT 20
 
 ```
 
@@ -820,14 +820,14 @@ Article.find(10).comments.reorder('name')
 The SQL that would be executed:
 
 ```sql
-SELECT * FROM articles WHERE id = 10
+SELECT * FROM articles WHERE id = 10 LIMIT 1
 SELECT * FROM comments WHERE article_id = 10 ORDER BY name
 ```
 
 In the case where the `reorder` clause is not used, the SQL executed would be:
 
 ```sql
-SELECT * FROM articles WHERE id = 10
+SELECT * FROM articles WHERE id = 10 LIMIT 1
 SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC
 ```
 
@@ -1091,7 +1091,7 @@ This produces:
 
 ```sql
 SELECT articles.* FROM articles
-  INNER JOIN categories ON articles.category_id = categories.id
+  INNER JOIN categories ON categories.id = articles.category_id
   INNER JOIN comments ON comments.article_id = articles.id
 ```
 
@@ -1871,14 +1871,14 @@ All calculation methods work directly on a model:
 
 ```ruby
 Client.count
-# SELECT count(*) AS count_all FROM clients
+# SELECT COUNT(*) FROM clients
 ```
 
 Or on a relation:
 
 ```ruby
 Client.where(first_name: 'Ryan').count
-# SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan')
+# SELECT COUNT(*) FROM clients WHERE (first_name = 'Ryan')
 ```
 
 You can also use various finder methods on a relation for performing complex calculations:
@@ -1890,9 +1890,9 @@ Client.includes("orders").where(first_name: 'Ryan', orders: { status: 'received'
 Which will execute:
 
 ```sql
-SELECT count(DISTINCT clients.id) AS count_all FROM clients
-  LEFT OUTER JOIN orders ON orders.client_id = clients.id WHERE
-  (clients.first_name = 'Ryan' AND orders.status = 'received')
+SELECT COUNT(DISTINCT clients.id) FROM clients
+  LEFT OUTER JOIN orders ON orders.client_id = clients.id
+  WHERE (clients.first_name = 'Ryan' AND orders.status = 'received')
 ```
 
 ### Count
-- 
cgit v1.2.3


From 07788c7ad8bad797ec97cba038e37e007f343afa Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Mon, 4 Dec 2017 08:18:31 +0900
Subject: `current_version` should catch `NoDatabaseError` from
 `get_all_versions`

`get_all_versions` doesn't use passed `connection`. So it should be
caught `NoDatabaseError` from `SchemaMigration.table_exists?`.
---
 activerecord/lib/active_record/migration.rb | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4d4b0dc67a..5c10d4ff24 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1045,7 +1045,7 @@ module ActiveRecord
         new(:up, migrations(migrations_paths), nil)
       end
 
-      def get_all_versions(connection = Base.connection)
+      def get_all_versions
         if SchemaMigration.table_exists?
           SchemaMigration.all_versions.map(&:to_i)
         else
@@ -1054,19 +1054,12 @@ module ActiveRecord
       end
 
       def current_version(connection = nil)
-        if connection.nil?
-          begin
-            connection = Base.connection
-          rescue ActiveRecord::NoDatabaseError
-            return nil
-          end
-        end
-
-        get_all_versions(connection).max || 0
+        get_all_versions.max || 0
+      rescue ActiveRecord::NoDatabaseError
       end
 
-      def needs_migration?(connection = Base.connection)
-        (migrations(migrations_paths).collect(&:version) - get_all_versions(connection)).size > 0
+      def needs_migration?(connection = nil)
+        (migrations(migrations_paths).collect(&:version) - get_all_versions).size > 0
       end
 
       def any_migrations?
-- 
cgit v1.2.3


From 915f0e682cecf80080914d0390ce22eb06f41470 Mon Sep 17 00:00:00 2001
From: Tsukuru Tanimichi <info+git@ttanimichi.com>
Date: Wed, 29 Nov 2017 18:44:46 +0900
Subject: Add tests for the `--webpack` option

We probably don't have any tests for the `--webpack` option.
related: #27288
---
 railties/test/generators/app_generator_test.rb | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 774fd0f315..c9c85078d4 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -743,6 +743,20 @@ class AppGeneratorTest < Rails::Generators::TestCase
     end
   end
 
+  def test_webpack_option
+    command_check = -> command, *_ do
+      @called ||= 0
+      @called += 1 if command == "webpacker:install"
+      assert_equal 1, @called, "webpacker:install expected to be called once, but was called #{@called} times."
+    end
+
+    generator([destination_root], webpack: true).stub(:rails_command, command_check) do
+      quietly { generator.invoke_all }
+    end
+
+    assert_gem "webpacker"
+  end
+
   def test_generator_if_skip_turbolinks_is_given
     run_generator [destination_root, "--skip-turbolinks"]
 
-- 
cgit v1.2.3


From 649f19cab1d4dd805c915912ede29c86655084cd Mon Sep 17 00:00:00 2001
From: Yoshiyuki Hirano <yhirano@me.com>
Date: Tue, 5 Dec 2017 15:25:12 +0900
Subject: Fix example code in ActiveJob::Core [ci skip]

1) It seems that it raise error on example code in `ActiveJob::Core`.

Before:

```ruby
class DeliverWebhookJob < ActiveJob::Base
  def serialize
    super.merge('attempt_number' => (@attempt_number || 0) + 1)
  end

  def deserialize(job_data)
    super
    @attempt_number = job_data['attempt_number']
  end

  rescue_from(Timeout::Error) do |exception|
    raise exception if @attempt_number > 5
    retry_job(wait: 10)
  end

  def perform
    raise Timeout::Error
  end
end
```

Then it run `DeliverWebhookJob.perform_now` in `rails console`. And raise error:

NoMethodError: undefined method `>' for nil:NilClass
from /app/jobs/deliver_webhook_job.rb:12:in `block in <class:DeliverWebhookJob>'

So I thought it's necessary to fix it.

After:

```ruby
class DeliverWebhookJob < ActiveJob::Base
  attr_writer :attempt_number

  def attempt_number
    @attempt_number ||= 0
  end

  def serialize
    super.merge('attempt_number' => attempt_number + 1)
  end

  def deserialize(job_data)
    super
    self.attempt_number = job_data['attempt_number']
  end

  rescue_from(Timeout::Error) do |exception|
    raise exception if attempt_number > 5
    retry_job(wait: 10)
  end

  def perform
    raise Timeout::Error
  end
end
```

Then it run `DeliverWebhookJob.perform_now` in `rails console`. And it does'nt raise error NoMethodError.

2) Use `Timeout::Error` instead of `TimeoutError` (`TimeoutError` is deprecated).
---
 activejob/lib/active_job/core.rb | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index c4e12fc518..879746fc01 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -97,17 +97,23 @@ module ActiveJob
     # ==== Examples
     #
     #    class DeliverWebhookJob < ActiveJob::Base
+    #      attr_writer :attempt_number
+    #
+    #      def attempt_number
+    #        @attempt_number ||= 0
+    #      end
+    #
     #      def serialize
-    #        super.merge('attempt_number' => (@attempt_number || 0) + 1)
+    #        super.merge('attempt_number' => attempt_number + 1)
     #      end
     #
     #      def deserialize(job_data)
     #        super
-    #        @attempt_number = job_data['attempt_number']
+    #        self.attempt_number = job_data['attempt_number']
     #      end
     #
-    #      rescue_from(TimeoutError) do |exception|
-    #        raise exception if @attempt_number > 5
+    #      rescue_from(Timeout::Error) do |exception|
+    #        raise exception if attempt_number > 5
     #        retry_job(wait: 10)
     #      end
     #    end
-- 
cgit v1.2.3


From 3c442b6df91e291ebbf17f37444414bf5f10fbe6 Mon Sep 17 00:00:00 2001
From: Simon Dawson <spdawson@gmail.com>
Date: Tue, 5 Dec 2017 07:13:48 +0000
Subject: Fix CSP copy boolean directives (#31326)

Use Object#deep_dup to safely duplicate policy values
---
 actionpack/lib/action_dispatch/http/content_security_policy.rb | 6 +-----
 actionpack/test/dispatch/content_security_policy_test.rb       | 9 +++++++++
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
index d10d4faf3d..c888a27720 100644
--- a/actionpack/lib/action_dispatch/http/content_security_policy.rb
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -110,7 +110,7 @@ module ActionDispatch #:nodoc:
     end
 
     def initialize_copy(other)
-      @directives = copy_directives(other.directives)
+      @directives = other.directives.deep_dup
     end
 
     DIRECTIVES.each do |name, directive|
@@ -174,10 +174,6 @@ module ActionDispatch #:nodoc:
     end
 
     private
-      def copy_directives(directives)
-        directives.transform_values { |sources| sources.map(&:dup) }
-      end
-
       def apply_mappings(sources)
         sources.map do |source|
           case source
diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb
index 8a1ac066e8..7c4a65a633 100644
--- a/actionpack/test/dispatch/content_security_policy_test.rb
+++ b/actionpack/test/dispatch/content_security_policy_test.rb
@@ -14,6 +14,15 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase
     assert_equal "script-src 'self';", @policy.build
   end
 
+  def test_dup
+    @policy.img_src :self
+    @policy.block_all_mixed_content
+    @policy.upgrade_insecure_requests
+    @policy.sandbox
+    copied = @policy.dup
+    assert_equal copied.build, @policy.build
+  end
+
   def test_mappings
     @policy.script_src :data
     assert_equal "script-src data:;", @policy.build
-- 
cgit v1.2.3


From 7efb4d23c1022108319add6218ae9d9284936ac5 Mon Sep 17 00:00:00 2001
From: "yuuji.yaginuma" <yuuji.yaginuma@gmail.com>
Date: Tue, 5 Dec 2017 18:16:41 +0900
Subject: Add missing require

Follow up of 3c442b6df91e291ebbf17f37444414bf5f10fbe6

Without this require, it will fail when run CSP test alone.
Ref: https://travis-ci.org/rails/rails/jobs/311715758#L2976
---
 actionpack/lib/action_dispatch/http/content_security_policy.rb | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
index c888a27720..4883e23d24 100644
--- a/actionpack/lib/action_dispatch/http/content_security_policy.rb
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -1,5 +1,7 @@
 # frozen_string_literal: true
 
+require "active_support/core_ext/object/deep_dup"
+
 module ActionDispatch #:nodoc:
   class ContentSecurityPolicy
     class Middleware
-- 
cgit v1.2.3


From b9fb74514b752df3b707fa420e09dca459a3844f Mon Sep 17 00:00:00 2001
From: Tsukuru Tanimichi <info+git@ttanimichi.com>
Date: Tue, 5 Dec 2017 18:41:44 +0900
Subject: Modify `test_webpack_option`

---
 railties/test/generators/app_generator_test.rb | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index decea77d48..68b31148d6 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -746,11 +746,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
   def test_webpack_option
     command_check = -> command, *_ do
       @called ||= 0
-      @called += 1 if command == "webpacker:install"
-      assert_equal 1, @called, "webpacker:install expected to be called once, but was called #{@called} times."
+      if command == "webpacker:install"
+        @called += 1
+        assert_equal 1, @called, "webpacker:install expected to be called once, but was called #{@called} times."
+      end
     end
 
-    generator([destination_root], webpack: true).stub(:rails_command, command_check) do
+    generator([destination_root], webpack: "webpack").stub(:rails_command, command_check) do
       quietly { generator.invoke_all }
     end
 
-- 
cgit v1.2.3


From 6a11b0c1549e88a7ca32a73764d8b6634dc326e2 Mon Sep 17 00:00:00 2001
From: Tsukuru Tanimichi <info+git@ttanimichi.com>
Date: Tue, 5 Dec 2017 18:43:15 +0900
Subject: Add more tests for the `--webpack` option

---
 railties/test/generators/app_generator_test.rb | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 68b31148d6..96803db838 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -759,6 +759,25 @@ class AppGeneratorTest < Rails::Generators::TestCase
     assert_gem "webpacker"
   end
 
+  def test_webpack_option_with_js_framework
+    command_check = -> command, *_ do
+      case command
+      when "webpacker:install"
+        @webpacker ||= 0
+        @webpacker += 1
+        assert_equal 1, @webpacker, "webpacker:install expected to be called once, but was called #{@webpacker} times."
+      when "webpacker:install:react"
+        @react ||= 0
+        @react += 1
+        assert_equal 1, @react, "webpacker:install:react expected to be called once, but was called #{@react} times."
+      end
+    end
+
+    generator([destination_root], webpack: "react").stub(:rails_command, command_check) do
+      quietly { generator.invoke_all }
+    end
+  end
+
   def test_generator_if_skip_turbolinks_is_given
     run_generator [destination_root, "--skip-turbolinks"]
 
-- 
cgit v1.2.3


From 6ec0ed67d9afcc666ad0424b10e9903f63e60714 Mon Sep 17 00:00:00 2001
From: Joe Francis <joe@lostapathy.com>
Date: Tue, 5 Dec 2017 08:23:30 -0600
Subject: only install ffmpeg and mupdf on activestorage builds

These are needed when GEM=ast, thanks @georgeclaghorn
---
 .travis.yml | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 290e0b5f2b..fd3b3f9002 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,10 +20,6 @@ addons:
     sources:
       - sourceline: "ppa:mc3man/trusty-media"
       - sourceline: "ppa:ubuntuhandbook1/apps"
-    packages:
-      - ffmpeg
-      - mupdf
-      - mupdf-tools
 
 bundler_args: --without test --jobs 3 --retry 3
 before_install:
@@ -42,6 +38,7 @@ before_script:
   # Decodes to e.g. `export VARIABLE=VALUE`
   - $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX0FDQ0VTU19LRVk9YTAzNTM0M2YtZTkyMi00MGIzLWFhM2MtMDZiM2VhNjM1YzQ4")
   - $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX1VTRVJOQU1FPXJ1YnlvbnJhaWxz")
+  - if [[ $GEM = *ast*  ]] ; then sudo apt-get update && sudo apt-get -y install ffmpeg mupdf mupdf-tools ; fi
 
 script: 'ci/travis.rb'
 
-- 
cgit v1.2.3


From 29d081c47a43cc5d979836541cb73a816c95981d Mon Sep 17 00:00:00 2001
From: Yasuo Honda <yasuo.honda@gmail.com>
Date: Tue, 5 Dec 2017 19:16:11 +0000
Subject: Execute `JsonAttributeTest` only if `supports_json?` returns `true`

Oracle enhanced adapter does not fully support JSON datatype then `supports_json?` returns `false`.
I wanted to skip known failures and errors when tested with Oracle enhanced adapter.
---
 activerecord/test/cases/json_attribute_test.rb | 46 ++++++++++++++------------
 1 file changed, 24 insertions(+), 22 deletions(-)

diff --git a/activerecord/test/cases/json_attribute_test.rb b/activerecord/test/cases/json_attribute_test.rb
index 63f3c77fc3..a6fd4f34dc 100644
--- a/activerecord/test/cases/json_attribute_test.rb
+++ b/activerecord/test/cases/json_attribute_test.rb
@@ -3,33 +3,35 @@
 require "cases/helper"
 require "cases/json_shared_test_cases"
 
-class JsonAttributeTest < ActiveRecord::TestCase
-  include JSONSharedTestCases
-  self.use_transactional_tests = false
+if ActiveRecord::Base.connection.supports_json?
+  class JsonAttributeTest < ActiveRecord::TestCase
+    include JSONSharedTestCases
+    self.use_transactional_tests = false
 
-  class JsonDataTypeOnText < ActiveRecord::Base
-    self.table_name = "json_data_type"
+    class JsonDataTypeOnText < ActiveRecord::Base
+      self.table_name = "json_data_type"
 
-    attribute :payload,  :json
-    attribute :settings, :json
+      attribute :payload,  :json
+      attribute :settings, :json
 
-    store_accessor :settings, :resolution
-  end
-
-  def setup
-    super
-    @connection.create_table("json_data_type") do |t|
-      t.text "payload"
-      t.text "settings"
+      store_accessor :settings, :resolution
     end
-  end
 
-  private
-    def column_type
-      :text
+    def setup
+      super
+      @connection.create_table("json_data_type") do |t|
+        t.text "payload"
+        t.text "settings"
+      end
     end
 
-    def klass
-      JsonDataTypeOnText
-    end
+    private
+      def column_type
+        :text
+      end
+
+      def klass
+        JsonDataTypeOnText
+      end
+  end
 end
-- 
cgit v1.2.3


From d2321aacc7451f6b4f154d894adcb76820abba3c Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Wed, 6 Dec 2017 08:30:27 +0900
Subject: Revert "Merge pull request #31341 from
 yahonda/skip_json_attribute_test"

This reverts commit 23226d04f921b79f0077ba38c5a5a923b6d43f89, reversing
changes made to 7544cf7603959f25100b21f70b5e70354bed7e45.
---
 activerecord/test/cases/json_attribute_test.rb | 46 ++++++++++++--------------
 1 file changed, 22 insertions(+), 24 deletions(-)

diff --git a/activerecord/test/cases/json_attribute_test.rb b/activerecord/test/cases/json_attribute_test.rb
index a6fd4f34dc..63f3c77fc3 100644
--- a/activerecord/test/cases/json_attribute_test.rb
+++ b/activerecord/test/cases/json_attribute_test.rb
@@ -3,35 +3,33 @@
 require "cases/helper"
 require "cases/json_shared_test_cases"
 
-if ActiveRecord::Base.connection.supports_json?
-  class JsonAttributeTest < ActiveRecord::TestCase
-    include JSONSharedTestCases
-    self.use_transactional_tests = false
+class JsonAttributeTest < ActiveRecord::TestCase
+  include JSONSharedTestCases
+  self.use_transactional_tests = false
 
-    class JsonDataTypeOnText < ActiveRecord::Base
-      self.table_name = "json_data_type"
+  class JsonDataTypeOnText < ActiveRecord::Base
+    self.table_name = "json_data_type"
 
-      attribute :payload,  :json
-      attribute :settings, :json
+    attribute :payload,  :json
+    attribute :settings, :json
 
-      store_accessor :settings, :resolution
-    end
+    store_accessor :settings, :resolution
+  end
 
-    def setup
-      super
-      @connection.create_table("json_data_type") do |t|
-        t.text "payload"
-        t.text "settings"
-      end
+  def setup
+    super
+    @connection.create_table("json_data_type") do |t|
+      t.text "payload"
+      t.text "settings"
     end
+  end
 
-    private
-      def column_type
-        :text
-      end
+  private
+    def column_type
+      :text
+    end
 
-      def klass
-        JsonDataTypeOnText
-      end
-  end
+    def klass
+      JsonDataTypeOnText
+    end
 end
-- 
cgit v1.2.3


From d721344280346f67c7d2dbffa9eaf9341e73673d Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Wed, 6 Dec 2017 08:33:16 +0900
Subject: Use `:string` instead of `:text` for `JsonAttributeTest`

Since CLOB data type has many limitations in Oracle SELECT WHERE clause.
---
 activerecord/test/cases/json_attribute_test.rb    | 6 +++---
 activerecord/test/cases/json_shared_test_cases.rb | 9 +++++++--
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/activerecord/test/cases/json_attribute_test.rb b/activerecord/test/cases/json_attribute_test.rb
index 63f3c77fc3..afc39d0420 100644
--- a/activerecord/test/cases/json_attribute_test.rb
+++ b/activerecord/test/cases/json_attribute_test.rb
@@ -19,14 +19,14 @@ class JsonAttributeTest < ActiveRecord::TestCase
   def setup
     super
     @connection.create_table("json_data_type") do |t|
-      t.text "payload"
-      t.text "settings"
+      t.string "payload"
+      t.string "settings"
     end
   end
 
   private
     def column_type
-      :text
+      :string
     end
 
     def klass
diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
index 56ec8c8a82..012aaffde1 100644
--- a/activerecord/test/cases/json_shared_test_cases.rb
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -23,7 +23,7 @@ module JSONSharedTestCases
   def test_column
     column = klass.columns_hash["payload"]
     assert_equal column_type, column.type
-    assert_equal column_type.to_s, column.sql_type
+    assert_type_match column_type, column.sql_type
 
     type = klass.type_for_attribute("payload")
     assert_not type.binary?
@@ -36,7 +36,7 @@ module JSONSharedTestCases
     klass.reset_column_information
     column = klass.columns_hash["users"]
     assert_equal column_type, column.type
-    assert_equal column_type.to_s, column.sql_type
+    assert_type_match column_type, column.sql_type
   end
 
   def test_schema_dumping
@@ -253,4 +253,9 @@ module JSONSharedTestCases
     def klass
       JsonDataType
     end
+
+    def assert_type_match(type, sql_type)
+      native_type = ActiveRecord::Base.connection.native_database_types[type][:name]
+      assert_match %r(\A#{native_type}\b), sql_type
+    end
 end
-- 
cgit v1.2.3


From 5f7fd1584f131d0afad558eaba6017e08df65892 Mon Sep 17 00:00:00 2001
From: Yoshiyuki Hirano <yhirano@me.com>
Date: Wed, 6 Dec 2017 09:09:32 +0900
Subject: Add `assert_in_epsilon` to Testing guide [ci skip]

I found `assert_in_epsilon` is not be in "2.4 Available Assertions".
So I Added them.

MiniTest::Assertions#assert_in_epsilon:

https://github.com/seattlerb/minitest/blob/master/lib/minitest/assertions.rb#L204-L210
---
 guides/source/testing.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/guides/source/testing.md b/guides/source/testing.md
index e0a2d281d9..9692f50b6e 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -319,6 +319,8 @@ specify to make your test failure messages clearer.
 | `assert_not_includes( collection, obj, [msg] )`                  | Ensures that `obj` is not in `collection`.|
 | `assert_in_delta( expected, actual, [delta], [msg] )`            | Ensures that the numbers `expected` and `actual` are within `delta` of each other.|
 | `assert_not_in_delta( expected, actual, [delta], [msg] )`        | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
+| `assert_in_epsilon ( expected, actual, [epsilon], [msg] )`       | Ensures that the numbers `expected` and `actual` have a relative error less than `epsilon`.|
+| `assert_not_in_epsilon ( expected, actual, [epsilon], [msg] )`   | Ensures that the numbers `expected` and `actual` don't have a relative error less than `epsilon`.|
 | `assert_throws( symbol, [msg] ) { block }`                       | Ensures that the given block throws the symbol.|
 | `assert_raises( exception1, exception2, ... ) { block }`         | Ensures that the given block raises one of the given exceptions.|
 | `assert_instance_of( class, obj, [msg] )`                        | Ensures that `obj` is an instance of `class`.|
-- 
cgit v1.2.3


From d7a121a0d624dd8180556521f0611d4c8e2c648f Mon Sep 17 00:00:00 2001
From: Dominic Cleal <dominic@cleal.org>
Date: Tue, 24 Jan 2017 10:55:43 +0000
Subject: Yield array from AC::Parameters#each for block with one arg

Matches Hash#each behaviour as used in Rails 4.
---
 .../lib/action_controller/metal/strong_parameters.rb     |  2 +-
 actionpack/test/controller/parameters/accessors_test.rb  | 16 ++++++++++++++++
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index ef7c4c4c16..a56ac749f8 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -335,7 +335,7 @@ module ActionController
     # the same way as <tt>Hash#each_pair</tt>.
     def each_pair(&block)
       @parameters.each_pair do |key, value|
-        yield key, convert_hashes_to_parameters(key, value)
+        yield [key, convert_hashes_to_parameters(key, value)]
       end
     end
     alias_method :each, :each_pair
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index 43cabae7d2..154430d4b0 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -51,6 +51,14 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
     @params.each { |key, value| assert_not(value.permitted?) if key == "person" }
   end
 
+  test "each returns key,value array for block with arity 1" do
+    @params.each do |arg|
+      assert_kind_of Array, arg
+      assert_equal "person", arg[0]
+      assert_kind_of ActionController::Parameters, arg[1]
+    end
+  end
+
   test "each_pair carries permitted status" do
     @params.permit!
     @params.each_pair { |key, value| assert(value.permitted?) if key == "person" }
@@ -60,6 +68,14 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
     @params.each_pair { |key, value| assert_not(value.permitted?) if key == "person" }
   end
 
+  test "each_pair returns key,value array for block with arity 1" do
+    @params.each_pair do |arg|
+      assert_kind_of Array, arg
+      assert_equal "person", arg[0]
+      assert_kind_of ActionController::Parameters, arg[1]
+    end
+  end
+
   test "empty? returns true when params contains no key/value pairs" do
     params = ActionController::Parameters.new
     assert params.empty?
-- 
cgit v1.2.3


From b34838cbe29434212eb81ca1e31bb0ab0765e32a Mon Sep 17 00:00:00 2001
From: Yasuo Honda <yasuo.honda@gmail.com>
Date: Wed, 6 Dec 2017 13:09:51 +0000
Subject: Address `ActiveRecord::NotNullViolation: OCIError: ORA-01400` for
 Oracle database which requires primary key value mentioned in insert
 statement explicitly.

---
 activerecord/test/cases/json_shared_test_cases.rb | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
index 012aaffde1..b0c0f2c283 100644
--- a/activerecord/test/cases/json_shared_test_cases.rb
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -66,26 +66,26 @@ module JSONSharedTestCases
   end
 
   def test_rewrite
-    @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|)
+    @connection.execute(insert_statement_per_database('{"k":"v"}'))
     x = klass.first
     x.payload = { '"a\'' => "b" }
     assert x.save!
   end
 
   def test_select
-    @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|)
+    @connection.execute(insert_statement_per_database('{"k":"v"}'))
     x = klass.first
     assert_equal({ "k" => "v" }, x.payload)
   end
 
   def test_select_multikey
-    @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|)
+    @connection.execute(insert_statement_per_database('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}'))
     x = klass.first
     assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
   end
 
   def test_null_json
-    @connection.execute("insert into json_data_type (payload) VALUES(null)")
+    @connection.execute(insert_statement_per_database("null"))
     x = klass.first
     assert_nil(x.payload)
   end
@@ -107,13 +107,13 @@ module JSONSharedTestCases
   end
 
   def test_select_array_json_value
-    @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|)
+    @connection.execute(insert_statement_per_database('["v0",{"k1":"v1"}]'))
     x = klass.first
     assert_equal(["v0", { "k1" => "v1" }], x.payload)
   end
 
   def test_rewrite_array_json_value
-    @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|)
+    @connection.execute(insert_statement_per_database('["v0",{"k1":"v1"}]'))
     x = klass.first
     x.payload = ["v1", { "k2" => "v2" }, "v3"]
     assert x.save!
@@ -258,4 +258,12 @@ module JSONSharedTestCases
       native_type = ActiveRecord::Base.connection.native_database_types[type][:name]
       assert_match %r(\A#{native_type}\b), sql_type
     end
+
+    def insert_statement_per_database(values)
+      if current_adapter?(:OracleAdapter)
+        "insert into json_data_type (id, payload) VALUES (json_data_type_seq.nextval, '#{values}')"
+      else
+        "insert into json_data_type (payload) VALUES ('#{values}')"
+      end
+    end
 end
-- 
cgit v1.2.3


From 5ac4f4d2563e7f9c5ffaecce4be4b9e2c5b0c081 Mon Sep 17 00:00:00 2001
From: Ashley Ellis Pierce <anellis12@gmail.com>
Date: Mon, 4 Dec 2017 16:44:33 -0500
Subject: Fix sqlite migrations with custom primary keys

Previously, if a record was created with a custom primary key, that
table could not be migrated using sqlite. While attempting to copy the
table, the type of the primary key was ignored.

Once that was corrected, copying the indexes would fail because custom
primary keys are autoindexed by sqlite by default.

To correct that, this skips copying the index if the index name begins
with "sqlite_". This is a reserved word that indicates that the
index is an internal schema object. SQLite prohibits applications from
creating objects whose names begin with "sqlite_", so this string should
be safe to use as a check.
ref https://www.sqlite.org/fileformat2.html#intschema
---
 .../connection_adapters/sqlite3_adapter.rb             |  6 +++++-
 .../cases/adapters/sqlite3/sqlite3_adapter_test.rb     | 18 ++++++++++++++++++
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index daece2bffd..3197b522b3 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -395,10 +395,11 @@ module ActiveRecord
 
         def copy_table(from, to, options = {})
           from_primary_key = primary_key(from)
+          from_primary_key_column = columns(from).select { |column| column.name == from_primary_key }.first
           options[:id] = false
           create_table(to, options) do |definition|
             @definition = definition
-            @definition.primary_key(from_primary_key) if from_primary_key.present?
+            @definition.primary_key(from_primary_key, from_primary_key_column.type) if from_primary_key.present?
             columns(from).each do |column|
               column_name = options[:rename] ?
                 (options[:rename][column.name] ||
@@ -422,6 +423,9 @@ module ActiveRecord
         def copy_table_indexes(from, to, rename = {})
           indexes(from).each do |index|
             name = index.name
+            # indexes sqlite creates for internal use start with `sqlite_` and
+            # don't need to be copied
+            next if name.starts_with?("sqlite_")
             if to == "a#{from}"
               name = "t#{name}"
             elsif from == "a#{to}"
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 1f057fe5c6..14f4997d5b 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -360,6 +360,24 @@ module ActiveRecord
         end
       end
 
+      class Barcode < ActiveRecord::Base
+      end
+
+      def test_existing_records_have_custom_primary_key
+        connection = Barcode.connection
+        connection.create_table(:barcodes, primary_key: "code", id: :string, limit: 42, force: true) do |t|
+          t.text :other_attr
+        end
+        code = "214fe0c2-dd47-46df-b53b-66090b3c1d40"
+        Barcode.create! code: code, other_attr: "xxx"
+
+        connection.change_table "barcodes" do |t|
+          connection.remove_column("barcodes", "other_attr")
+        end
+
+        assert_equal code, Barcode.first.id
+      end
+
       def test_supports_extensions
         assert_not @conn.supports_extensions?, "does not support extensions"
       end
-- 
cgit v1.2.3


From faf169eedd10326233eaea55ed76089ede608336 Mon Sep 17 00:00:00 2001
From: Philip Tolton <mrtolton@gmail.com>
Date: Wed, 6 Dec 2017 16:01:00 -0500
Subject: Correct routing test spelling mistake.

---
 actionpack/test/controller/routing_test.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index f09051b306..71b01c36a7 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -213,7 +213,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
     assert_equal expected, ActiveSupport::JSON.decode(get(u))
   end
 
-  def test_regexp_precidence
+  def test_regexp_precedence
     rs.draw do
       get "/whois/:domain", constraints: {
         domain: /\w+\.[\w\.]+/ },
-- 
cgit v1.2.3


From 2fa29037678874b25ad257dff8974055e25c9384 Mon Sep 17 00:00:00 2001
From: Yoshiyuki Hirano <yhirano@me.com>
Date: Thu, 7 Dec 2017 14:46:59 +0900
Subject: [ci skip] Make Todo classes inherit ApplicationRecord

Example codes that use `has_many` or `before_create` in `Module::Concerning` look like active record models.
So I've made them inherit `ApplicationRecord`.
---
 activesupport/lib/active_support/core_ext/module/concerning.rb | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index 370a948eea..800bf213cc 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -22,7 +22,7 @@ class Module
   #
   # == Using comments:
   #
-  #   class Todo
+  #   class Todo < ApplicationRecord
   #     # Other todo implementation
   #     # ...
   #
@@ -42,7 +42,7 @@ class Module
   #
   # Noisy syntax.
   #
-  #   class Todo
+  #   class Todo < ApplicationRecord
   #     # Other todo implementation
   #     # ...
   #
@@ -70,7 +70,7 @@ class Module
   # increased overhead can be a reasonable tradeoff even if it reduces our
   # at-a-glance perception of how things work.
   #
-  #   class Todo
+  #   class Todo < ApplicationRecord
   #     # Other todo implementation
   #     # ...
   #
@@ -82,7 +82,7 @@ class Module
   # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
   # separate bite-sized concerns.
   #
-  #   class Todo
+  #   class Todo < ApplicationRecord
   #     # Other todo implementation
   #     # ...
   #
@@ -101,7 +101,7 @@ class Module
   #   end
   #
   #   Todo.ancestors
-  #   # => [Todo, Todo::EventTracking, Object]
+  #   # => [Todo, Todo::EventTracking, ApplicationRecord, Object]
   #
   # This small step has some wonderful ripple effects. We can
   # * grok the behavior of our class in one glance,
-- 
cgit v1.2.3


From 82b974813b28748e5affcff1d8c4ad60ab2971be Mon Sep 17 00:00:00 2001
From: bogdanvlviv <bogdanvlviv@gmail.com>
Date: Thu, 7 Dec 2017 20:02:34 +0200
Subject: Add headless firefox driver to System Tests

---
 actionpack/CHANGELOG.md                                     |  4 ++++
 actionpack/lib/action_dispatch/system_test_case.rb          |  8 ++++++--
 actionpack/lib/action_dispatch/system_testing/driver.rb     | 13 ++++++++++++-
 actionpack/test/abstract_unit.rb                            |  4 ++++
 actionpack/test/dispatch/system_testing/driver_test.rb      |  8 ++++++++
 .../test/dispatch/system_testing/system_test_case_test.rb   |  6 ++++++
 guides/source/testing.md                                    |  3 ++-
 7 files changed, 42 insertions(+), 4 deletions(-)

diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index d120d15770..753dd8589a 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,7 @@
+*   Add headless firefox support to System Tests.
+
+    *bogdanvlviv*
+
 *   Changed the default system test screenshot output from `inline` to `simple`.
 
     `inline` works well for iTerm2 but not everyone uses iTerm2. Some terminals like
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index 7246e01cff..99d0c06751 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -121,11 +121,15 @@ module ActionDispatch
     #
     #   driven_by :poltergeist
     #
-    #   driven_by :selenium, using: :firefox
+    #   driven_by :selenium, screen_size: [800, 800]
+    #
+    #   driven_by :selenium, using: :chrome
     #
     #   driven_by :selenium, using: :headless_chrome
     #
-    #   driven_by :selenium, screen_size: [800, 800]
+    #   driven_by :selenium, using: :firefox
+    #
+    #   driven_by :selenium, using: :headless_firefox
     def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {})
       self.driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options)
     end
diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb
index 2687772b4b..280989a146 100644
--- a/actionpack/lib/action_dispatch/system_testing/driver.rb
+++ b/actionpack/lib/action_dispatch/system_testing/driver.rb
@@ -37,6 +37,11 @@ module ActionDispatch
             browser_options.args << "--headless"
             browser_options.args << "--disable-gpu"
 
+            @options.merge(options: browser_options)
+          elsif @browser == :headless_firefox
+            browser_options = Selenium::WebDriver::Firefox::Options.new
+            browser_options.args << "-headless"
+
             @options.merge(options: browser_options)
           else
             @options
@@ -44,7 +49,13 @@ module ActionDispatch
         end
 
         def browser
-          @browser == :headless_chrome ? :chrome : @browser
+          if @browser == :headless_chrome
+            :chrome
+          elsif @browser == :headless_firefox
+            :firefox
+          else
+            @browser
+          end
         end
 
         def register_selenium(app)
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 5262e85a28..55ad9c245e 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -453,3 +453,7 @@ end
 class DrivenBySeleniumWithHeadlessChrome < ActionDispatch::SystemTestCase
   driven_by :selenium, using: :headless_chrome
 end
+
+class DrivenBySeleniumWithHeadlessFirefox < ActionDispatch::SystemTestCase
+  driven_by :selenium, using: :headless_firefox
+end
diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb
index 75feae6fe0..fcdaf7fb4c 100644
--- a/actionpack/test/dispatch/system_testing/driver_test.rb
+++ b/actionpack/test/dispatch/system_testing/driver_test.rb
@@ -25,6 +25,14 @@ class DriverTest < ActiveSupport::TestCase
     assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options)
   end
 
+  test "initializing the driver with a headless firefox" do
+    driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :headless_firefox, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" })
+    assert_equal :selenium, driver.instance_variable_get(:@name)
+    assert_equal :headless_firefox, driver.instance_variable_get(:@browser)
+    assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size)
+    assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options)
+  end
+
   test "initializing the driver with a poltergeist" do
     driver = ActionDispatch::SystemTesting::Driver.new(:poltergeist, screen_size: [1400, 1400], options: { js_errors: false })
     assert_equal :poltergeist, driver.instance_variable_get(:@name)
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 c6a6aef92b..b078a5abc5 100644
--- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb
+++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
@@ -28,6 +28,12 @@ class SetDriverToSeleniumHeadlessChromeTest < DrivenBySeleniumWithHeadlessChrome
   end
 end
 
+class SetDriverToSeleniumHeadlessFirefoxTest < DrivenBySeleniumWithHeadlessFirefox
+  test "uses selenium headless firefox" do
+    assert_equal :selenium, Capybara.current_driver
+  end
+end
+
 class SetHostTest < DrivenByRackTest
   test "sets default host" do
     assert_equal "http://127.0.0.1", Capybara.app_host
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 9692f50b6e..f28c4c224a 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -673,7 +673,8 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
 end
 ```
 
-If you want to use a headless browser, you could use Headless Chrome by adding `headless_chrome` in the `:using` argument.
+If you want to use a headless browser, you could use Headless Chrome or Headless Firefox by adding
+`headless_chrome` or `headless_firefox` in the `:using` argument.
 
 ```ruby
 require "test_helper"
-- 
cgit v1.2.3


From 6a8ce7416d6615a13ee5c4b9f6bcd91cc5adef4d Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Fri, 8 Dec 2017 02:37:02 +0900
Subject: Fix `scope_for_create` to do not lose polymorphic associations

This regression was caused at 213796fb due to polymorphic predicates are
combined by `Arel::Nodes::And`. But I'd like to keep that combined
because it would help inverting polymorphic predicates correctly
(e9ba12f7), and we can collect equality nodes regardless of combined by
`Arel::Nodes::And` (`a AND (b AND c) AND d` == `a AND b AND c AND d`).
This change fixes the regression to collect equality nodes in
`Arel::Nodes::And` as well.

Fixes #31338.
---
 .../lib/active_record/relation/where_clause.rb         | 18 ++++++++++++++++--
 activerecord/test/cases/relation_test.rb               |  2 +-
 activerecord/test/cases/relations_test.rb              |  9 +++++++++
 3 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 752bb38481..a502713e56 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -47,7 +47,7 @@ module ActiveRecord
       end
 
       def to_h(table_name = nil)
-        equalities = predicates.grep(Arel::Nodes::Equality)
+        equalities = equalities(predicates)
         if table_name
           equalities = equalities.select do |node|
             node.left.relation.name == table_name
@@ -90,6 +90,20 @@ module ActiveRecord
         end
 
       private
+        def equalities(predicates)
+          equalities = []
+
+          predicates.each do |node|
+            case node
+            when Arel::Nodes::Equality
+              equalities << node
+            when Arel::Nodes::And
+              equalities.concat equalities(node.children)
+            end
+          end
+
+          equalities
+        end
 
         def predicates_unreferenced_by(other)
           predicates.reject do |n|
@@ -121,7 +135,7 @@ module ActiveRecord
         end
 
         def except_predicates(columns)
-          self.predicates.reject do |node|
+          predicates.reject do |node|
             case node
             when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
               subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index a71d8de521..b424ca91de 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -68,7 +68,7 @@ module ActiveRecord
       relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
       left     = relation.table[:id].eq(10)
       right    = relation.table[:id].eq(10)
-      combine  = left.and right
+      combine  = left.or(right)
       relation.where! combine
       assert_equal({}, relation.where_values_hash)
     end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 50ad1d5b26..675aafabda 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1189,6 +1189,15 @@ class RelationTest < ActiveRecord::TestCase
     assert_equal "hen", hen.name
   end
 
+  def test_create_with_polymorphic_association
+    author = authors(:david)
+    post = posts(:welcome)
+    comment = Comment.where(post: post, author: author).create!(body: "hello")
+
+    assert_equal author, comment.author
+    assert_equal post, comment.post
+  end
+
   def test_first_or_create
     parrot = Bird.where(color: "green").first_or_create(name: "parrot")
     assert_kind_of Bird, parrot
-- 
cgit v1.2.3


From e8286ee272a3e51daebc198519accd1f6895a8d2 Mon Sep 17 00:00:00 2001
From: George Claghorn <george@basecamp.com>
Date: Thu, 7 Dec 2017 15:14:22 -0500
Subject: Fix customizing Content-Type via GCS service URLs

---
 activestorage/lib/active_storage/service/gcs_service.rb |  8 +++++++-
 activestorage/test/service/gcs_service_test.rb          | 14 ++++++++++++++
 2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb
index c13ce4786d..6f6f4105fe 100644
--- a/activestorage/lib/active_storage/service/gcs_service.rb
+++ b/activestorage/lib/active_storage/service/gcs_service.rb
@@ -16,7 +16,13 @@ module ActiveStorage
     def upload(key, io, checksum: nil)
       instrument :upload, key: key, checksum: checksum do
         begin
-          bucket.create_file(io, key, md5: checksum)
+          # The official GCS client library doesn't allow us to create a file with no Content-Type metadata.
+          # We need the file we create to have no Content-Type so we can control it via the response-content-type
+          # param in signed URLs. Workaround: let the GCS client create the file with an inferred
+          # Content-Type (usually "application/octet-stream") then clear it.
+          bucket.create_file(io, key, md5: checksum).update do |file|
+            file.content_type = nil
+          end
         rescue Google::Cloud::InvalidArgumentError
           raise ActiveStorage::IntegrityError
         end
diff --git a/activestorage/test/service/gcs_service_test.rb b/activestorage/test/service/gcs_service_test.rb
index 1860149da9..7efcd60fb7 100644
--- a/activestorage/test/service/gcs_service_test.rb
+++ b/activestorage/test/service/gcs_service_test.rb
@@ -35,6 +35,20 @@ if SERVICE_CONFIGURATIONS[:gcs]
       assert_match(/storage\.googleapis\.com\/.*response-content-disposition=inline.*test\.txt.*response-content-type=text%2Fplain/,
         @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain"))
     end
+
+    test "signed URL response headers" do
+      begin
+        key  = SecureRandom.base58(24)
+        data = "Something else entirely!"
+        @service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data))
+
+        url = @service.url(key, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
+        response = Net::HTTP.get_response(URI(url))
+        assert_equal "text/plain", response.header["Content-Type"]
+      ensure
+        @service.delete key
+      end
+    end
   end
 else
   puts "Skipping GCS Service tests because no GCS configuration was supplied"
-- 
cgit v1.2.3


From 131cc6eab64eeae6c7f508dca4176183144cf3a6 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Fri, 8 Dec 2017 10:49:25 +0900
Subject: SQLite: Fix `copy_table` with composite primary keys

`connection.primary_key` also return composite primary keys, so
`from_primary_key_column` may not be found even if `from_primary_key` is
presented.

```
% ARCONN=sqlite3 be ruby -w -Itest
test/cases/adapters/sqlite3/sqlite3_adapter_test.rb -n
test_copy_table_with_composite_primary_keys
Using sqlite3
Run options: -n test_copy_table_with_composite_primary_keys --seed 19041

# Running:

E

Error:
ActiveRecord::ConnectionAdapters::SQLite3AdapterTest#test_copy_table_with_composite_primary_keys:
NoMethodError: undefined method `type' for nil:NilClass
    /path/to/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb:411:in
    `block in copy_table'
```

This change fixes `copy_table` to do not lose composite primary keys.
---
 .../connection_adapters/sqlite3_adapter.rb         | 10 ++++---
 .../cases/adapters/sqlite3/sqlite3_adapter_test.rb | 31 ++++++++++++++++++++--
 2 files changed, 35 insertions(+), 6 deletions(-)

diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 65aba20052..c72db15ce3 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -404,22 +404,24 @@ module ActiveRecord
 
         def copy_table(from, to, options = {})
           from_primary_key = primary_key(from)
-          from_primary_key_column = columns(from).select { |column| column.name == from_primary_key }.first
           options[:id] = false
           create_table(to, options) do |definition|
             @definition = definition
-            @definition.primary_key(from_primary_key, from_primary_key_column.type) if from_primary_key.present?
+            if from_primary_key.is_a?(Array)
+              @definition.primary_keys from_primary_key
+            end
             columns(from).each do |column|
               column_name = options[:rename] ?
                 (options[:rename][column.name] ||
                  options[:rename][column.name.to_sym] ||
                  column.name) : column.name
-              next if column_name == from_primary_key
 
               @definition.column(column_name, column.type,
                 limit: column.limit, default: column.default,
                 precision: column.precision, scale: column.scale,
-                null: column.null, collation: column.collation)
+                null: column.null, collation: column.collation,
+                primary_key: column_name == from_primary_key
+              )
             end
             yield @definition if block_given?
           end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 14f4997d5b..1357719422 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -361,21 +361,48 @@ module ActiveRecord
       end
 
       class Barcode < ActiveRecord::Base
+        self.primary_key = "code"
       end
 
-      def test_existing_records_have_custom_primary_key
+      def test_copy_table_with_existing_records_have_custom_primary_key
         connection = Barcode.connection
         connection.create_table(:barcodes, primary_key: "code", id: :string, limit: 42, force: true) do |t|
           t.text :other_attr
         end
         code = "214fe0c2-dd47-46df-b53b-66090b3c1d40"
-        Barcode.create! code: code, other_attr: "xxx"
+        Barcode.create!(code: code, other_attr: "xxx")
 
         connection.change_table "barcodes" do |t|
           connection.remove_column("barcodes", "other_attr")
         end
 
         assert_equal code, Barcode.first.id
+      ensure
+        Barcode.reset_column_information
+      end
+
+      def test_copy_table_with_composite_primary_keys
+        connection = Barcode.connection
+        connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t|
+          t.string :region
+          t.string :code
+          t.text :other_attr
+        end
+        region = "US"
+        code = "214fe0c2-dd47-46df-b53b-66090b3c1d40"
+        Barcode.create!(region: region, code: code, other_attr: "xxx")
+
+        connection.change_table "barcodes" do |t|
+          connection.remove_column("barcodes", "other_attr")
+        end
+
+        assert_equal ["region", "code"], connection.primary_keys("barcodes")
+
+        barcode = Barcode.first
+        assert_equal region, barcode.region
+        assert_equal code, barcode.code
+      ensure
+        Barcode.reset_column_information
       end
 
       def test_supports_extensions
-- 
cgit v1.2.3


From da8e0ba03cbae33857954c0c1a228bd6dae562da Mon Sep 17 00:00:00 2001
From: George Claghorn <george@basecamp.com>
Date: Fri, 8 Dec 2017 13:15:04 -0500
Subject: Swap raw video width and height if angle is 90 or 270 degrees

---
 .../lib/active_storage/analyzer/video_analyzer.rb          | 14 +++++++++++++-
 activestorage/test/analyzer/video_analyzer_test.rb         |  4 ++--
 2 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/activestorage/lib/active_storage/analyzer/video_analyzer.rb b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
index b6fc54d917..1c144baa37 100644
--- a/activestorage/lib/active_storage/analyzer/video_analyzer.rb
+++ b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
@@ -31,10 +31,18 @@ module ActiveStorage
 
     private
       def width
-        Integer(video_stream["width"]) if video_stream["width"]
+        rotated? ? raw_height : raw_width
       end
 
       def height
+        rotated? ? raw_width : raw_height
+      end
+
+      def raw_width
+        Integer(video_stream["width"]) if video_stream["width"]
+      end
+
+      def raw_height
         Integer(video_stream["height"]) if video_stream["height"]
       end
 
@@ -52,6 +60,10 @@ module ActiveStorage
         end
       end
 
+      def rotated?
+        angle == 90 || angle == 270
+      end
+
 
       def tags
         @tags ||= video_stream["tags"] || {}
diff --git a/activestorage/test/analyzer/video_analyzer_test.rb b/activestorage/test/analyzer/video_analyzer_test.rb
index 4a3c4a8bfc..b3b9c97fe4 100644
--- a/activestorage/test/analyzer/video_analyzer_test.rb
+++ b/activestorage/test/analyzer/video_analyzer_test.rb
@@ -21,8 +21,8 @@ class ActiveStorage::Analyzer::VideoAnalyzerTest < ActiveSupport::TestCase
     blob = create_file_blob(filename: "rotated_video.mp4", content_type: "video/mp4")
     metadata = blob.tap(&:analyze).metadata
 
-    assert_equal 640, metadata[:width]
-    assert_equal 480, metadata[:height]
+    assert_equal 480, metadata[:width]
+    assert_equal 640, metadata[:height]
     assert_equal [4, 3], metadata[:aspect_ratio]
     assert_equal 5.227975, metadata[:duration]
     assert_equal 90, metadata[:angle]
-- 
cgit v1.2.3