From f7b27513f794a17e90b1a709e22e9777d88eb487 Mon Sep 17 00:00:00 2001 From: abhishekkanojia Date: Thu, 1 Mar 2018 19:25:51 +0530 Subject: [PATCH 01/30] Remove index:true option from belongs to as defaults to true. --- guides/source/association_basics.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 9616647f153..5fddf671714 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -109,7 +109,7 @@ class CreateBooks < ActiveRecord::Migration[5.0] end create_table :books do |t| - t.belongs_to :author, index: true + t.belongs_to :author t.datetime :published_at t.timestamps end @@ -140,7 +140,7 @@ class CreateSuppliers < ActiveRecord::Migration[5.0] end create_table :accounts do |t| - t.belongs_to :supplier, index: true + t.belongs_to :supplier t.string :account_number t.timestamps end @@ -184,7 +184,7 @@ class CreateAuthors < ActiveRecord::Migration[5.0] end create_table :books do |t| - t.belongs_to :author, index: true + t.belongs_to :author t.datetime :published_at t.timestamps end @@ -231,8 +231,8 @@ class CreateAppointments < ActiveRecord::Migration[5.0] end create_table :appointments do |t| - t.belongs_to :physician, index: true - t.belongs_to :patient, index: true + t.belongs_to :physician + t.belongs_to :patient t.datetime :appointment_date t.timestamps end @@ -312,13 +312,13 @@ class CreateAccountHistories < ActiveRecord::Migration[5.0] end create_table :accounts do |t| - t.belongs_to :supplier, index: true + t.belongs_to :supplier t.string :account_number t.timestamps end create_table :account_histories do |t| - t.belongs_to :account, index: true + t.belongs_to :account t.integer :credit_rating t.timestamps end @@ -358,8 +358,8 @@ class CreateAssembliesAndParts < ActiveRecord::Migration[5.0] end create_table :assemblies_parts, id: false do |t| - t.belongs_to :assembly, index: true - t.belongs_to :part, index: true + t.belongs_to :assembly + t.belongs_to :part end end end @@ -517,7 +517,7 @@ In your migrations/schema, you will add a references column to the model itself. class CreateEmployees < ActiveRecord::Migration[5.0] def change create_table :employees do |t| - t.references :manager, index: true + t.references :manager t.timestamps end end From 6865f73b9124eb7c89320f31bd893648a7819653 Mon Sep 17 00:00:00 2001 From: Orhan Toy Date: Thu, 11 Oct 2018 13:29:34 +0200 Subject: [PATCH 02/30] [ci skip] Fix link to Concurrent::ThreadPoolExecutor docs --- activejob/lib/active_job/queue_adapters/async_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activejob/lib/active_job/queue_adapters/async_adapter.rb b/activejob/lib/active_job/queue_adapters/async_adapter.rb index ebf6f384e3c..53a7e3d53e5 100644 --- a/activejob/lib/active_job/queue_adapters/async_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/async_adapter.rb @@ -31,7 +31,7 @@ module ActiveJob # jobs. Since jobs share a single thread pool, long-running jobs will block # short-lived jobs. Fine for dev/test; bad for production. class AsyncAdapter - # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options. + # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ThreadPoolExecutor.html] for executor options. def initialize(**executor_options) @scheduler = Scheduler.new(**executor_options) end From 2e53c2b1304cbb154fc633efd09a283ca377f016 Mon Sep 17 00:00:00 2001 From: Gannon McGibbon Date: Wed, 10 Oct 2018 18:33:09 -0400 Subject: [PATCH 03/30] Add test retries for railties --- Gemfile | 1 + Gemfile.lock | 5 ++++- railties/test/isolation/abstract_unit.rb | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index c630f954823..fb15bf127ee 100644 --- a/Gemfile +++ b/Gemfile @@ -99,6 +99,7 @@ instance_eval File.read local_gemfile if File.exist? local_gemfile group :test do gem "minitest-bisect" + gem "minitest-retry" platforms :mri do gem "stackprof" diff --git a/Gemfile.lock b/Gemfile.lock index b4f46698bc8..927b36e9fca 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -323,6 +323,8 @@ GEM minitest-bisect (1.4.0) minitest-server (~> 1.0) path_expander (~> 1.0) + minitest-retry (0.1.9) + minitest (>= 5.0) minitest-server (1.0.5) minitest (~> 5.0) mono_logger (1.1.0) @@ -538,6 +540,7 @@ DEPENDENCIES libxml-ruby listen (>= 3.0.5, < 3.2) minitest-bisect + minitest-retry mysql2 (>= 0.4.10) nokogiri (>= 1.8.1) pg (>= 0.18.0) @@ -576,4 +579,4 @@ DEPENDENCIES websocket-client-simple! BUNDLED WITH - 1.16.5 + 1.16.6 diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index e44f21380ea..d4eed69a871 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -16,6 +16,9 @@ require "active_support/testing/autorun" require "active_support/testing/stream" require "active_support/testing/method_call_assertions" require "active_support/test_case" +require "minitest/retry" + +Minitest::Retry.use!(verbose: false, retry_count: 1) RAILS_FRAMEWORK_ROOT = File.expand_path("../../..", __dir__) From d888bec6dacd6445e3afee8737aaaf5e73256992 Mon Sep 17 00:00:00 2001 From: Gannon McGibbon Date: Thu, 11 Oct 2018 10:42:53 -0400 Subject: [PATCH 04/30] Include test gems in CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a4f5fa045d7..f703ed0f90b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ addons: - postgresql-10 - postgresql-client-10 -bundler_args: --without test --jobs 3 --retry 3 +bundler_args: --jobs 3 --retry 3 before_install: - "rm ${BUNDLE_GEMFILE}.lock" - "travis_retry gem update --system" From 8df7ed3b88fc9f19446d7207a745a331893b81cd Mon Sep 17 00:00:00 2001 From: Eileen Uchitelle Date: Thu, 11 Oct 2018 15:28:52 -0400 Subject: [PATCH 05/30] Test that nested structs to_json works as expected Check that options passed to the to_json are passed to all objects that respond to as_json. --- activesupport/test/json/encoding_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index ebc5df14dd0..80628733860 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -157,6 +157,16 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal({ "foo" => "hello" }, JSON.parse(json)) end + def test_struct_to_json_with_options_nested + klass = Struct.new(:foo, :bar) + struct = klass.new "hello", "world" + parent_struct = klass.new struct, "world" + json = parent_struct.to_json only: [:foo] + + assert_equal({ "foo" => { "foo" => "hello" } }, JSON.parse(json)) + end + + def test_hash_should_pass_encoding_options_to_children_in_as_json person = { name: "John", From e52b223487c4a72ccdd6d631318fc3cfcf8097ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Rodr=C3=ADguez?= Date: Mon, 8 Oct 2018 10:57:18 +0200 Subject: [PATCH 06/30] Deprecate Unicode#downcase/upcase/swapcase. Use String methods directly instead. --- activesupport/CHANGELOG.md | 5 ++++ .../lib/active_support/multibyte/chars.rb | 25 ++----------------- .../lib/active_support/multibyte/unicode.rb | 17 ++++++------- activesupport/test/multibyte_chars_test.rb | 6 +++++ 4 files changed, 21 insertions(+), 32 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index b796df26aa3..4e709e0a303 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,8 @@ +* Deprecate `ActiveSupport::Multibyte::Unicode#downcase/upcase/swapcase` in favor of + `String#downcase/upcase/swapcase`. + + *Francesco Rodríguez* + * Add `ActiveSupport::ParameterFilter`. *Yoshiyuki Kinjo* diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 499a206f49a..fec6667ce7a 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -120,27 +120,6 @@ module ActiveSupport #:nodoc: slice(0...translate_offset(limit)) end - # Converts characters in the string to uppercase. - # - # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?" - def upcase - chars Unicode.upcase(@wrapped_string) - end - - # Converts characters in the string to lowercase. - # - # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum" - def downcase - chars Unicode.downcase(@wrapped_string) - end - - # Converts characters in the string to the opposite case. - # - # 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN" - def swapcase - chars Unicode.swapcase(@wrapped_string) - end - # Converts the first character to uppercase and the remainder to lowercase. # # 'über'.mb_chars.capitalize.to_s # => "Über" @@ -153,7 +132,7 @@ module ActiveSupport #:nodoc: # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró" # "日本語".mb_chars.titleize.to_s # => "日本語" def titleize - chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) }) + chars(downcase.to_s.gsub(/\b('?\S)/u) { $1.upcase }) end alias_method :titlecase, :titleize @@ -205,7 +184,7 @@ module ActiveSupport #:nodoc: to_s.as_json(options) end - %w(capitalize downcase reverse tidy_bytes upcase).each do |method| + %w(capitalize reverse tidy_bytes).each do |method| define_method("#{method}!") do |*args| @wrapped_string = send(method, *args).to_s self diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 4f0e1165efb..a3261126d6b 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -115,16 +115,15 @@ module ActiveSupport end end - def downcase(string) - string.downcase - end + %w(downcase upcase swapcase).each do |method| + define_method(method) do |string| + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveSupport::Multibyte::Unicode##{method} is deprecated and + will be removed from Rails 6.1. Use String methods directly. + MSG - def upcase(string) - string.upcase - end - - def swapcase(string) - string.swapcase + string.send(method) + end end private diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 11c48227487..af637799177 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -719,6 +719,12 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase assert_equal BYTE_STRING.dup.mb_chars.class, ActiveSupport::Multibyte::Chars end + def test_unicode_deprecations + assert_deprecated { ActiveSupport::Multibyte::Unicode.downcase("") } + assert_deprecated { ActiveSupport::Multibyte::Unicode.upcase("") } + assert_deprecated { ActiveSupport::Multibyte::Unicode.swapcase("") } + end + private def string_from_classes(classes) From 16e3b65674e3a41afe0415c88c75667e72fd0de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Rodr=C3=ADguez?= Date: Thu, 11 Oct 2018 11:00:46 +0200 Subject: [PATCH 07/30] Use native String#capitalize --- activesupport/lib/active_support/multibyte/chars.rb | 9 +-------- activesupport/test/multibyte_chars_test.rb | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index fec6667ce7a..01a0cc131f7 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -120,13 +120,6 @@ module ActiveSupport #:nodoc: slice(0...translate_offset(limit)) end - # Converts the first character to uppercase and the remainder to lowercase. - # - # 'über'.mb_chars.capitalize.to_s # => "Über" - def capitalize - (slice(0) || chars("")).upcase + (slice(1..-1) || chars("")).downcase - end - # Capitalizes the first letter of every word, when possible. # # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró" @@ -184,7 +177,7 @@ module ActiveSupport #:nodoc: to_s.as_json(options) end - %w(capitalize reverse tidy_bytes).each do |method| + %w(reverse tidy_bytes).each do |method| define_method("#{method}!") do |*args| @wrapped_string = send(method, *args).to_s self diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index af637799177..3585b1ed32d 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -477,7 +477,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase def test_method_works_for_proxyed_methods assert_equal "ll", "hello".mb_chars.method(:slice).call(2..3) # Defined on Chars - chars = "hello".mb_chars + chars = +"hello".mb_chars assert_equal "Hello", chars.method(:capitalize!).call # Defined on Chars assert_equal "Hello", chars assert_equal "jello", "hello".mb_chars.method(:gsub).call(/h/, "j") # Defined on String From 29b1984a05db7c556bde2a7a79a9062fbdf1a4da Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Fri, 12 Oct 2018 18:38:25 +0900 Subject: [PATCH 08/30] Runs the generator before assertions --- railties/test/generators/app_generator_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index a1d91a2f986..be97abfa52f 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -209,6 +209,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_new_application_doesnt_need_defaults + run_generator assert_no_file "config/initializers/new_framework_defaults_6_0.rb" end From ee95bed3e6e16eadd1940d9c27953236f1649c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Rodr=C3=ADguez?= Date: Fri, 12 Oct 2018 17:40:29 +0200 Subject: [PATCH 09/30] Deprecate Unicode#normalize and Chars#normalize (#34202) --- activesupport/CHANGELOG.md | 5 + .../active_support/inflector/transliterate.rb | 6 +- .../lib/active_support/multibyte/chars.rb | 21 +++- .../lib/active_support/multibyte/unicode.rb | 33 ++++--- activesupport/test/multibyte_chars_test.rb | 73 +++++++++++--- .../test/multibyte_conformance_test.rb | 98 ++++++++++--------- ...ultibyte_normalization_conformance_test.rb | 98 ++++++++++--------- 7 files changed, 212 insertions(+), 122 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 4e709e0a303..2e465a0310a 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,8 @@ +* Deprecate `ActiveSupport::Multibyte::Unicode#normalize` and `ActiveSuppport::Multibyte::Chars#normalize` + in favor of `String#unicode_normalize` + + *Francesco Rodríguez* + * Deprecate `ActiveSupport::Multibyte::Unicode#downcase/upcase/swapcase` in favor of `String#downcase/upcase/swapcase`. diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index dbc8b8a2fa5..0d2a17970fc 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -62,9 +62,9 @@ module ActiveSupport raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String) I18n.transliterate( - ActiveSupport::Multibyte::Unicode.normalize( - ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c), - replacement: replacement) + ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc), + replacement: replacement + ) end # Replaces special characters in a string so that it may be used as part of diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 01a0cc131f7..3e846271b23 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -17,7 +17,7 @@ module ActiveSupport #:nodoc: # through the +mb_chars+ method. Methods which would normally return a # String object now return a Chars object so methods can be chained. # - # 'The Perfect String '.mb_chars.downcase.strip.normalize + # 'The Perfect String '.mb_chars.downcase.strip # # => # # # Chars objects are perfectly interchangeable with String objects as long as @@ -137,7 +137,24 @@ module ActiveSupport #:nodoc: # :c, :kc, :d, or :kd. Default is # ActiveSupport::Multibyte::Unicode.default_normalization_form def normalize(form = nil) - chars(Unicode.normalize(@wrapped_string, form)) + form ||= Unicode.default_normalization_form + + # See https://www.unicode.org/reports/tr15, Table 1 + if alias_form = Unicode::NORMALIZATION_FORM_ALIASES[form] + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveSupport::Multibyte::Chars#normalize is deprecated and will be + removed from Rails 6.1. Use #unicode_normalize(:#{alias_form}) instead. + MSG + + send(:unicode_normalize, alias_form) + else + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveSupport::Multibyte::Chars#normalize is deprecated and will be + removed from Rails 6.1. Use #unicode_normalize instead. + MSG + + raise ArgumentError, "#{form} is not a valid normalization variant", caller + end end # Performs canonical decomposition on all the characters. diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index a3261126d6b..43d196eeeb0 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -6,10 +6,17 @@ module ActiveSupport extend self # A list of all available normalization forms. - # See http://www.unicode.org/reports/tr15/tr15-29.html for more + # See https://www.unicode.org/reports/tr15/tr15-29.html for more # information about normalization. NORMALIZATION_FORMS = [:c, :kc, :d, :kd] + NORMALIZATION_FORM_ALIASES = { # :nodoc: + c: :nfc, + d: :nfd, + kc: :nfkc, + kd: :nfkd + } + # The Unicode version that is supported by the implementation UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"] @@ -100,17 +107,21 @@ module ActiveSupport # Default is ActiveSupport::Multibyte::Unicode.default_normalization_form. def normalize(string, form = nil) form ||= @default_normalization_form - # See http://www.unicode.org/reports/tr15, Table 1 - case form - when :d - string.unicode_normalize(:nfd) - when :c - string.unicode_normalize(:nfc) - when :kd - string.unicode_normalize(:nfkd) - when :kc - string.unicode_normalize(:nfkc) + + # See https://www.unicode.org/reports/tr15, Table 1 + if alias_form = NORMALIZATION_FORM_ALIASES[form] + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be + removed from Rails 6.1. Use String#unicode_normalize(:#{alias_form}) instead. + MSG + + string.unicode_normalize(alias_form) else + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be + removed from Rails 6.1. Use String#unicode_normalize instead. + MSG + raise ArgumentError, "#{form} is not a valid normalization variant", caller end end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 3585b1ed32d..86bca2e40b9 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -165,7 +165,9 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase assert chars("").upcase.kind_of?(ActiveSupport::Multibyte.proxy_class) assert chars("").downcase.kind_of?(ActiveSupport::Multibyte.proxy_class) assert chars("").capitalize.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars("").normalize.kind_of?(ActiveSupport::Multibyte.proxy_class) + ActiveSupport::Deprecation.silence do + assert chars("").normalize.kind_of?(ActiveSupport::Multibyte.proxy_class) + end assert chars("").decompose.kind_of?(ActiveSupport::Multibyte.proxy_class) assert chars("").compose.kind_of?(ActiveSupport::Multibyte.proxy_class) assert chars("").tidy_bytes.kind_of?(ActiveSupport::Multibyte.proxy_class) @@ -383,10 +385,12 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase def test_reverse_should_work_with_normalized_strings str = "bös" reversed_str = "söb" - assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse - assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse - assert_equal chars(reversed_str).normalize(:d), chars(str).normalize(:d).reverse - assert_equal chars(reversed_str).normalize(:kd), chars(str).normalize(:kd).reverse + ActiveSupport::Deprecation.silence do + assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse + assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse + assert_equal chars(reversed_str).normalize(:d), chars(str).normalize(:d).reverse + assert_equal chars(reversed_str).normalize(:kd), chars(str).normalize(:kd).reverse + end assert_equal chars(reversed_str).decompose, chars(str).decompose.reverse assert_equal chars(reversed_str).compose, chars(str).compose.reverse end @@ -568,7 +572,9 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase def test_composition_exclusion_is_set_up_properly # Normalization of DEVANAGARI LETTER QA breaks when composition exclusion isn't used correctly qa = [0x915, 0x93c].pack("U*") - assert_equal qa, chars(qa).normalize(:c) + ActiveSupport::Deprecation.silence do + assert_equal qa, chars(qa).normalize(:c) + end end # Test for the Public Review Issue #29, bad explanation of composition might lead to a @@ -578,17 +584,21 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase [0x0B47, 0x0300, 0x0B3E], [0x1100, 0x0300, 0x1161] ].map { |c| c.pack("U*") }.each do |c| - assert_equal_codepoints c, chars(c).normalize(:c) + ActiveSupport::Deprecation.silence do + assert_equal_codepoints c, chars(c).normalize(:c) + end end end def test_normalization_shouldnt_strip_null_bytes null_byte_str = "Test\0test" - assert_equal null_byte_str, chars(null_byte_str).normalize(:kc) - assert_equal null_byte_str, chars(null_byte_str).normalize(:c) - assert_equal null_byte_str, chars(null_byte_str).normalize(:d) - assert_equal null_byte_str, chars(null_byte_str).normalize(:kd) + ActiveSupport::Deprecation.silence do + assert_equal null_byte_str, chars(null_byte_str).normalize(:kc) + assert_equal null_byte_str, chars(null_byte_str).normalize(:c) + assert_equal null_byte_str, chars(null_byte_str).normalize(:d) + assert_equal null_byte_str, chars(null_byte_str).normalize(:kd) + end assert_equal null_byte_str, chars(null_byte_str).decompose assert_equal null_byte_str, chars(null_byte_str).compose end @@ -601,11 +611,13 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase 323 # COMBINING DOT BELOW ].pack("U*") - assert_equal_codepoints "", chars("").normalize - assert_equal_codepoints [44, 105, 106, 328, 323].pack("U*"), chars(comp_str).normalize(:kc).to_s - assert_equal_codepoints [44, 307, 328, 323].pack("U*"), chars(comp_str).normalize(:c).to_s - assert_equal_codepoints [44, 307, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:d).to_s - assert_equal_codepoints [44, 105, 106, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:kd).to_s + ActiveSupport::Deprecation.silence do + assert_equal_codepoints "", chars("").normalize + assert_equal_codepoints [44, 105, 106, 328, 323].pack("U*"), chars(comp_str).normalize(:kc).to_s + assert_equal_codepoints [44, 307, 328, 323].pack("U*"), chars(comp_str).normalize(:c).to_s + assert_equal_codepoints [44, 307, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:d).to_s + assert_equal_codepoints [44, 105, 106, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:kd).to_s + end end def test_should_compute_grapheme_length @@ -719,6 +731,35 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase assert_equal BYTE_STRING.dup.mb_chars.class, ActiveSupport::Multibyte::Chars end + def test_unicode_normalize_deprecation + # String#unicode_normalize default form is `:nfc`, and + # different than Multibyte::Unicode default, `:nkfc`. + # Deprecation should suggest the right form if no params + # are given and default is used. + assert_deprecated(/unicode_normalize\(:nfkc\)/) do + ActiveSupport::Multibyte::Unicode.normalize("") + end + + assert_deprecated(/unicode_normalize\(:nfd\)/) do + ActiveSupport::Multibyte::Unicode.normalize("", :d) + end + end + + def test_chars_normalize_deprecation + # String#unicode_normalize default form is `:nfc`, and + # different than Multibyte::Unicode default, `:nkfc`. + # Deprecation should suggest the right form if no params + # are given and default is used. + assert_deprecated(/unicode_normalize\(:nfkc\)/) do + "".mb_chars.normalize + end + + assert_deprecated(/unicode_normalize\(:nfc\)/) { "".mb_chars.normalize(:c) } + assert_deprecated(/unicode_normalize\(:nfd\)/) { "".mb_chars.normalize(:d) } + assert_deprecated(/unicode_normalize\(:nfkc\)/) { "".mb_chars.normalize(:kc) } + assert_deprecated(/unicode_normalize\(:nfkd\)/) { "".mb_chars.normalize(:kd) } + end + def test_unicode_deprecations assert_deprecated { ActiveSupport::Multibyte::Unicode.downcase("") } assert_deprecated { ActiveSupport::Multibyte::Unicode.upcase("") } diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb index a704505fc6e..b19689723fb 100644 --- a/activesupport/test/multibyte_conformance_test.rb +++ b/activesupport/test/multibyte_conformance_test.rb @@ -18,64 +18,72 @@ class MultibyteConformanceTest < ActiveSupport::TestCase end def test_normalizations_C - each_line_of_norm_tests do |*cols| - col1, col2, col3, col4, col5, comment = *cols + ActiveSupport::Deprecation.silence do + each_line_of_norm_tests do |*cols| + col1, col2, col3, col4, col5, comment = *cols - # CONFORMANCE: - # 1. The following invariants must be true for all conformant implementations - # - # NFC - # c2 == NFC(c1) == NFC(c2) == NFC(c3) - assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}" - assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}" - assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}" - # - # c4 == NFC(c4) == NFC(c5) - assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}" + # CONFORMANCE: + # 1. The following invariants must be true for all conformant implementations + # + # NFC + # c2 == NFC(c1) == NFC(c2) == NFC(c3) + assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}" + assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}" + assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}" + # + # c4 == NFC(c4) == NFC(c5) + assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}" + end end end def test_normalizations_D - each_line_of_norm_tests do |*cols| - col1, col2, col3, col4, col5, comment = *cols - # - # NFD - # c3 == NFD(c1) == NFD(c2) == NFD(c3) - assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}" - assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}" - assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}" - # c5 == NFD(c4) == NFD(c5) - assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}" + ActiveSupport::Deprecation.silence do + each_line_of_norm_tests do |*cols| + col1, col2, col3, col4, col5, comment = *cols + # + # NFD + # c3 == NFD(c1) == NFD(c2) == NFD(c3) + assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}" + assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}" + assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}" + # c5 == NFD(c4) == NFD(c5) + assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}" + end end end def test_normalizations_KC - each_line_of_norm_tests do | *cols | - col1, col2, col3, col4, col5, comment = *cols - # - # NFKC - # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5) - assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}" + ActiveSupport::Deprecation.silence do + each_line_of_norm_tests do | *cols | + col1, col2, col3, col4, col5, comment = *cols + # + # NFKC + # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5) + assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}" + end end end def test_normalizations_KD - each_line_of_norm_tests do | *cols | - col1, col2, col3, col4, col5, comment = *cols - # - # NFKD - # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5) - assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}" + ActiveSupport::Deprecation.silence do + each_line_of_norm_tests do | *cols | + col1, col2, col3, col4, col5, comment = *cols + # + # NFKD + # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5) + assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}" + end end end diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb index 3674ea44f35..82edf69294d 100644 --- a/activesupport/test/multibyte_normalization_conformance_test.rb +++ b/activesupport/test/multibyte_normalization_conformance_test.rb @@ -18,64 +18,72 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase end def test_normalizations_C - each_line_of_norm_tests do |*cols| - col1, col2, col3, col4, col5, comment = *cols + ActiveSupport::Deprecation.silence do + each_line_of_norm_tests do |*cols| + col1, col2, col3, col4, col5, comment = *cols - # CONFORMANCE: - # 1. The following invariants must be true for all conformant implementations - # - # NFC - # c2 == NFC(c1) == NFC(c2) == NFC(c3) - assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}" - assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}" - assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}" - # - # c4 == NFC(c4) == NFC(c5) - assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}" + # CONFORMANCE: + # 1. The following invariants must be true for all conformant implementations + # + # NFC + # c2 == NFC(c1) == NFC(c2) == NFC(c3) + assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}" + assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}" + assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}" + # + # c4 == NFC(c4) == NFC(c5) + assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}" + end end end def test_normalizations_D - each_line_of_norm_tests do |*cols| - col1, col2, col3, col4, col5, comment = *cols - # - # NFD - # c3 == NFD(c1) == NFD(c2) == NFD(c3) - assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}" - assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}" - assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}" - # c5 == NFD(c4) == NFD(c5) - assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}" + ActiveSupport::Deprecation.silence do + each_line_of_norm_tests do |*cols| + col1, col2, col3, col4, col5, comment = *cols + # + # NFD + # c3 == NFD(c1) == NFD(c2) == NFD(c3) + assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}" + assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}" + assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}" + # c5 == NFD(c4) == NFD(c5) + assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}" + end end end def test_normalizations_KC - each_line_of_norm_tests do | *cols | - col1, col2, col3, col4, col5, comment = *cols - # - # NFKC - # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5) - assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}" + ActiveSupport::Deprecation.silence do + each_line_of_norm_tests do | *cols | + col1, col2, col3, col4, col5, comment = *cols + # + # NFKC + # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5) + assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}" + end end end def test_normalizations_KD - each_line_of_norm_tests do | *cols | - col1, col2, col3, col4, col5, comment = *cols - # - # NFKD - # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5) - assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}" + ActiveSupport::Deprecation.silence do + each_line_of_norm_tests do | *cols | + col1, col2, col3, col4, col5, comment = *cols + # + # NFKD + # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5) + assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}" + end end end From 99c87ad2474d5c5b6e52ceac34c3cf9f9cb57f9f Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Fri, 22 Jun 2018 11:54:31 -0400 Subject: [PATCH 10/30] Improve model attribute accessor method names for backtraces Ruby uses the original method name, so will show the __temp__ method name in the backtrace. However, in the common case the method name is compatible with the `def` keyword, so we can avoid the __temp__ method name in that case to improve the name shown in backtraces or TracePoint#method_id. --- .../lib/active_model/attribute_methods.rb | 38 ++++++++++++++++ activemodel/lib/active_model/attributes.rb | 32 +++++--------- .../lib/active_record/attribute_methods.rb | 9 ---- .../active_record/attribute_methods/read.rb | 43 +++++-------------- .../active_record/attribute_methods/write.rb | 22 +++++----- 5 files changed, 69 insertions(+), 75 deletions(-) diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 1ad9071cc2a..d8352343e94 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -474,5 +474,43 @@ module ActiveModel def _read_attribute(attr) __send__(attr) end + + module AttrNames # :nodoc: + DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/ + + # We want to generate the methods via module_eval rather than + # define_method, because define_method is slower on dispatch. + # Evaluating many similar methods may use more memory as the instruction + # sequences are duplicated and cached (in MRI). define_method may + # be slower on dispatch, but if you're careful about the closure + # created, then define_method will consume much less memory. + # + # But sometimes the database might return columns with + # characters that are not allowed in normal method names (like + # 'my_column(omg)'. So to work around this we first define with + # the __temp__ identifier, and then use alias method to rename + # it to what we want. + # + # We are also defining a constant to hold the frozen string of + # the attribute name. Using a constant means that we do not have + # to allocate an object on each call to the attribute method. + # Making it frozen means that it doesn't get duped when used to + # key the @attributes in read_attribute. + def self.define_attribute_accessor_method(mod, attr_name, writer: false) + method_name = "#{attr_name}#{'=' if writer}" + if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name) + yield method_name, "'#{attr_name}'.freeze" + else + safe_name = attr_name.unpack1("h*") + const_name = "ATTR_#{safe_name}" + const_set(const_name, attr_name) unless const_defined?(const_name) + temp_method_name = "__temp__#{safe_name}#{'=' if writer}" + attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}" + yield temp_method_name, attr_name_expr + mod.send(:alias_method, method_name, temp_method_name) + mod.send(:undef_method, temp_method_name) + end + end + end end end diff --git a/activemodel/lib/active_model/attributes.rb b/activemodel/lib/active_model/attributes.rb index 41fe5168f36..c3a446098c9 100644 --- a/activemodel/lib/active_model/attributes.rb +++ b/activemodel/lib/active_model/attributes.rb @@ -29,17 +29,16 @@ module ActiveModel private def define_method_attribute=(name) - safe_name = name.unpack1("h*") - ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name - - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__#{safe_name}=(value) - name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name} - write_attribute(name, value) - end - alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= - undef_method :__temp__#{safe_name}= - STR + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + generated_attribute_methods, name, writer: true, + ) do |temp_method_name, attr_name_expr| + generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{temp_method_name}(value) + name = #{attr_name_expr} + write_attribute(name, value) + end + RUBY + end end NO_DEFAULT_PROVIDED = Object.new # :nodoc: @@ -97,15 +96,4 @@ module ActiveModel write_attribute(attribute_name, value) end end - - module AttributeMethods #:nodoc: - AttrNames = Module.new { - def self.set_name_cache(name, value) - const_name = "ATTR_#{name}" - unless const_defined? const_name - const_set const_name, -value - end - end - } - end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 221ebea8ead..3c785180e16 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -22,15 +22,6 @@ module ActiveRecord delegate :column_for_attribute, to: :class end - AttrNames = Module.new { - def self.set_name_cache(name, value) - const_name = "ATTR_#{name}" - unless const_defined? const_name - const_set const_name, -value - end - end - } - RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass) class GeneratedAttributeMethods < Module #:nodoc: diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 903fe86e041..ff0e863529c 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -8,42 +8,19 @@ module ActiveRecord module ClassMethods # :nodoc: private - # We want to generate the methods via module_eval rather than - # define_method, because define_method is slower on dispatch. - # Evaluating many similar methods may use more memory as the instruction - # sequences are duplicated and cached (in MRI). define_method may - # be slower on dispatch, but if you're careful about the closure - # created, then define_method will consume much less memory. - # - # But sometimes the database might return columns with - # characters that are not allowed in normal method names (like - # 'my_column(omg)'. So to work around this we first define with - # the __temp__ identifier, and then use alias method to rename - # it to what we want. - # - # We are also defining a constant to hold the frozen string of - # the attribute name. Using a constant means that we do not have - # to allocate an object on each call to the attribute method. - # Making it frozen means that it doesn't get duped when used to - # key the @attributes in read_attribute. def define_method_attribute(name) - safe_name = name.unpack1("h*") - temp_method = "__temp__#{safe_name}" - - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def #{temp_method} - #{sync_with_transaction_state} - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - _read_attribute(name) { |n| missing_attribute(n, caller) } - end - STR - - generated_attribute_methods.module_eval do - alias_method name, temp_method - undef_method temp_method + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + generated_attribute_methods, name + ) do |temp_method_name, attr_name_expr| + generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{temp_method_name} + #{sync_with_transaction_state} + name = #{attr_name_expr} + _read_attribute(name) { |n| missing_attribute(n, caller) } + end + RUBY end end end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 62743bc9d84..7f246eed461 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -13,19 +13,19 @@ module ActiveRecord private def define_method_attribute=(name) - safe_name = name.unpack1("h*") - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__#{safe_name}=(value) - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - #{sync_with_transaction_state} - _write_attribute(name, value) - end - alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= - undef_method :__temp__#{safe_name}= - STR + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + generated_attribute_methods, name, writer: true, + ) do |temp_method_name, attr_name_expr| + generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{temp_method_name}(value) + name = #{attr_name_expr} + #{sync_with_transaction_state} + _write_attribute(name, value) + end + RUBY + end end end From c85e3f65f3409fc329732912908c3601d8e5fac9 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Tue, 9 Oct 2018 13:36:56 -0400 Subject: [PATCH 11/30] Fix issue where duration where always rounded up to a second: - Adding a Float as a duration to a datetime would result in the Float being rounded. Doing something like would have no effect because the 0.45 seconds would be rounded to 0 second. ```ruby time = DateTime.parse("2018-1-1") time += 0.45.seconds ``` This behavior was intentionally added a very long time ago, the reason was because Ruby 1.8 was using `Integer#gcd` in the constructor of Rational which didn't accept a float value. That's no longer the case and doing `Rational(0.45, 86400)` would now perfectly work fine. - Fixes #34008 --- activesupport/CHANGELOG.md | 9 +++++++++ .../active_support/core_ext/date_time/calculations.rb | 2 +- activesupport/test/core_ext/date_time_ext_test.rb | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 2e465a0310a..2f5ed35f462 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,12 @@ +* Fix duration being rounded to a full second. + ``` + time = DateTime.parse("2018-1-1") + time += 0.51.seconds + ``` + Will now correctly add 0.51 second and not 1 full second. + + *Edouard Chin* + * Deprecate `ActiveSupport::Multibyte::Unicode#normalize` and `ActiveSuppport::Multibyte::Chars#normalize` in favor of `String#unicode_normalize` diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index e61b23f8420..bc670c3e764 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -110,7 +110,7 @@ class DateTime # instance time. Do not use this method in combination with x.months, use # months_since instead! def since(seconds) - self + Rational(seconds.round, 86400) + self + Rational(seconds, 86400) end alias :in :since diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 894fb80cbaa..f9f6b21c9b8 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -152,8 +152,8 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2005, 2, 22, 11, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).since(3600) assert_equal DateTime.civil(2005, 2, 24, 10, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).since(86400 * 2) assert_equal DateTime.civil(2005, 2, 24, 11, 10, 35), DateTime.civil(2005, 2, 22, 10, 10, 10).since(86400 * 2 + 3600 + 25) - assert_equal DateTime.civil(2005, 2, 22, 10, 10, 11), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.333) - assert_equal DateTime.civil(2005, 2, 22, 10, 10, 12), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.667) + assert_not_equal DateTime.civil(2005, 2, 22, 10, 10, 11), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.333) + assert_not_equal DateTime.civil(2005, 2, 22, 10, 10, 12), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.667) end def test_change From 4a51cbba58435bbba65ca50670bd6ae4887942bd Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Sun, 14 Oct 2018 08:33:40 +0900 Subject: [PATCH 12/30] Bump mail to `2.7.1` --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 927b36e9fca..13214368143 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -306,7 +306,7 @@ GEM loofah (2.2.2) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.7.0) + mail (2.7.1) mini_mime (>= 0.1.1) marcel (0.3.3) mimemagic (~> 0.3.2) From d2d6f6b7dbbefea84fe847e9c0dc7313cc82700c Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Sun, 14 Oct 2018 09:12:46 +0900 Subject: [PATCH 13/30] Fix tests on Mail 2.7.1 Up to `2.7.0`, encoding was chosen using `Mail::Encodings::TransferEncoding.negotiate`, and base64 encoding was used. In `2.7.1`, when `transfer_encoding` is not specified, the encoding of the message is respected. Related to: https://github.com/mikel/mail/commit/dead487e02f592d9058fd07deedcde39b569d18d However, what chosen for transfer encoding is not essential in these tests. To test more accurately, confirm that the decoded body instead. --- actionmailer/test/base_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 7a1a505398e..cbbee9fae82 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -152,9 +152,9 @@ class BaseTest < ActiveSupport::TestCase assert_equal(2, email.parts.length) assert_equal("multipart/mixed", email.mime_type) assert_equal("text/html", email.parts[0].mime_type) - assert_equal("Attachment with content", email.parts[0].body.encoded) + assert_equal("Attachment with content", email.parts[0].decoded) assert_equal("application/pdf", email.parts[1].mime_type) - assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded) + assert_equal("This is test File content", email.parts[1].decoded) end test "adds the given :body as part" do @@ -162,9 +162,9 @@ class BaseTest < ActiveSupport::TestCase assert_equal(2, email.parts.length) assert_equal("multipart/mixed", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) - assert_equal("I'm the eggman", email.parts[0].body.encoded) + assert_equal("I'm the eggman", email.parts[0].decoded) assert_equal("application/pdf", email.parts[1].mime_type) - assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded) + assert_equal("This is test File content", email.parts[1].decoded) end test "can embed an inline attachment" do From ecb2850a04adc6c6665f9a30a1d60ca73965ccfa Mon Sep 17 00:00:00 2001 From: Rasesh Patel Date: Wed, 24 May 2017 01:34:07 -0400 Subject: [PATCH 14/30] Show object ids in scaffold pages when displaying referenced objects Resolve Issue#29200 When scaffolding a model that references another model the generated show and index html pages display the object directly on the page. Basically, it just shows a memory address. That is not very helpful. In this commit we show the object's id rather than the memory address. This updates the scaffold templates and the json builder files. --- railties/CHANGELOG.md | 5 +++++ .../erb/scaffold/templates/index.html.erb.tt | 2 +- .../erb/scaffold/templates/show.html.erb.tt | 2 +- .../test/generators/scaffold_generator_test.rb | 16 +++++++++++++--- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 1a415449d1c..e581766c699 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +* Use Ids instead of memory addresses when displaying references in scaffold views + Fixes #29200. + + *Rasesh Patel* + * Adds support for multiple databases to `rails db:migrate:status`. Subtasks are also added to get the status of individual databases (eg. `rails db:migrate:status:animals`). diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt index e1ede7c713d..2cf4e5c9d00 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt @@ -16,7 +16,7 @@ <%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %> <% attributes.reject(&:password_digest?).each do |attribute| -%> - <%%= <%= singular_table_name %>.<%= attribute.name %> %> + <%%= <%= singular_table_name %>.<%= attribute.column_name %> %> <% end -%> <%%= link_to 'Show', <%= model_resource_name %> %> <%%= link_to 'Edit', edit_<%= singular_route_name %>_path(<%= singular_table_name %>) %> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt index 5e634153beb..7deba079260 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt +++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt @@ -3,7 +3,7 @@ <% attributes.reject(&:password_digest?).each do |attribute| -%>

