aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock12
-rw-r--r--RELEASING_RAILS.md2
-rw-r--r--actionpack/lib/action_dispatch/http/content_security_policy.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb6
-rw-r--r--actionpack/test/dispatch/content_security_policy_test.rb6
-rw-r--r--actionview/lib/action_view/digestor.rb15
-rw-r--r--activejob/lib/active_job/core.rb20
-rw-r--r--activejob/test/cases/job_serialization_test.rb12
-rw-r--r--activerecord/CHANGELOG.md4
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb4
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/test/cases/arel/attributes/attribute_test.rb2
-rw-r--r--activerecord/test/cases/arel/helper.rb15
-rw-r--r--activerecord/test/cases/arel/nodes/ascending_test.rb2
-rw-r--r--activerecord/test/cases/arel/nodes/binary_test.rb28
-rw-r--r--activerecord/test/cases/arel/nodes/case_test.rb106
-rw-r--r--activerecord/test/cases/arel/nodes/descending_test.rb2
-rw-r--r--activerecord/test/cases/arel/nodes/select_core_test.rb6
-rw-r--r--activerecord/test/cases/arel/select_manager_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb9
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/migration/index_test.rb5
-rw-r--r--activerecord/test/cases/test_case.rb2
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb20
-rw-r--r--activestorage/CHANGELOG.md5
-rw-r--r--activestorage/app/controllers/active_storage/disk_controller.rb15
-rw-r--r--activestorage/lib/active_storage/service/gcs_service.rb44
-rw-r--r--activestorage/test/controllers/disk_controller_test.rb10
-rw-r--r--activestorage/test/service/shared_service_tests.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb2
-rw-r--r--activesupport/test/core_ext/load_error_test.rb7
-rw-r--r--guides/source/2_2_release_notes.md4
-rw-r--r--guides/source/2_3_release_notes.md8
-rw-r--r--guides/source/3_0_release_notes.md4
-rw-r--r--guides/source/active_storage_overview.md10
-rw-r--r--guides/source/autoloading_and_reloading_constants.md6
-rw-r--r--guides/source/getting_started.md2
-rw-r--r--guides/source/layout.html.erb2
-rw-r--r--guides/source/upgrading_ruby_on_rails.md2
-rw-r--r--railties/lib/rails/command/spellchecker.rb51
42 files changed, 317 insertions, 168 deletions
diff --git a/Gemfile b/Gemfile
index fc7680c9bf..f0c489f642 100644
--- a/Gemfile
+++ b/Gemfile
@@ -85,7 +85,7 @@ end
# Active Storage
group :storage do
gem "aws-sdk-s3", require: false
- gem "google-cloud-storage", "~> 1.8", require: false
+ gem "google-cloud-storage", "~> 1.11", require: false
gem "azure-storage", require: false
gem "image_processing", "~> 1.2"
diff --git a/Gemfile.lock b/Gemfile.lock
index d5e3bafc53..8d3c3e70b7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -240,21 +240,21 @@ GEM
ffi (1.9.21-x86-mingw32)
globalid (0.4.1)
activesupport (>= 4.2.0)
- google-api-client (0.17.3)
+ google-api-client (0.19.8)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.5, < 0.7.0)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- google-cloud-core (1.1.0)
+ google-cloud-core (1.2.0)
google-cloud-env (~> 1.0)
google-cloud-env (1.0.1)
faraday (~> 0.11)
- google-cloud-storage (1.9.0)
+ google-cloud-storage (1.11.0)
digest-crc (~> 0.4)
- google-api-client (~> 0.17.0)
- google-cloud-core (~> 1.1)
+ google-api-client (~> 0.19.0)
+ google-cloud-core (~> 1.2)
googleauth (~> 0.6.2)
googleauth (0.6.2)
faraday (~> 0.12)
@@ -523,7 +523,7 @@ DEPENDENCIES
delayed_job
delayed_job_active_record
ffi (<= 1.9.21)
- google-cloud-storage (~> 1.8)
+ google-cloud-storage (~> 1.11)
hiredis
image_processing (~> 1.2)
json (>= 2.0.0)
diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md
index 60434451fe..287dd4fa12 100644
--- a/RELEASING_RAILS.md
+++ b/RELEASING_RAILS.md
@@ -157,7 +157,7 @@ break existing applications.
If you used Markdown format for your email, you can just paste it into the
blog.
-* http://weblog.rubyonrails.org
+* https://weblog.rubyonrails.org
### Post the announcement to the Rails Twitter account.
diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
index 17e72b46ff..35041fd072 100644
--- a/actionpack/lib/action_dispatch/http/content_security_policy.rb
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -126,6 +126,7 @@ module ActionDispatch #:nodoc:
manifest_src: "manifest-src",
media_src: "media-src",
object_src: "object-src",
+ prefetch_src: "prefetch-src",
script_src: "script-src",
style_src: "style-src",
worker_src: "worker-src"
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 7171b6942c..f0398dc7b1 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -189,6 +189,12 @@ module ActionDispatch
# merged into the Rack env hash.
# - +env+: Additional env to pass, as a Hash. The headers will be
# merged into the Rack env hash.
+ # - +xhr+: Set to `true` if you want to make and Ajax request.
+ # Adds request headers characteristic of XMLHttpRequest e.g. HTTP_X_REQUESTED_WITH.
+ # The headers will be merged into the Rack env hash.
+ # - +as+: Used for encoding the request with different content type.
+ # Supports `:json` by default and will set the approriate request headers.
+ # The headers will be merged into the Rack env hash.
#
# This method is rarely used directly. Use +#get+, +#post+, or other standard
# HTTP methods in integration tests. +#process+ is only required when using a
diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb
index c4c7f53903..4f9a4ff2bd 100644
--- a/actionpack/test/dispatch/content_security_policy_test.rb
+++ b/actionpack/test/dispatch/content_security_policy_test.rb
@@ -116,6 +116,12 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase
@policy.object_src false
assert_no_match %r{object-src}, @policy.build
+ @policy.prefetch_src :self
+ assert_match %r{prefetch-src 'self'}, @policy.build
+
+ @policy.prefetch_src false
+ assert_no_match %r{prefetch-src}, @policy.build
+
@policy.script_src :self
assert_match %r{script-src 'self'}, @policy.build
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 3832293251..ffc3d42592 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -70,18 +70,11 @@ module ActionView
end
private
- def find_template(finder, *args)
- name = args.first
- prefixes = args[1] || []
- partial = args[2] || false
- keys = args[3] || []
- options = args[4] || {}
+ def find_template(finder, name, prefixes, partial, keys)
finder.disable_cache do
- if format = finder.rendered_format
- finder.find_all(name, prefixes, partial, keys, options.merge(formats: [format])).first || finder.find_all(name, prefixes, partial, keys, options).first
- else
- finder.find_all(name, prefixes, partial, keys, options).first
- end
+ format = finder.rendered_format
+ result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format
+ result || finder.find_all(name, prefixes, partial, keys).first
end
end
end
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index da841ae45b..61d402cfca 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -88,7 +88,7 @@ module ActiveJob
"provider_job_id" => provider_job_id,
"queue_name" => queue_name,
"priority" => priority,
- "arguments" => serialize_arguments(arguments),
+ "arguments" => serialize_arguments_if_needed(arguments),
"executions" => executions,
"locale" => I18n.locale.to_s,
"timezone" => Time.zone.try(:name)
@@ -133,19 +133,31 @@ module ActiveJob
end
private
+ def serialize_arguments_if_needed(arguments)
+ if arguments_serialized?
+ @serialized_arguments
+ else
+ serialize_arguments(arguments)
+ end
+ end
+
def deserialize_arguments_if_needed
- if defined?(@serialized_arguments) && @serialized_arguments.present?
+ if arguments_serialized?
@arguments = deserialize_arguments(@serialized_arguments)
@serialized_arguments = nil
end
end
- def serialize_arguments(serialized_args)
- Arguments.serialize(serialized_args)
+ def serialize_arguments(arguments)
+ Arguments.serialize(arguments)
end
def deserialize_arguments(serialized_args)
Arguments.deserialize(serialized_args)
end
+
+ def arguments_serialized?
+ defined?(@serialized_arguments) && @serialized_arguments
+ end
end
end
diff --git a/activejob/test/cases/job_serialization_test.rb b/activejob/test/cases/job_serialization_test.rb
index 5c9994508e..86f3651564 100644
--- a/activejob/test/cases/job_serialization_test.rb
+++ b/activejob/test/cases/job_serialization_test.rb
@@ -23,16 +23,16 @@ class JobSerializationTest < ActiveSupport::TestCase
test "serialize and deserialize are symmetric" do
# Round trip a job in memory only
- h1 = HelloJob.new
- h1.deserialize(h1.serialize)
+ h1 = HelloJob.new("Rafael")
+ h2 = HelloJob.deserialize(h1.serialize)
+ assert_equal h1.serialize, h2.serialize
# Now verify it's identical to a JSON round trip.
# We don't want any non-native JSON elements in the job hash,
# like symbols.
- payload = JSON.dump(h1.serialize)
- h2 = HelloJob.new
- h2.deserialize(JSON.load(payload))
- assert_equal h1.serialize, h2.serialize
+ payload = JSON.dump(h2.serialize)
+ h3 = HelloJob.deserialize(JSON.load(payload))
+ assert_equal h2.serialize, h3.serialize
end
test "deserialize sets locale" do
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 36a3d59784..dda7d19915 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Fix logic on disabling commit callbacks so they are not called unexpectedly when errors occur.
+
+ *Brian Durand*
+
* Ensure `Associations::CollectionAssociation#size` and `Associations::CollectionAssociation#empty?`
use loaded association ids if present.
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 090b082cb0..4e8e7ee43b 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -107,6 +107,14 @@ module ActiveRecord
yield
end
end
+
+ def _create_record(attributes, raise_error = false)
+ unless owner.persisted?
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
+ end
+
+ super
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 441bd715e4..ead89bfe6c 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -63,10 +63,6 @@ module ActiveRecord
end
def _create_record(attributes, raise_error = false)
- unless owner.persisted?
- raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
- end
-
record = build_record(attributes)
yield(record) if block_given?
saved = record.save
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 97cba5d1c7..be4f41050e 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -340,7 +340,7 @@ module ActiveRecord
# Ensure that it is not called if the object was never persisted (failed create),
# but call it after the commit of a destroyed object.
def committed!(should_run_callbacks: true) #:nodoc:
- if should_run_callbacks && destroyed? || persisted?
+ if should_run_callbacks && (destroyed? || persisted?)
_run_commit_without_transaction_enrollment_callbacks
_run_commit_callbacks
end
diff --git a/activerecord/test/cases/arel/attributes/attribute_test.rb b/activerecord/test/cases/arel/attributes/attribute_test.rb
index 52573021a5..671e273543 100644
--- a/activerecord/test/cases/arel/attributes/attribute_test.rb
+++ b/activerecord/test/cases/arel/attributes/attribute_test.rb
@@ -978,7 +978,7 @@ module Arel
table = Table.new(:foo)
condition = table["id"].eq("1")
- refute table.able_to_type_cast?
+ assert_not table.able_to_type_cast?
condition.to_sql.must_equal %("foo"."id" = '1')
end
diff --git a/activerecord/test/cases/arel/helper.rb b/activerecord/test/cases/arel/helper.rb
index 1f8612f799..f8ce658440 100644
--- a/activerecord/test/cases/arel/helper.rb
+++ b/activerecord/test/cases/arel/helper.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "rubygems"
+require "active_support"
require "minitest/autorun"
require "arel"
@@ -13,7 +13,7 @@ class Object
end
module Arel
- class Test < Minitest::Test
+ class Test < ActiveSupport::TestCase
def setup
super
@arel_engine = Arel::Table.engine
@@ -24,11 +24,6 @@ module Arel
Arel::Table.engine = @arel_engine if defined? @arel_engine
super
end
-
- def assert_like(expected, actual)
- assert_equal expected.gsub(/\s+/, " ").strip,
- actual.gsub(/\s+/, " ").strip
- end
end
class Spec < Minitest::Spec
@@ -40,5 +35,11 @@ module Arel
after do
Arel::Table.engine = @arel_engine if defined? @arel_engine
end
+ include ActiveSupport::Testing::Assertions
+
+ # test/unit backwards compatibility methods
+ alias :assert_no_match :refute_match
+ alias :assert_not_equal :refute_equal
+ alias :assert_not_same :refute_same
end
end
diff --git a/activerecord/test/cases/arel/nodes/ascending_test.rb b/activerecord/test/cases/arel/nodes/ascending_test.rb
index 1efb16222a..4811e6ff5b 100644
--- a/activerecord/test/cases/arel/nodes/ascending_test.rb
+++ b/activerecord/test/cases/arel/nodes/ascending_test.rb
@@ -29,7 +29,7 @@ module Arel
def test_descending?
ascending = Ascending.new "zomg"
- assert !ascending.descending?
+ assert_not ascending.descending?
end
def test_equality_with_same_ivars
diff --git a/activerecord/test/cases/arel/nodes/binary_test.rb b/activerecord/test/cases/arel/nodes/binary_test.rb
index 9bc55a155b..d160e7cd9d 100644
--- a/activerecord/test/cases/arel/nodes/binary_test.rb
+++ b/activerecord/test/cases/arel/nodes/binary_test.rb
@@ -4,22 +4,24 @@ require_relative "../helper"
module Arel
module Nodes
- describe "Binary" do
- describe "#hash" do
- it "generates a hash based on its value" do
- eq = Equality.new("foo", "bar")
- eq2 = Equality.new("foo", "bar")
- eq3 = Equality.new("bar", "baz")
+ class NodesTest < Arel::Spec
+ describe "Binary" do
+ describe "#hash" do
+ it "generates a hash based on its value" do
+ eq = Equality.new("foo", "bar")
+ eq2 = Equality.new("foo", "bar")
+ eq3 = Equality.new("bar", "baz")
- assert_equal eq.hash, eq2.hash
- refute_equal eq.hash, eq3.hash
- end
+ assert_equal eq.hash, eq2.hash
+ assert_not_equal eq.hash, eq3.hash
+ end
- it "generates a hash specific to its class" do
- eq = Equality.new("foo", "bar")
- neq = NotEqual.new("foo", "bar")
+ it "generates a hash specific to its class" do
+ eq = Equality.new("foo", "bar")
+ neq = NotEqual.new("foo", "bar")
- refute_equal eq.hash, neq.hash
+ assert_not_equal eq.hash, neq.hash
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/nodes/case_test.rb b/activerecord/test/cases/arel/nodes/case_test.rb
index 2c087e624e..89861488df 100644
--- a/activerecord/test/cases/arel/nodes/case_test.rb
+++ b/activerecord/test/cases/arel/nodes/case_test.rb
@@ -4,79 +4,81 @@ require_relative "../helper"
module Arel
module Nodes
- describe "Case" do
- describe "#initialize" do
- it "sets case expression from first argument" do
- node = Case.new "foo"
+ class NodesTest < Arel::Spec
+ describe "Case" do
+ describe "#initialize" do
+ it "sets case expression from first argument" do
+ node = Case.new "foo"
- assert_equal "foo", node.case
- end
+ assert_equal "foo", node.case
+ end
- it "sets default case from second argument" do
- node = Case.new nil, "bar"
+ it "sets default case from second argument" do
+ node = Case.new nil, "bar"
- assert_equal "bar", node.default
+ assert_equal "bar", node.default
+ end
end
- end
- describe "#clone" do
- it "clones case, conditions and default" do
- foo = Nodes.build_quoted "foo"
+ describe "#clone" do
+ it "clones case, conditions and default" do
+ foo = Nodes.build_quoted "foo"
- node = Case.new
- node.case = foo
- node.conditions = [When.new(foo, foo)]
- node.default = foo
+ node = Case.new
+ node.case = foo
+ node.conditions = [When.new(foo, foo)]
+ node.default = foo
- dolly = node.clone
+ dolly = node.clone
- assert_equal dolly.case, node.case
- refute_same dolly.case, node.case
+ assert_equal dolly.case, node.case
+ assert_not_same dolly.case, node.case
- assert_equal dolly.conditions, node.conditions
- refute_same dolly.conditions, node.conditions
+ assert_equal dolly.conditions, node.conditions
+ assert_not_same dolly.conditions, node.conditions
- assert_equal dolly.default, node.default
- refute_same dolly.default, node.default
+ assert_equal dolly.default, node.default
+ assert_not_same dolly.default, node.default
+ end
end
- end
- describe "equality" do
- it "is equal with equal ivars" do
- foo = Nodes.build_quoted "foo"
- one = Nodes.build_quoted 1
- zero = Nodes.build_quoted 0
+ describe "equality" do
+ it "is equal with equal ivars" do
+ foo = Nodes.build_quoted "foo"
+ one = Nodes.build_quoted 1
+ zero = Nodes.build_quoted 0
- case1 = Case.new foo
- case1.conditions = [When.new(foo, one)]
- case1.default = Else.new zero
+ case1 = Case.new foo
+ case1.conditions = [When.new(foo, one)]
+ case1.default = Else.new zero
- case2 = Case.new foo
- case2.conditions = [When.new(foo, one)]
- case2.default = Else.new zero
+ case2 = Case.new foo
+ case2.conditions = [When.new(foo, one)]
+ case2.default = Else.new zero
- array = [case1, case2]
+ array = [case1, case2]
- assert_equal 1, array.uniq.size
- end
+ assert_equal 1, array.uniq.size
+ end
- it "is not equal with different ivars" do
- foo = Nodes.build_quoted "foo"
- bar = Nodes.build_quoted "bar"
- one = Nodes.build_quoted 1
- zero = Nodes.build_quoted 0
+ it "is not equal with different ivars" do
+ foo = Nodes.build_quoted "foo"
+ bar = Nodes.build_quoted "bar"
+ one = Nodes.build_quoted 1
+ zero = Nodes.build_quoted 0
- case1 = Case.new foo
- case1.conditions = [When.new(foo, one)]
- case1.default = Else.new zero
+ case1 = Case.new foo
+ case1.conditions = [When.new(foo, one)]
+ case1.default = Else.new zero
- case2 = Case.new foo
- case2.conditions = [When.new(bar, one)]
- case2.default = Else.new zero
+ case2 = Case.new foo
+ case2.conditions = [When.new(bar, one)]
+ case2.default = Else.new zero
- array = [case1, case2]
+ array = [case1, case2]
- assert_equal 2, array.uniq.size
+ assert_equal 2, array.uniq.size
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/nodes/descending_test.rb b/activerecord/test/cases/arel/nodes/descending_test.rb
index 45e22de17b..5f1747e1da 100644
--- a/activerecord/test/cases/arel/nodes/descending_test.rb
+++ b/activerecord/test/cases/arel/nodes/descending_test.rb
@@ -24,7 +24,7 @@ module Arel
def test_ascending?
descending = Descending.new "zomg"
- assert !descending.ascending?
+ assert_not descending.ascending?
end
def test_descending?
diff --git a/activerecord/test/cases/arel/nodes/select_core_test.rb b/activerecord/test/cases/arel/nodes/select_core_test.rb
index 1cdc7a2360..0b698205ff 100644
--- a/activerecord/test/cases/arel/nodes/select_core_test.rb
+++ b/activerecord/test/cases/arel/nodes/select_core_test.rb
@@ -17,9 +17,9 @@ module Arel
assert_equal core.projections, dolly.projections
assert_equal core.wheres, dolly.wheres
- refute_same core.froms, dolly.froms
- refute_same core.projections, dolly.projections
- refute_same core.wheres, dolly.wheres
+ assert_not_same core.froms, dolly.froms
+ assert_not_same core.projections, dolly.projections
+ assert_not_same core.wheres, dolly.wheres
end
def test_set_quantifier
diff --git a/activerecord/test/cases/arel/select_manager_test.rb b/activerecord/test/cases/arel/select_manager_test.rb
index 1bc9f6abf2..f318577b94 100644
--- a/activerecord/test/cases/arel/select_manager_test.rb
+++ b/activerecord/test/cases/arel/select_manager_test.rb
@@ -1144,7 +1144,7 @@ module Arel
assert_match("LIMIT", manager.to_sql)
manager.limit = nil
- refute_match("LIMIT", manager.to_sql)
+ assert_no_match("LIMIT", manager.to_sql)
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index a85b56ac4b..5011a9bbde 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -272,6 +272,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple, citibank.firm
end
+ def test_creating_the_belonging_object_from_new_record
+ citibank = Account.new("credit_limit" => 10)
+ apple = citibank.create_firm("name" => "Apple")
+ assert_equal apple, citibank.firm
+ citibank.save
+ citibank.reload
+ assert_equal apple, citibank.firm
+ end
+
def test_creating_the_belonging_object_with_primary_key
client = Client.create(name: "Primary key client")
apple = client.create_firm_with_primary_key("name" => "Apple")
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index fd008ca8e3..fcfab074a2 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -982,7 +982,7 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_clear_cash_when_setting_table_name
+ def test_clear_cache_when_setting_table_name
original_table_name = Joke.table_name
Joke.table_name = "funny_jokes"
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index f9b2dc0c73..f8fecc83cd 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -135,9 +135,12 @@ module ActiveRecord
end
def test_remove_named_index
- connection.add_index :testings, :foo, name: "custom_index_name"
+ connection.add_index :testings, :foo, name: "index_testings_on_custom_index_name"
assert connection.index_exists?(:testings, :foo)
+
+ assert_raise(ArgumentError) { connection.remove_index(:testings, "custom_index_name") }
+
connection.remove_index :testings, :foo
assert_not connection.index_exists?(:testings, :foo)
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 024b5bd8a1..409b07e56c 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "active_support/test_case"
+require "active_support"
require "active_support/testing/autorun"
require "active_support/testing/method_call_assertions"
require "active_support/testing/stream"
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index e89ac53732..05941c75ac 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -367,6 +367,26 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_match(/:on conditions for after_commit and after_rollback callbacks have to be one of \[:create, :destroy, :update\]/, e.message)
end
+ def test_after_commit_chain_not_called_on_errors
+ record_1 = TopicWithCallbacks.create!
+ record_2 = TopicWithCallbacks.create!
+ record_3 = TopicWithCallbacks.create!
+ callbacks = []
+ record_1.after_commit_block { raise }
+ record_2.after_commit_block { callbacks << record_2.id }
+ record_3.after_commit_block { callbacks << record_3.id }
+ begin
+ TopicWithCallbacks.transaction do
+ record_1.save!
+ record_2.save!
+ record_3.save!
+ end
+ rescue
+ # From record_1.after_commit
+ end
+ assert_equal [], callbacks
+ end
+
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_call_callbacks_on_the_parent_object
pet = Pet.first
owner = pet.owner
diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md
index 60d7d19540..4c12adae56 100644
--- a/activestorage/CHANGELOG.md
+++ b/activestorage/CHANGELOG.md
@@ -1,3 +1,8 @@
+* The Google Cloud Storage service properly supports streaming downloads.
+ It now requires version 1.11 or newer of the google-cloud-storage gem.
+
+ *George Claghorn*
+
* Use the [ImageProcessing](https://github.com/janko-m/image_processing) gem
for Active Storage variants, and deprecate the MiniMagick backend.
diff --git a/activestorage/app/controllers/active_storage/disk_controller.rb b/activestorage/app/controllers/active_storage/disk_controller.rb
index 7bc5eb3fdb..63918eb6f4 100644
--- a/activestorage/app/controllers/active_storage/disk_controller.rb
+++ b/activestorage/app/controllers/active_storage/disk_controller.rb
@@ -5,21 +5,30 @@
# Always go through the BlobsController, or your own authenticated controller, rather than directly
# to the service url.
class ActiveStorage::DiskController < ActiveStorage::BaseController
+ include ActionController::Live
+
skip_forgery_protection
def show
if key = decode_verified_key
- send_data disk_service.download(key),
- disposition: params[:disposition], content_type: params[:content_type]
+ response.headers["Content-Type"] = params[:content_type] || DEFAULT_SEND_FILE_TYPE
+ response.headers["Content-Disposition"] = params[:disposition] || DEFAULT_SEND_FILE_DISPOSITION
+
+ disk_service.download key do |chunk|
+ response.stream.write chunk
+ end
else
head :not_found
end
+ ensure
+ response.stream.close
end
def update
if token = decode_verified_token
if acceptable_content?(token)
disk_service.upload token[:key], request.body, checksum: token[:checksum]
+ head :no_content
else
head :unprocessable_entity
end
@@ -28,6 +37,8 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
end
rescue ActiveStorage::IntegrityError
head :unprocessable_entity
+ ensure
+ response.stream.close
end
private
diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb
index 7a1839a1aa..38acef81f4 100644
--- a/activestorage/lib/active_storage/service/gcs_service.rb
+++ b/activestorage/lib/active_storage/service/gcs_service.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-gem "google-cloud-storage", "~> 1.8"
+gem "google-cloud-storage", "~> 1.11"
require "google/cloud/storage"
require "net/http"
@@ -32,28 +32,21 @@ module ActiveStorage
end
end
- # FIXME: Download in chunks when given a block.
- def download(key)
- instrument :download, key: key do
- io = file_for(key).download
- io.rewind
-
- if block_given?
- yield io.string
- else
- io.string
+ def download(key, &block)
+ if block_given?
+ instrument :streaming_download, key: key do
+ stream(key, &block)
+ end
+ else
+ instrument :download, key: key do
+ file_for(key).download.string
end
end
end
def download_chunk(key, range)
instrument :download_chunk, key: key, range: range do
- file = file_for(key)
- uri = URI(file.signed_url(expires: 30.seconds))
-
- Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client|
- client.get(uri, "Range" => "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body
- end
+ file_for(key).download(range: range).string
end
end
@@ -111,8 +104,21 @@ module ActiveStorage
private
attr_reader :config
- def file_for(key)
- bucket.file(key, skip_lookup: true)
+ def file_for(key, skip_lookup: true)
+ bucket.file(key, skip_lookup: skip_lookup)
+ end
+
+ # Reads the file for the given key in chunks, yielding each to the block.
+ def stream(key)
+ file = file_for(key, skip_lookup: false)
+
+ chunk_size = 5.megabytes
+ offset = 0
+
+ while offset < file.size
+ yield file.download(range: offset..(offset + chunk_size - 1)).string
+ offset += chunk_size
+ end
end
def bucket
diff --git a/activestorage/test/controllers/disk_controller_test.rb b/activestorage/test/controllers/disk_controller_test.rb
index 940dbf5918..9af7c83bdf 100644
--- a/activestorage/test/controllers/disk_controller_test.rb
+++ b/activestorage/test/controllers/disk_controller_test.rb
@@ -8,16 +8,18 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
blob = create_blob
get blob.service_url
- assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", @response.headers["Content-Disposition"]
- assert_equal "text/plain", @response.headers["Content-Type"]
+ assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
+ assert_equal "text/plain", response.headers["Content-Type"]
+ assert_equal "Hello world!", response.body
end
test "showing blob as attachment" do
blob = create_blob
get blob.service_url(disposition: :attachment)
- assert_equal "attachment; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", @response.headers["Content-Disposition"]
- assert_equal "text/plain", @response.headers["Content-Type"]
+ assert_equal "attachment; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
+ assert_equal "text/plain", response.headers["Content-Type"]
+ assert_equal "Hello world!", response.body
end
diff --git a/activestorage/test/service/shared_service_tests.rb b/activestorage/test/service/shared_service_tests.rb
index 24debe7f47..b9f352e460 100644
--- a/activestorage/test/service/shared_service_tests.rb
+++ b/activestorage/test/service/shared_service_tests.rb
@@ -51,13 +51,21 @@ module ActiveStorage::Service::SharedServiceTests
end
test "downloading in chunks" do
- chunks = []
+ key = SecureRandom.base58(24)
+ expected_chunks = [ "a" * 5.megabytes, "b" ]
+ actual_chunks = []
- @service.download(FIXTURE_KEY) do |chunk|
- chunks << chunk
- end
+ begin
+ @service.upload key, StringIO.new(expected_chunks.join)
+
+ @service.download key do |chunk|
+ actual_chunks << chunk
+ end
- assert_equal [ FIXTURE_DATA ], chunks
+ assert_equal expected_chunks, actual_chunks, "Downloaded chunks did not match uploaded data"
+ ensure
+ @service.delete key
+ end
end
test "downloading partially" do
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index 750f858fcc..6b0dcab905 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -4,6 +4,6 @@ class LoadError
# Returns true if the given path name (except perhaps for the ".rb"
# extension) is the missing file which caused the exception to be raised.
def is_missing?(location)
- location.sub(/\.rb$/, "".freeze) == path.sub(/\.rb$/, "".freeze)
+ location.sub(/\.rb$/, "".freeze) == path.to_s.sub(/\.rb$/, "".freeze)
end
end
diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb
index 41b11d0c33..126aa51cb4 100644
--- a/activesupport/test/core_ext/load_error_test.rb
+++ b/activesupport/test/core_ext/load_error_test.rb
@@ -7,13 +7,20 @@ class TestLoadError < ActiveSupport::TestCase
def test_with_require
assert_raise(LoadError) { require "no_this_file_don't_exist" }
end
+
def test_with_load
assert_raise(LoadError) { load "nor_does_this_one" }
end
+
def test_path
begin load "nor/this/one.rb"
rescue LoadError => e
assert_equal "nor/this/one.rb", e.path
end
end
+
+ def test_is_missing_with_nil_path
+ error = LoadError.new(nil)
+ assert_nothing_raised { error.is_missing?("anything") }
+ end
end
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index 8b91b4853f..005331977e 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -60,7 +60,7 @@ This will put the guides inside `Rails.root/doc/guides` and you may start surfin
* Major contributions from [Xavier Noria](http://advogato.org/person/fxn/diary.html) and [Hongli Lai](http://izumi.plan99.net/blog/).
* More information:
* [Rails Guides hackfest](http://hackfest.rubyonrails.org/guide)
- * [Help improve Rails documentation on Git branch](http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch)
+ * [Help improve Rails documentation on Git branch](https://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch)
Better integration with HTTP : Out of the box ETag support
----------------------------------------------------------
@@ -112,7 +112,7 @@ config.threadsafe!
* More information :
* [Thread safety for your Rails](http://m.onkey.org/2008/10/23/thread-safety-for-your-rails)
- * [Thread safety project announcement](http://weblog.rubyonrails.org/2008/8/16/josh-peek-officially-joins-the-rails-core)
+ * [Thread safety project announcement](https://weblog.rubyonrails.org/2008/8/16/josh-peek-officially-joins-the-rails-core)
* [Q/A: What Thread-safe Rails Means](http://blog.headius.com/2008/08/qa-what-thread-safe-rails-means.html)
Active Record
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 634569fa2d..bc44ff4152 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -54,7 +54,7 @@ Documentation
The [Ruby on Rails guides](http://guides.rubyonrails.org/) project has published several additional guides for Rails 2.3. In addition, a [separate site](http://edgeguides.rubyonrails.org/) maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the [Rails wiki](http://newwiki.rubyonrails.org/) and early planning for a Rails Book.
-* More Information: [Rails Documentation Projects](http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects)
+* More Information: [Rails Documentation Projects](https://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects)
Ruby 1.9.1 Support
------------------
@@ -89,7 +89,7 @@ accepts_nested_attributes_for :author,
```
* Lead Contributor: [Eloy Duran](http://superalloy.nl/)
-* More Information: [Nested Model Forms](http://weblog.rubyonrails.org/2009/1/26/nested-model-forms)
+* More Information: [Nested Model Forms](https://weblog.rubyonrails.org/2009/1/26/nested-model-forms)
### Nested Transactions
@@ -376,7 +376,7 @@ You can write this view in Rails 2.3:
* Lead Contributor: [Eloy Duran](http://superalloy.nl/)
* More Information:
- * [Nested Model Forms](http://weblog.rubyonrails.org/2009/1/26/nested-model-forms)
+ * [Nested Model Forms](https://weblog.rubyonrails.org/2009/1/26/nested-model-forms)
* [complex-form-examples](https://github.com/alloy/complex-form-examples)
* [What's New in Edge Rails: Nested Object Forms](http://archives.ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes)
@@ -552,7 +552,7 @@ In addition to the Rack changes covered above, Railties (the core code of Rails
Rails Metal is a new mechanism that provides superfast endpoints inside of your Rails applications. Metal classes bypass routing and Action Controller to give you raw speed (at the cost of all the things in Action Controller, of course). This builds on all of the recent foundation work to make Rails a Rack application with an exposed middleware stack. Metal endpoints can be loaded from your application or from plugins.
* More Information:
- * [Introducing Rails Metal](http://weblog.rubyonrails.org/2008/12/17/introducing-rails-metal)
+ * [Introducing Rails Metal](https://weblog.rubyonrails.org/2008/12/17/introducing-rails-metal)
* [Rails Metal: a micro-framework with the power of Rails](http://soylentfoo.jnewland.com/articles/2008/12/16/rails-metal-a-micro-framework-with-the-power-of-rails-m)
* [Metal: Super-fast Endpoints within your Rails Apps](http://www.railsinside.com/deployment/180-metal-super-fast-endpoints-within-your-rails-apps.html)
* [What's New in Edge Rails: Rails Metal](http://archives.ryandaigle.com/articles/2008/12/18/what-s-new-in-edge-rails-rails-metal)
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index 7ffa7d4a5c..b3c1be3d61 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -155,7 +155,7 @@ Documentation
The documentation in the Rails tree is being updated with all the API changes, additionally, the [Rails Edge Guides](http://edgeguides.rubyonrails.org/) are being updated one by one to reflect the changes in Rails 3.0. The guides at [guides.rubyonrails.org](http://guides.rubyonrails.org/) however will continue to contain only the stable version of Rails (at this point, version 2.3.5, until 3.0 is released).
-More Information: - [Rails Documentation Projects](http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects)
+More Information: - [Rails Documentation Projects](https://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects)
Internationalization
@@ -250,7 +250,7 @@ Deprecations:
More Information:
* [Render Options in Rails 3](https://blog.engineyard.com/2010/render-options-in-rails-3)
-* [Three reasons to love ActionController::Responder](http://weblog.rubyonrails.org/2009/8/31/three-reasons-love-responder)
+* [Three reasons to love ActionController::Responder](https://weblog.rubyonrails.org/2009/8/31/three-reasons-love-responder)
### Action Dispatch
diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md
index 54266c494a..09af899074 100644
--- a/guides/source/active_storage_overview.md
+++ b/guides/source/active_storage_overview.md
@@ -416,14 +416,14 @@ end
```
If the external program is run as a separate program, you might also want to
-chmod the file and it's directory, as it is unaccessible by other users because
-Tempfile will set the permissions to 0600.
+`chmod` the file and it's directory, as it is inaccessible by other users
+because `Tempfile` will set the permissions to `0600`.
Transforming Images
-------------------
-To create variation of the image, call `variant` on the Blob. You can pass
+To create a variation of the image, call `variant` on the `Blob`. You can pass
any transformation to the method supported by the processor. The default
processor is [MiniMagick](https://github.com/minimagick/minimagick), but you
can also use [Vips](http://www.rubydoc.info/gems/ruby-vips/Vips/Image).
@@ -434,8 +434,8 @@ To enable variants, add the `image_processing` gem to your `Gemfile`:
gem 'image_processing', '~> 1.2'
```
-When the browser hits the variant URL, Active Storage will lazy transform the
-original blob into the format you specified and redirect to its new service
+When the browser hits the variant URL, Active Storage will lazily transform the
+original blob into the specified format and redirect to its new service
location.
```erb
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index 5428b16edc..ab411201e8 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -231,9 +231,9 @@ is not entirely equivalent to the one of the body of the definitions using the
assignment.
Thus, when one informally says "the `String` class", that really means: the
-class object stored in the constant called "String" in the class object stored
-in the `Object` constant. `String` is otherwise an ordinary Ruby constant and
-everything related to constants such as resolution algorithms applies to it.
+class object stored in the constant called "String" and this "String" constant
+gets stored in `Object` class. `String` is otherwise an ordinary Ruby constant
+and everything related to constants such as resolution algorithms applies to it.
Likewise, in the controller
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 1fdfbf1cff..ca77430479 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -809,7 +809,7 @@ private
TIP: For more information, refer to the reference above and
[this blog article about Strong Parameters]
-(http://weblog.rubyonrails.org/2012/3/21/strong-parameters/).
+(https://weblog.rubyonrails.org/2012/3/21/strong-parameters/).
### Showing Articles
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index d2024b726d..4ed2793fe3 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -29,7 +29,7 @@
More Ruby on Rails
</span>
<ul class="more-info-links s-hidden">
- <li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
+ <li class="more-info"><a href="https://weblog.rubyonrails.org/">Blog</a></li>
<li class="more-info"><a href="http://guides.rubyonrails.org/">Guides</a></li>
<li class="more-info"><a href="http://api.rubyonrails.org/">API</a></li>
<li class="more-info"><a href="https://stackoverflow.com/questions/tagged/ruby-on-rails">Ask for help</a></li>
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index c2fe012eeb..55e78a47de 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -1132,7 +1132,7 @@ being used, you can update your form to use the `PUT` method instead:
<%= form_for [ :update_name, @user ], method: :put do |f| %>
```
-For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/26/edge-rails-patch-is-the-new-primary-http-method-for-updates/)
+For more on PATCH and why this change was made, see [this post](https://weblog.rubyonrails.org/2012/2/26/edge-rails-patch-is-the-new-primary-http-method-for-updates/)
on the Rails blog.
#### A note about media types
diff --git a/railties/lib/rails/command/spellchecker.rb b/railties/lib/rails/command/spellchecker.rb
index 154358cd45..04485097fa 100644
--- a/railties/lib/rails/command/spellchecker.rb
+++ b/railties/lib/rails/command/spellchecker.rb
@@ -3,8 +3,55 @@
module Rails
module Command
module Spellchecker # :nodoc:
- def self.suggest(word, from:)
- DidYouMean::SpellChecker.new(dictionary: from.map(&:to_s)).correct(word).first
+ class << self
+ def suggest(word, from:)
+ if defined?(DidYouMean::SpellChecker)
+ DidYouMean::SpellChecker.new(dictionary: from.map(&:to_s)).correct(word).first
+ else
+ from.sort_by { |w| levenshtein_distance(word, w) }.first
+ end
+ end
+
+ private
+
+ # This code is based directly on the Text gem implementation.
+ # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
+ #
+ # Returns a value representing the "cost" of transforming str1 into str2.
+ def levenshtein_distance(str1, str2) # :doc:
+ s = str1
+ t = str2
+ n = s.length
+ m = t.length
+
+ return m if (0 == n)
+ return n if (0 == m)
+
+ d = (0..m).to_a
+ x = nil
+
+ # avoid duplicating an enumerable object in the loop
+ str2_codepoint_enumerable = str2.each_codepoint
+
+ str1.each_codepoint.with_index do |char1, i|
+ e = i + 1
+
+ str2_codepoint_enumerable.with_index do |char2, j|
+ cost = (char1 == char2) ? 0 : 1
+ x = [
+ d[j + 1] + 1, # insertion
+ e + 1, # deletion
+ d[j] + cost # substitution
+ ].min
+ d[j] = e
+ e = x
+ end
+
+ d[m] = x
+ end
+
+ x
+ end
end
end
end