diff options
42 files changed, 317 insertions, 168 deletions
@@ -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 |