<%= attribute.human_name %>: - <%%= @<%= singular_table_name %>.<%= attribute.name %> %> + <%%= @<%= singular_table_name %>.<%= attribute.column_name %> %>

<% end -%> diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 21b5013484e..3cffdddd383 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -435,8 +435,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end end - def test_scaffold_generator_belongs_to - run_generator ["account", "name", "currency:belongs_to"] + def test_scaffold_generator_belongs_to_and_references + run_generator ["account", "name", "currency:belongs_to", "user:references"] assert_file "app/models/account.rb", /belongs_to :currency/ @@ -449,7 +449,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "app/controllers/accounts_controller.rb" do |content| assert_instance_method :account_params, content do |m| - assert_match(/permit\(:name, :currency_id\)/, m) + assert_match(/permit\(:name, :currency_id, :user_id\)/, m) end end @@ -457,6 +457,16 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_match(/^\W{4}<%= form\.text_field :name %>/, content) assert_match(/^\W{4}<%= form\.text_field :currency_id %>/, content) end + + assert_file "app/views/accounts/index.html.erb" do |content| + assert_match(/^\W{8}<%= account\.name %><\/td>/, content) + assert_match(/^\W{8}<%= account\.user_id %><\/td>/, content) + end + + assert_file "app/views/accounts/show.html.erb" do |content| + assert_match(/^\W{2}<%= @account\.name %>/, content) + assert_match(/^\W{2}<%= @account\.user_id %>/, content) + end end def test_scaffold_generator_database From ecd46231ff97f6735b62543416a230390fa22b21 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 15 Oct 2018 11:20:05 +0900 Subject: [PATCH 15/30] Ensure to test that `project.developers` is ordered by `developers.name desc` `developers.name desc` was added at d59f3a7, but any test case isn't failed even if the `developers.name desc` is removed since all tested developers are consistently ordered on both `name` and `id`. I changed one developers creation ordering to ensure to test that `project.developers` is ordered by `developers.name desc`. --- .../associations/has_and_belongs_to_many_associations_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 38b121d37b1..ed7dde115ab 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -275,7 +275,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_habtm_saving_multiple_relationships new_project = Project.new("name" => "Grimetime") amount_of_developers = 4 - developers = (0...amount_of_developers).collect { |i| Developer.create(name: "JME #{i}") }.reverse + developers = (0...amount_of_developers).reverse_each.map { |i| Developer.create(name: "JME #{i}") } new_project.developer_ids = [developers[0].id, developers[1].id] new_project.developers_with_callback_ids = [developers[2].id, developers[3].id] From 4c15ed775304abd9e3e41afef4e7fec4077f699b Mon Sep 17 00:00:00 2001 From: Adam Demirel Date: Mon, 15 Oct 2018 15:14:48 +1100 Subject: [PATCH 16/30] Update snippet to rails 5 syntax --- guides/source/action_controller_overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 43bc9306ce8..aa746e47312 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -166,7 +166,7 @@ NOTE: Support for parsing XML parameters has been extracted into a gem named `ac The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id`, will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL: ```ruby -get '/clients/:status' => 'clients#index', foo: 'bar' +get '/clients/:status', to: 'clients#index', foo: 'bar' ``` In this case, when a user opens the URL `/clients/active`, `params[:status]` will be set to "active". When this route is used, `params[:foo]` will also be set to "bar", as if it were passed in the query string. Your controller will also receive `params[:action]` as "index" and `params[:controller]` as "clients". From 6fe45f378ab7bc88fecb99576fa367f83b3255f3 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Mon, 15 Oct 2018 15:28:15 +0300 Subject: [PATCH 17/30] Fix `ActionController::Parameters#each_value` and add changelog entry to this method (#34210) * Fix `ActionController::Parameters#each_value` `each_value` should yield with "value" of the params instead of "value" as an array. Related to #33979 * Add changelog entry about `ActionController::Parameters#each_value`. Follow up #33979 --- actionpack/CHANGELOG.md | 4 ++++ .../lib/action_controller/metal/strong_parameters.rb | 2 +- actionpack/test/controller/parameters/accessors_test.rb | 8 ++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 8205d79dd2f..3858c211ea5 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add `ActionController::Parameters#each_value`. + + *Lukáš Zapletal* + * Deprecate `ActionDispatch::Http::ParameterFilter` in favor of `ActiveSupport::ParameterFilter`. *Yoshiyuki Kinjo* diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index c1272ce667e..04922b07154 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -352,7 +352,7 @@ module ActionController # the same way as Hash#each_value. def each_value(&block) @parameters.each_pair do |key, value| - yield [convert_hashes_to_parameters(key, value)] + yield convert_hashes_to_parameters(key, value) end end diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb index 9f1fb3d0428..7789e654d52 100644 --- a/actionpack/test/controller/parameters/accessors_test.rb +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -77,11 +77,15 @@ class ParametersAccessorsTest < ActiveSupport::TestCase test "each_value carries permitted status" do @params.permit! - @params["person"].each_value { |value| assert(value.permitted?) if value == 32 } + @params.each_value do |value| + assert_predicate(value, :permitted?) + end end test "each_value carries unpermitted status" do - @params["person"].each_value { |value| assert_not(value.permitted?) if value == 32 } + @params.each_value do |value| + assert_not_predicate(value, :permitted?) + end end test "each_key converts to hash for permitted" do From 134dab46e4e94d7e6e37cec43dca8183fe72aea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Rodr=C3=ADguez?= Date: Mon, 15 Oct 2018 08:29:56 +0200 Subject: [PATCH 18/30] Deprecate ActiveSupport::Multibyte::Chars.consumes? In favor of String#is_utf8?. I think this method was made for internal use only, and its usage was removed here: https://github.com/rails/rails/pull/8261/files#diff-ce956ebe93786930e40f18db1da5fd46L39. --- activesupport/CHANGELOG.md | 4 ++++ activesupport/lib/active_support/multibyte/chars.rb | 5 +++++ activesupport/test/multibyte_chars_test.rb | 12 +++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 2f5ed35f462..88dfc64ed1a 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,7 @@ +* Deprecate `ActiveSupport::Multibyte::Chars.consumes?` in favor of `String#is_utf8?`. + + *Francesco Rodríguez* + * Fix duration being rounded to a full second. ``` time = DateTime.parse("2018-1-1") diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 3e846271b23..424ffa993c1 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -76,6 +76,11 @@ module ActiveSupport #:nodoc: # Returns +true+ when the proxy class can handle the string. Returns # +false+ otherwise. def self.consumes?(string) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveSupport::Multibyte::Chars.consumes? is deprecated and will be + removed from Rails 6.1. Use string.is_utf8? instead. + MSG + string.encoding == Encoding::UTF_8 end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 86bca2e40b9..6b33f5f9b2c 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -73,9 +73,15 @@ class MultibyteCharsTest < ActiveSupport::TestCase end def test_consumes_utf8_strings - assert @proxy_class.consumes?(UNICODE_STRING) - assert @proxy_class.consumes?(ASCII_STRING) - assert_not @proxy_class.consumes?(BYTE_STRING) + ActiveSupport::Deprecation.silence do + assert @proxy_class.consumes?(UNICODE_STRING) + assert @proxy_class.consumes?(ASCII_STRING) + assert_not @proxy_class.consumes?(BYTE_STRING) + end + end + + def test_consumes_is_deprecated + assert_deprecated { @proxy_class.consumes?(UNICODE_STRING) } end def test_concatenation_should_return_a_proxy_class_instance From f45267bc423017109e442e5c35a5765dc482b12b Mon Sep 17 00:00:00 2001 From: schneems Date: Thu, 11 Oct 2018 17:07:28 -0500 Subject: [PATCH 19/30] ActiveRecord#respond_to? No longer allocates strings This is an alternative to https://github.com/rails/rails/pull/34195 The active record `respond_to?` method needs to do two things if `super` does not say that the method exists. It has to see if the "name" being passed in represents a column in the table. If it does then it needs to pass it to `has_attribute?` to see if the key exists in the current object. The reason why this is slow is that `has_attribute?` needs a string and most (almost all) objects passed in are symbols. The only time we need to allocate a string in this method is if the column does exist in the database, and since these are a limited number of strings (since column names are a finite set) then we can pre-generate all of them and use the same string. We generate a list hash of column names and convert them to symbols, and store the value as the string name. This allows us to both check if the "name" exists as a column, but also provides us with a string object we can use for the `has_attribute?` call. I then ran the test suite and found there was only one case where we're intentionally passing in a string and changed it to a symbol. (However there are tests where we are using a string key, but they don't ship with rails). As re-written this method should never allocate unless the user passes in a string key, which is fairly uncommon with `respond_to?`. This also eliminates the need to special case every common item that might come through the method via the `case` that was originally added in https://github.com/rails/rails/commit/f80aa5994603e684e3fecd3f53bfbf242c73a107 (by me) and then with an attempt to extend in https://github.com/rails/rails/pull/34195. As a bonus this reduces 6,300 comparisons (in the CodeTriage app homepage) to 450 as we also no longer need to loop through the column array to check for an `include?`. --- .../lib/active_record/attribute_methods.rb | 15 ++++----------- activerecord/lib/active_record/model_schema.rb | 6 ++++++ .../lib/active_record/nested_attributes.rb | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 3c785180e16..eeb88e4b7a0 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -261,21 +261,14 @@ module ActiveRecord def respond_to?(name, include_private = false) return false unless super - case name - when :to_partial_path - name = "to_partial_path" - when :to_model - name = "to_model" - else - name = name.to_s - end - # If the result is true then check for the select case. # For queries selecting a subset of columns, return false for unselected columns. # We check defined?(@attributes) not to issue warnings if called on objects that # have been allocated but not yet initialized. - if defined?(@attributes) && self.class.column_names.include?(name) - return has_attribute?(name) + if defined?(@attributes) + if name = self.class.symbol_column_to_string(name.to_sym) + return has_attribute?(name) + end end true diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 5a4a1fb9696..c50a420432a 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -388,6 +388,11 @@ module ActiveRecord @column_names ||= columns.map(&:name) end + def symbol_column_to_string(name_symbol) # :nodoc: + @symbol_column_to_string_name_hash ||= column_names.index_by(&:to_sym) + @symbol_column_to_string_name_hash[name_symbol] + end + # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count", # and columns used for single table inheritance have been removed. def content_columns @@ -477,6 +482,7 @@ module ActiveRecord def reload_schema_from_cache @arel_table = nil @column_names = nil + @symbol_column_to_string_name_hash = nil @attribute_types = nil @content_columns = nil @default_attributes = nil diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 50767ee93ff..8b9098df6c0 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -426,7 +426,7 @@ module ActiveRecord existing_record.assign_attributes(assignable_attributes) association(association_name).initialize_attributes(existing_record) else - method = "build_#{association_name}" + method = :"build_#{association_name}" if respond_to?(method) send(method, assignable_attributes) else From 0925d7340e719a1d82fb9faf81233cb33405de51 Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Tue, 16 Oct 2018 00:26:18 +0300 Subject: [PATCH 20/30] Clarify docs of `ActiveJob::TestHelper` [ci skip] I found a few sentences that should be updated as well. See https://github.com/rails/rails/pull/33571#discussion_r209435886 Follow up #33571 --- activejob/lib/active_job/test_helper.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 261b68d4f82..229855c5d9d 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -197,7 +197,7 @@ module ActiveJob # assert_performed_jobs 2 # end # - # If a block is passed, that block should cause the specified number of + # If a block is passed, asserts that the block will cause the specified number of # jobs to be performed. # # def test_jobs_again @@ -279,7 +279,7 @@ module ActiveJob # end # end # - # If a block is passed, that block should not cause any job to be performed. + # If a block is passed, asserts that the block will not cause any job to be performed. # # def test_jobs_again # assert_no_performed_jobs do @@ -347,7 +347,7 @@ module ActiveJob # end # # - # If a block is passed, that block should cause the job to be + # If a block is passed, asserts that the block will cause the job to be # enqueued with the given arguments. # # def test_assert_enqueued_with From b1aeae0494c904db224b728330084ea173f38cf5 Mon Sep 17 00:00:00 2001 From: Federico Martinez Date: Thu, 7 Jun 2018 01:05:30 -0300 Subject: [PATCH 21/30] Fix Collection cache key with limit and custom select (PG:AmbigousColumn: Error) Change query to use alias name for timestamp_column to avoid ambiguity problems when using timestamp from subquery. --- activerecord/CHANGELOG.md | 6 ++++++ .../lib/active_record/collection_cache_key.rb | 4 ++-- .../test/cases/collection_cache_key_test.rb | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 00f4ee1aaa5..4e0336f46b4 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* Fix Collection cache key with limit and custom select + + Fixes #33056. + + *Federico Martinez* + * Add basic API for connection switching to support multiple databases. 1) Adds a `connects_to` method for models to connect to multiple databases. Example: diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb index 61581b04512..4b6db8a96c5 100644 --- a/activerecord/lib/active_record/collection_cache_key.rb +++ b/activerecord/lib/active_record/collection_cache_key.rb @@ -20,9 +20,9 @@ module ActiveRecord select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp" if collection.has_limit_or_offset? - query = collection.select(column) + query = collection.select("#{column} AS collection_cache_key_timestamp") subquery_alias = "subquery_for_cache_key" - subquery_column = "#{subquery_alias}.#{timestamp_column}" + subquery_column = "#{subquery_alias}.collection_cache_key_timestamp" subquery = query.arel.as(subquery_alias) arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column) else diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb index 844b2b2162d..483383257b5 100644 --- a/activerecord/test/cases/collection_cache_key_test.rb +++ b/activerecord/test/cases/collection_cache_key_test.rb @@ -42,6 +42,20 @@ module ActiveRecord assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3 end + test "cache_key for relation with custom select and limit" do + developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5) + developers_with_select = developers.select("developers.*") + last_developer_timestamp = developers.first.updated_at + + assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers_with_select.cache_key) + + /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers_with_select.cache_key + + assert_equal ActiveSupport::Digest.hexdigest(developers_with_select.to_sql), $1 + assert_equal developers.count.to_s, $2 + assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3 + end + test "cache_key for loaded relation" do developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5).load last_developer_timestamp = developers.first.updated_at From cae1b2ce8612d87687c44a282eb469599baabdc9 Mon Sep 17 00:00:00 2001 From: Carlos Donderis Date: Tue, 16 Oct 2018 11:43:38 +0900 Subject: [PATCH 22/30] Extends documentation for ActiveSupport::Cache#fetch_multi [ci skip] --- activesupport/lib/active_support/cache.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index a5d0c52b13c..68c03f294ce 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -411,8 +411,6 @@ module ActiveSupport # to the cache. If you do not want to write the cache when the cache is # not found, use #read_multi. # - # Options are passed to the underlying cache implementation. - # # Returns a hash with the data for each of the names. For example: # # cache.write("bim", "bam") @@ -422,6 +420,17 @@ module ActiveSupport # # => { "bim" => "bam", # # "unknown_key" => "Fallback value for key: unknown_key" } # + # Options are passed to the underlying cache implementation. For example: + # + # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key| + # "buzz" + # end + # # => {"fizz"=>"buzz"} + # cache.read("fizz") + # # => "buzz" + # sleep(6) + # cache.read("fizz") + # # => nil def fetch_multi(*names) raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given? From 5e92770e5a6d997e5f973cc59aebc58c4ebff80b Mon Sep 17 00:00:00 2001 From: Alberto Almagro Date: Fri, 12 Oct 2018 17:14:06 +0200 Subject: [PATCH 23/30] Add regression test against habtm memoized singular_ids Starting in Rails 5.0.0 and still present in Rails 5.2.1, `singular_ids` got memoized and didn't reload after more items were added to the relation. Although 19c8071 happens to fix the issue, it only adds tests for `has_many` relations while this bug only affected `has_and_belongs_to_many` relations. This commit adds a regression test to ensure it never happens again with `habtm` relations. Ensures #34179 never gets reproduced. --- .../has_and_belongs_to_many_associations_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index ed7dde115ab..515eb65d37c 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -25,6 +25,8 @@ require "models/user" require "models/member" require "models/membership" require "models/sponsor" +require "models/lesson" +require "models/student" require "models/country" require "models/treaty" require "models/vertex" @@ -780,6 +782,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort end + def test_singular_ids_are_reloaded_after_collection_concat + student = Student.create(name: "Alberto Almagro") + student.lesson_ids + + lesson = Lesson.create(name: "DSI") + student.lessons << lesson + + assert_includes student.lesson_ids, lesson.id + end + def test_scoped_find_on_through_association_doesnt_return_read_only_records tag = Post.find(1).tags.find_by_name("General") From e1f6821105a23fe4dc6e37903fb642a253e89e81 Mon Sep 17 00:00:00 2001 From: Brooke Kuhlmann Date: Mon, 15 Oct 2018 16:58:22 -0600 Subject: [PATCH 24/30] Refactored abstract MySQL adapter to support lazy version check. Will allow sub classes to override the protected `#check_version` method hook if desired. For example, this will be most helpful in sub classes that wish to support lazy initialization because the version check can be postponed until the connection is ready to be initialized. --- .../connection_adapters/abstract_mysql_adapter.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) 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 d40f38fb772..5ad3fdbb880 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -26,6 +26,7 @@ module ActiveRecord # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false class_attribute :emulate_booleans, default: true + SUPPORTED_VERSION = "5.5.8" NATIVE_DATABASE_TYPES = { primary_key: "bigint auto_increment PRIMARY KEY", string: { name: "varchar", limit: 255 }, @@ -54,10 +55,7 @@ module ActiveRecord super(connection, logger, config) @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) - - if version < "5.5.8" - raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8." - end + check_version end def version #:nodoc: @@ -534,6 +532,15 @@ module ActiveRecord end end + protected + + def check_version + if version < SUPPORTED_VERSION + raise "Your version of MySQL (#{version_string}) is too old. Active Record supports " \ + "MySQL >= #{SUPPORTED_VERSION}." + end + end + private def combine_multi_statements(total_sql) total_sql.each_with_object([]) do |sql, total_sql_chunks| From b69a6c3214800dafb4d467e8181dfaeb4f98ab4f Mon Sep 17 00:00:00 2001 From: Adam Demirel Date: Wed, 17 Oct 2018 07:57:56 +1100 Subject: [PATCH 25/30] Fix mapping of content --- guides/source/active_record_basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index fad4c19827c..b9e24099b11 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -82,9 +82,9 @@ of two or more words, the model class name should follow the Ruby conventions, using the CamelCase form, while the table name must contain the words separated by underscores. Examples: -* Database Table - Plural with underscores separating words (e.g., `book_clubs`). * Model Class - Singular with the first letter of each word capitalized (e.g., `BookClub`). +* Database Table - Plural with underscores separating words (e.g., `book_clubs`). | Model / Class | Table / Schema | | ---------------- | -------------- | From 5b5367364f7b17ca89b50a8738e6b0c692bb730f Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 17 Oct 2018 06:45:41 +0900 Subject: [PATCH 26/30] Consistently extract checking version for all adapters I don't prefer to extract it for one adapter even though all adapters also does. Related to #34227. --- .../connection_adapters/abstract_adapter.rb | 5 ++++ .../abstract_mysql_adapter.rb | 11 ++----- .../connection_adapters/postgresql_adapter.rb | 29 ++++++++++--------- .../connection_adapters/sqlite3_adapter.rb | 11 +++---- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index fa10f18cb7a..0fe868478c4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -125,6 +125,8 @@ module ActiveRecord @advisory_locks_enabled = self.class.type_cast_config_to_boolean( config.fetch(:advisory_locks, true) ) + + check_version end def replica? @@ -502,6 +504,9 @@ module ActiveRecord end private + def check_version + end + def type_map @type_map ||= Type::TypeMap.new.tap do |mapping| initialize_type_map(mapping) 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 5ad3fdbb880..cb5eeb64dd2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -26,7 +26,6 @@ module ActiveRecord # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false class_attribute :emulate_booleans, default: true - SUPPORTED_VERSION = "5.5.8" NATIVE_DATABASE_TYPES = { primary_key: "bigint auto_increment PRIMARY KEY", string: { name: "varchar", limit: 255 }, @@ -55,7 +54,6 @@ module ActiveRecord super(connection, logger, config) @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) - check_version end def version #:nodoc: @@ -532,16 +530,13 @@ module ActiveRecord end end - protected - + private def check_version - if version < SUPPORTED_VERSION - raise "Your version of MySQL (#{version_string}) is too old. Active Record supports " \ - "MySQL >= #{SUPPORTED_VERSION}." + if version < "5.5.8" + raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8." end end - private def combine_multi_statements(total_sql) total_sql.each_with_object([]) do |sql, total_sql_chunks| previous_packet = total_sql_chunks.last diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index bc6eb11572a..a280ca500a2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -43,9 +43,14 @@ module ActiveRecord valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl] conn_params.slice!(*valid_conn_param_keys) - # The postgres drivers don't allow the creation of an unconnected PG::Connection object, - # so just pass a nil connection object for the time being. - ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config) + conn = PG.connect(conn_params) + ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config) + rescue ::PG::Error => error + if error.message.include?("does not exist") + raise ActiveRecord::NoDatabaseError + else + raise + end end end @@ -220,15 +225,11 @@ module ActiveRecord @local_tz = nil @max_identifier_length = nil - connect + configure_connection add_pg_encoders @statements = StatementPool.new @connection, self.class.type_cast_config_to_integer(config[:statement_limit]) - if postgresql_version < 90100 - raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1." - end - add_pg_decoders @type_map = Type::HashLookupTypeMap.new @@ -410,6 +411,12 @@ module ActiveRecord end private + def check_version + if postgresql_version < 90100 + raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1." + end + end + # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html VALUE_LIMIT_VIOLATION = "22001" NUMERIC_VALUE_OUT_OF_RANGE = "22003" @@ -699,12 +706,6 @@ module ActiveRecord def connect @connection = PG.connect(@connection_parameters) configure_connection - rescue ::PG::Error => error - if error.message.include?("does not exist") - raise ActiveRecord::NoDatabaseError - else - raise - end end # Configures the encoding, verbosity, schema search path, and time zone of the connection. diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 81882f6cc12..e0355a316b1 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -105,11 +105,6 @@ module ActiveRecord @active = true @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) - - if sqlite_version < "3.8.0" - raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8." - end - configure_connection end @@ -401,6 +396,12 @@ module ActiveRecord end private + def check_version + if sqlite_version < "3.8.0" + raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8." + end + end + def initialize_type_map(m = type_map) super register_class_with_limit m, %r(int)i, SQLite3Integer From d234dd677a54d2ec787b8a2ee552b620fe6c3bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Rodr=C3=ADguez?= Date: Tue, 16 Oct 2018 22:36:30 +0200 Subject: [PATCH 27/30] Refactor Chars#reverse and Chars#grapheme_length Use \X meta character directly to get grapheme clusters. Thanks to @mtsmfm for the tip: https://github.com/rails/rails/pull/34123#issuecomment-429028878 r? @jeremy --- activesupport/lib/active_support/multibyte/chars.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 424ffa993c1..bd8cab34602 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -113,7 +113,7 @@ module ActiveSupport #:nodoc: # # 'Café'.mb_chars.reverse.to_s # => 'éfaC' def reverse - chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*")) + chars(@wrapped_string.scan(/\X/).reverse.join) end # Limits the byte size of the string to a number of bytes without breaking @@ -183,7 +183,7 @@ module ActiveSupport #:nodoc: # 'क्षि'.mb_chars.length # => 4 # 'क्षि'.mb_chars.grapheme_length # => 3 def grapheme_length - Unicode.unpack_graphemes(@wrapped_string).length + @wrapped_string.scan(/\X/).length end # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent From 8d5a2c4da12725beeb14ba70508762242c86696f Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Tue, 16 Oct 2018 20:52:34 -0400 Subject: [PATCH 28/30] Update guide for the counter variable when rendering with the `as:` option [ci skip] --- guides/source/layouts_and_rendering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 00da65b7845..ad08e5a5a99 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -1266,7 +1266,7 @@ You can also pass in arbitrary local variables to any partial you are rendering In this case, the partial will have access to a local variable `title` with the value "Products Page". -TIP: Rails also makes a counter variable available within a partial called by the collection, named after the title of the partial followed by `_counter`. For example, when rendering a collection `@products` the partial `_product.html.erb` can access the variable `product_counter` which indexes the number of times it has been rendered within the enclosing view. +TIP: Rails also makes a counter variable available within a partial called by the collection, named after the title of the partial followed by `_counter`. For example, when rendering a collection `@products` the partial `_product.html.erb` can access the variable `product_counter` which indexes the number of times it has been rendered within the enclosing view. Note that it also applies for when the partial name was changed by using the `as:` option. For example, the counter variable for the code above would be `item_counter`. You can also specify a second partial to be rendered between instances of the main partial by using the `:spacer_template` option: From 1198a3880bb2918bb771b55d94cfb333813744fa Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 26 Sep 2018 04:17:35 +0900 Subject: [PATCH 29/30] Consolidate duplicated code that initializing an empty model object `init_with` and `init_from_db` are almost the same code except decode `coder`. And also, named `init_from_db` is a little misreading, a raw values hash from the database is already converted to an attributes object by `attributes_builder.build_from_database`, so passed `attributes` in that method is just an attributes object. I renamed that method to `init_with_attributes` since the method is shared with `init_with` to initialize an empty model object. --- activerecord/lib/active_record/core.rb | 26 +++++-------------- activerecord/lib/active_record/persistence.rb | 2 +- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index b53681ee203..07186888637 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -344,34 +344,22 @@ module ActiveRecord # post = Post.allocate # post.init_with(coder) # post.title # => 'hello world' - def init_with(coder) + def init_with(coder, &block) coder = LegacyYamlAdapter.convert(self.class, coder) - @attributes = self.class.yaml_encoder.decode(coder) - - init_internals - - @new_record = coder["new_record"] - - self.class.define_attribute_methods - - yield self if block_given? - - _run_find_callbacks - _run_initialize_callbacks - - self + attributes = self.class.yaml_encoder.decode(coder) + init_with_attributes(attributes, coder["new_record"], &block) end ## - # Initializer used for instantiating objects that have been read from the - # database. +attributes+ should be an attributes object, and unlike the + # Initialize an empty model object from +attributes+. + # +attributes+ should be an attributes object, and unlike the # `initialize` method, no assignment calls are made per attribute. # # :nodoc: - def init_from_db(attributes) + def init_with_attributes(attributes, new_record = false) init_internals - @new_record = false + @new_record = new_record @attributes = attributes self.class.define_attribute_methods diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 84041196317..45ceb1e3ad3 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -209,7 +209,7 @@ module ActiveRecord # new instance of the class. Accepts only keys as strings. def instantiate_instance_of(klass, attributes, column_types = {}, &block) attributes = klass.attributes_builder.build_from_database(attributes, column_types) - klass.allocate.init_from_db(attributes, &block) + klass.allocate.init_with_attributes(attributes, &block) end # Called by +instantiate+ to decide which class to use for a new From 12c7b101f8cfa9ecb9d9483a02b0b6ebf792b77e Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 17 Oct 2018 21:28:12 +0900 Subject: [PATCH 30/30] Remove and flip `index: true` for `references` in the doc [ci skip] Follow up #32146. --- .../abstract/schema_definitions.rb | 4 ++-- .../abstract/schema_statements.rb | 12 ++++++------ guides/source/association_basics.md | 2 +- 3 files changed, 9 insertions(+), 9 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 015204c0563..70607fda5a0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -348,8 +348,8 @@ module ActiveRecord # # create_table :taggings do |t| # t.references :tag, index: { name: 'index_taggings_on_tag_id' } - # t.references :tagger, polymorphic: true, index: true - # t.references :taggable, polymorphic: { default: 'Photo' } + # t.references :tagger, polymorphic: true + # t.references :taggable, polymorphic: { default: 'Photo' }, index: false # end def column(name, type, options = {}) name = name.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 723d8c318d7..8a7020a799f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -846,17 +846,17 @@ module ActiveRecord # [:null] # Whether the column allows nulls. Defaults to true. # - # ====== Create a user_id bigint column + # ====== Create a user_id bigint column without a index # - # add_reference(:products, :user) + # add_reference(:products, :user, index: false) # # ====== Create a user_id string column # # add_reference(:products, :user, type: :string) # - # ====== Create supplier_id, supplier_type columns and appropriate index + # ====== Create supplier_id, supplier_type columns # - # add_reference(:products, :supplier, polymorphic: true, index: true) + # add_reference(:products, :supplier, polymorphic: true) # # ====== Create a supplier_id column with a unique index # @@ -884,7 +884,7 @@ module ActiveRecord # # ====== Remove the reference # - # remove_reference(:products, :user, index: true) + # remove_reference(:products, :user, index: false) # # ====== Remove polymorphic reference # @@ -892,7 +892,7 @@ module ActiveRecord # # ====== Remove the reference with a foreign key # - # remove_reference(:products, :user, index: true, foreign_key: true) + # remove_reference(:products, :user, foreign_key: true) # def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options) if foreign_key diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 406b65d2234..b0a905c7545 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -487,7 +487,7 @@ class CreatePictures < ActiveRecord::Migration[5.0] def change create_table :pictures do |t| t.string :name - t.references :imageable, polymorphic: true, index: true + t.references :imageable, polymorphic: true t.timestamps end end