From 53e4a277b38cf218bc8f55f9fa0a7d85aaabb5b9 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Tue, 16 Jan 2018 15:13:21 -0500 Subject: Convert keys to binary in the Redis cache store Fix encoding errors when using the pure-Ruby Redis driver instead of Hiredis. Dodge incompatibilities between UTF-8 and arbitrary value encodings, which rear their heads when the Redis driver tries to build a single command string from incompatibly-encoded keys and values. --- activesupport/lib/active_support/cache/redis_cache_store.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index e7f015bff5..0368423dad 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -363,7 +363,7 @@ module ActiveSupport # Truncate keys that exceed 1kB. def normalize_key(key, options) - truncate_key super + truncate_key super.b end def truncate_key(key) -- cgit v1.2.3 From 5ac6ec54a673e493cddf6bf2eff5b98e52b3c268 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Thu, 18 Jan 2018 12:09:16 +0900 Subject: Enable autocorrect for `Lint/EndAlignment` cop ### Summary This PR changes .rubocop.yml. Regarding the code using `if ... else ... end`, I think the coding style that Rails expects is as follows. ```ruby var = if cond a else b end ``` However, the current .rubocop.yml setting does not offense for the following code. ```ruby var = if cond a else b end ``` I think that the above code expects offense to be warned. Moreover, the layout by autocorrect is unnatural. ```ruby var = if cond a else b end ``` This PR adds a setting to .rubocop.yml to make an offense warning and autocorrect as expected by the coding style. And this change also fixes `case ... when ... end` together. Also this PR itself is an example that arranges the layout using `rubocop -a`. ### Other Information Autocorrect of `Lint/EndAlignment` cop is `false` by default. https://github.com/bbatsov/rubocop/blob/v0.51.0/config/default.yml#L1443 This PR changes this value to `true`. Also this PR has changed it together as it is necessary to enable `Layout/ElseAlignment` cop to make this behavior. --- activesupport/lib/active_support/core_ext/hash/conversions.rb | 4 ++-- activesupport/lib/active_support/dependencies.rb | 2 +- activesupport/lib/active_support/deprecation/reporting.rb | 2 +- activesupport/lib/active_support/inflector/inflections.rb | 2 +- activesupport/lib/active_support/inflector/methods.rb | 2 +- activesupport/lib/active_support/multibyte/unicode.rb | 2 +- activesupport/lib/active_support/values/time_zone.rb | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 11d28d12a1..5b48254646 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -165,7 +165,7 @@ module ActiveSupport Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ] when Array params.map { |v| normalize_keys(v) } - else + else params end end @@ -178,7 +178,7 @@ module ActiveSupport process_array(value) when String value - else + else raise "can't typecast #{value.class.name} - #{value.inspect}" end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 82c10b3079..abc648e0c6 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -670,7 +670,7 @@ module ActiveSupport #:nodoc: when Module desc.name || raise(ArgumentError, "Anonymous modules have no name to be referenced by") - else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" + else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" end end diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index 242e21b782..2c004f4c9e 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -61,7 +61,7 @@ module ActiveSupport case message when Symbol then "#{warning} (use #{message} instead)" when String then "#{warning} (#{message})" - else warning + else warning end end diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 0450a4be4c..7e5dff1d6d 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -227,7 +227,7 @@ module ActiveSupport case scope when :all @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, [] - else + else instance_variable_set "@#{scope}", [] end end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 60eeaa77cb..7e782e2a93 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -350,7 +350,7 @@ module ActiveSupport when 1; "st" when 2; "nd" when 3; "rd" - else "th" + else "th" end end end diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index a64223c0e0..f923061fae 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -276,7 +276,7 @@ module ActiveSupport reorder_characters(decompose(:compatibility, codepoints)) when :kc compose(reorder_characters(decompose(:compatibility, codepoints))) - else + else raise ArgumentError, "#{form} is not a valid normalization variant", caller end.pack("U*".freeze) end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 4d81ac939e..b75f5733b5 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -238,7 +238,7 @@ module ActiveSupport when Numeric, ActiveSupport::Duration arg *= 3600 if arg.abs <= 13 all.find { |z| z.utc_offset == arg.to_i } - else + else raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" end end -- cgit v1.2.3 From 4ac143d1937e8d6f33162863a17c1b1db57687c1 Mon Sep 17 00:00:00 2001 From: Gabriel Sobrinho Date: Tue, 12 Dec 2017 22:11:47 -0200 Subject: Support for connection pooling on mem cache store --- .../lib/active_support/cache/mem_cache_store.rb | 34 ++++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 00a3670d64..cae0d44e7d 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -63,7 +63,23 @@ module ActiveSupport addresses = addresses.flatten options = addresses.extract_options! addresses = ["localhost:11211"] if addresses.empty? - Dalli::Client.new(addresses, options) + + pool_options = {} + pool_options[:size] = options[:pool_size] if options[:pool_size] + pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout] + + if pool_options.empty? + Dalli::Client.new(addresses, options) + else + begin + require "connection_pool" + rescue LoadError => e + $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + + ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) } + end end # Creates a new MemCacheStore object, with the given memcached server @@ -99,7 +115,7 @@ module ActiveSupport options = merged_options(options) instrument(:increment, name, amount: amount) do rescue_error_with nil do - @data.incr(normalize_key(name, options), amount, options[:expires_in]) + @data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) } end end end @@ -112,7 +128,7 @@ module ActiveSupport options = merged_options(options) instrument(:decrement, name, amount: amount) do rescue_error_with nil do - @data.decr(normalize_key(name, options), amount, options[:expires_in]) + @data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) } end end end @@ -120,18 +136,18 @@ module ActiveSupport # Clear the entire cache on all memcached servers. This method should # be used with care when shared cache is being used. def clear(options = nil) - rescue_error_with(nil) { @data.flush_all } + rescue_error_with(nil) { @data.with { |c| c.flush_all } } end # Get the statistics from the memcached servers. def stats - @data.stats + @data.with { |c| c.stats } end private # Read an entry from the cache. def read_entry(key, options) - rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) } + rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) } end # Write an entry to the cache. @@ -144,7 +160,7 @@ module ActiveSupport expires_in += 5.minutes end rescue_error_with false do - @data.send(method, key, value, expires_in, options) + @data.with { |c| c.send(method, key, value, expires_in, options) } end end @@ -152,7 +168,7 @@ module ActiveSupport def read_multi_entries(names, options) keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }] - raw_values = @data.get_multi(keys_to_names.keys) + raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) } values = {} raw_values.each do |key, value| @@ -168,7 +184,7 @@ module ActiveSupport # Delete an entry from the cache. def delete_entry(key, options) - rescue_error_with(false) { @data.delete(key) } + rescue_error_with(false) { @data.with { |c| c.delete(key) } } end # Memcache keys are binaries. So we need to force their encoding to binary -- cgit v1.2.3 From e0f0d717d63b7896186f40c4d389977ddba72a97 Mon Sep 17 00:00:00 2001 From: Julien Meichelbeck Date: Thu, 18 Jan 2018 21:20:34 +0100 Subject: Support hash as first argument in `assert_difference`. (#31600) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support hash as first argument for `assert_difference`. This allows to specify multiple numeric differences in the same assertion. Example: assert_difference 'Article.count' => 1, 'Notification.count' => 2 do # post :create, params: { article: {...} } end * Support error message when passing a hash as a first parameter * Format CHANGELOG properly [Julien Meichelbeck + Rafael Mendonça França] --- .../lib/active_support/testing/assertions.rb | 28 ++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 6f69c48674..a891ff616d 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -58,6 +58,12 @@ module ActiveSupport # post :create, params: { article: {...} } # end # + # A hash of expressions/numeric differences can also be passed in and evaluated. + # + # assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do + # post :create, params: { article: {...} } + # end + # # A lambda or a list of lambdas can be passed in and evaluated: # # assert_difference ->{ Article.count }, 2 do @@ -73,20 +79,28 @@ module ActiveSupport # assert_difference 'Article.count', -1, 'An Article should be destroyed' do # post :delete, params: { id: ... } # end - def assert_difference(expression, difference = 1, message = nil, &block) - expressions = Array(expression) - - exps = expressions.map { |e| + def assert_difference(expression, *args, &block) + expressions = + if expression.is_a?(Hash) + message = args[0] + expression + else + difference = args[0] || 1 + message = args[1] + Hash[Array(expression).map { |e| [e, difference] }] + end + + exps = expressions.keys.map { |e| e.respond_to?(:call) ? e : lambda { eval(e, block.binding) } } before = exps.map(&:call) retval = yield - expressions.zip(exps).each_with_index do |(code, e), i| - error = "#{code.inspect} didn't change by #{difference}" + expressions.zip(exps, before) do |(code, diff), exp, before_value| + error = "#{code.inspect} didn't change by #{diff}" error = "#{message}.\n#{error}" if message - assert_equal(before[i] + difference, e.call, error) + assert_equal(before_value + diff, exp.call, error) end retval -- cgit v1.2.3 From 4efbbc844b3cd5f76e0e24ae1a1f90bb57da3b18 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Thu, 14 Dec 2017 17:05:13 +0200 Subject: Add support for connection pooling on RedisCacheStore --- activesupport/lib/active_support/cache.rb | 17 ++++++ .../lib/active_support/cache/mem_cache_store.rb | 13 +---- .../lib/active_support/cache/redis_cache_store.rb | 64 ++++++++++++++++++---- 3 files changed, 71 insertions(+), 23 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 2d038dba77..d221b36365 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -160,6 +160,23 @@ module ActiveSupport attr_reader :silence, :options alias :silence? :silence + class << self + private + def retrieve_pool_options(options) + {}.tap do |pool_options| + pool_options[:size] = options[:pool_size] if options[:pool_size] + pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout] + end + end + + def ensure_connection_pool_added! + require "connection_pool" + rescue LoadError => e + $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + end + # Creates a new cache. The options will be passed to any write method calls # except for :namespace which can be used to set the global # namespace for the cache. diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index cae0d44e7d..2840781dde 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -63,21 +63,12 @@ module ActiveSupport addresses = addresses.flatten options = addresses.extract_options! addresses = ["localhost:11211"] if addresses.empty? - - pool_options = {} - pool_options[:size] = options[:pool_size] if options[:pool_size] - pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout] + pool_options = retrieve_pool_options(options) if pool_options.empty? Dalli::Client.new(addresses, options) else - begin - require "connection_pool" - rescue LoadError => e - $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" - raise e - end - + ensure_connection_pool_added! ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) } end end diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index 0368423dad..3347576651 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -20,6 +20,31 @@ require "active_support/core_ext/marshal" module ActiveSupport module Cache + module ConnectionPoolLike + def with + yield self + end + end + + ::Redis.include(ConnectionPoolLike) + + class RedisDistributedWithConnectionPool < ::Redis::Distributed + def add_node(options) + pool_options = {} + pool_options[:size] = options[:pool_size] if options[:pool_size] + pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout] + + if pool_options.empty? + super + else + options = { url: options } if options.is_a?(String) + options = @default_options.merge(options) + pool = ConnectionPool.new(pool_options) { ::Redis.new(options) } + @ring.add_node(pool) + end + end + end + # Redis cache store. # # Deployment note: Take care to use a *dedicated Redis cache* rather @@ -122,7 +147,7 @@ module ActiveSupport private def build_redis_distributed_client(urls:, **redis_options) - ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist| + RedisDistributedWithConnectionPool.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist| urls.each { |u| dist.add_node url: u } end end @@ -172,7 +197,7 @@ module ActiveSupport end def redis - @redis ||= self.class.build_redis(**redis_options) + @redis ||= wrap_in_connection_pool(self.class.build_redis(**redis_options)) end def inspect @@ -211,7 +236,7 @@ module ActiveSupport instrument :delete_matched, matcher do case matcher when String - redis.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)] + redis.with { |c| c.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)] } else raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}" end @@ -228,7 +253,7 @@ module ActiveSupport # Failsafe: Raises errors. def increment(name, amount = 1, options = nil) instrument :increment, name, amount: amount do - redis.incrby normalize_key(name, options), amount + redis.with { |c| c.incrby normalize_key(name, options), amount } end end @@ -242,7 +267,7 @@ module ActiveSupport # Failsafe: Raises errors. def decrement(name, amount = 1, options = nil) instrument :decrement, name, amount: amount do - redis.decrby normalize_key(name, options), amount + redis.with { |c| c.decrby normalize_key(name, options), amount } end end @@ -263,7 +288,7 @@ module ActiveSupport if namespace = merged_options(options)[namespace] delete_matched "*", namespace: namespace else - redis.flushdb + redis.with { |c| c.flushdb } end end end @@ -279,6 +304,21 @@ module ActiveSupport end private + def wrap_in_connection_pool(redis_connection) + if redis_connection.is_a?(::Redis) + pool_options = self.class.send(:retrieve_pool_options, redis_options) + + if pool_options.empty? + redis_connection + else + self.class.send(:ensure_connection_pool_added!) + ConnectionPool.new(pool_options) { redis_connection } + end + else + redis_connection + end + end + def set_redis_capabilities case redis when Redis::Distributed @@ -294,7 +334,7 @@ module ActiveSupport # Read an entry from the cache. def read_entry(key, options = nil) failsafe :read_entry do - deserialize_entry redis.get(key) + deserialize_entry redis.with { |c| c.get(key) } end end @@ -303,7 +343,7 @@ module ActiveSupport options = merged_options(options) keys = names.map { |name| normalize_key(name, options) } - values = redis.mget(*keys) + values = redis.with { |c| c.mget(*keys) } names.zip(values).each_with_object({}) do |(name, value), results| if value @@ -334,9 +374,9 @@ module ActiveSupport modifiers[:nx] = unless_exist modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in - redis.set key, value, modifiers + redis.with { |c| c.set key, value, modifiers } else - redis.set key, value + redis.with { |c| c.set key, value } end end end @@ -344,7 +384,7 @@ module ActiveSupport # Delete an entry from the cache. def delete_entry(key, options) failsafe :delete_entry, returning: false do - redis.del key + redis.with { |c| c.del key } end end @@ -353,7 +393,7 @@ module ActiveSupport if entries.any? if mset_capable? && expires_in.nil? failsafe :write_multi_entries do - redis.mapped_mset(entries) + redis.with { |c| c.mapped_mset(entries) } end else super -- cgit v1.2.3 From bd54991d20aa30a76815ce80912c8122a2e4ffd3 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Sat, 16 Dec 2017 22:33:22 +0200 Subject: Improve fault tolerance for redis cache store --- .../lib/active_support/cache/redis_cache_store.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index 3347576651..c4cd9c4761 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -253,7 +253,9 @@ module ActiveSupport # Failsafe: Raises errors. def increment(name, amount = 1, options = nil) instrument :increment, name, amount: amount do - redis.with { |c| c.incrby normalize_key(name, options), amount } + failsafe :increment do + redis.with { |c| c.incrby normalize_key(name, options), amount } + end end end @@ -267,7 +269,9 @@ module ActiveSupport # Failsafe: Raises errors. def decrement(name, amount = 1, options = nil) instrument :decrement, name, amount: amount do - redis.with { |c| c.decrby normalize_key(name, options), amount } + failsafe :decrement do + redis.with { |c| c.decrby normalize_key(name, options), amount } + end end end @@ -343,7 +347,10 @@ module ActiveSupport options = merged_options(options) keys = names.map { |name| normalize_key(name, options) } - values = redis.with { |c| c.mget(*keys) } + + values = failsafe(:read_multi_mget, returning: {}) do + redis.with { |c| c.mget(*keys) } + end names.zip(values).each_with_object({}) do |(name, value), results| if value @@ -368,7 +375,7 @@ module ActiveSupport expires_in += 5.minutes end - failsafe :write_entry do + failsafe :write_entry, returning: false do if unless_exist || expires_in modifiers = {} modifiers[:nx] = unless_exist -- cgit v1.2.3 From d92fb27885ddcb0a92ac67f69bf0eb8c912f4dc7 Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Fri, 26 Jan 2018 09:41:27 -0500 Subject: Remove duplicates after autoloading modules --- activesupport/lib/active_support/dependencies.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index abc648e0c6..0f59558bb5 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -447,6 +447,7 @@ module ActiveSupport #:nodoc: mod = Module.new into.const_set const_name, mod autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path) + autoloaded_constants.uniq! mod end -- cgit v1.2.3 From 1c383df324fdf0b68b3f54a649eb7d2a4f55bcb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 30 Jan 2018 18:51:17 -0500 Subject: Start Rails 6.0 development!!! :tada::tada::tada: --- activesupport/lib/active_support/gem_version.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index 1e09adbb52..c951ad16a3 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -7,10 +7,10 @@ module ActiveSupport end module VERSION - MAJOR = 5 - MINOR = 2 + MAJOR = 6 + MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end -- cgit v1.2.3 From 3ba52d6a2b6708c408f9e8a4eee1d73709b067d1 Mon Sep 17 00:00:00 2001 From: Yuji Yaginuma Date: Wed, 31 Jan 2018 14:27:45 +0900 Subject: Update default deprecation horizon Because the master branch is already 6.0. --- activesupport/lib/active_support/deprecation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index a1ad2ca465..7271ab565b 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -35,7 +35,7 @@ module ActiveSupport # and the second is a library name. # # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') - def initialize(deprecation_horizon = "6.0", gem_name = "Rails") + def initialize(deprecation_horizon = "6.1", gem_name = "Rails") self.gem_name = gem_name self.deprecation_horizon = deprecation_horizon # By default, warnings are not silenced and debugging is off. -- cgit v1.2.3 From 148d0077c539ebca99a8cf776902805da3431f95 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Wed, 31 Jan 2018 09:49:32 -0500 Subject: Revert "Merge pull request #31447 from fatkodima/redis_cache-connection_pool" This reverts commit ac74e2c521f6ddc0eac02d74a1313261bcc1d60f, reversing changes made to ffdb06136152b3c5f7f4a93ca5928e16e755d228. --- activesupport/lib/active_support/cache.rb | 17 ------ .../lib/active_support/cache/mem_cache_store.rb | 13 ++++- .../lib/active_support/cache/redis_cache_store.rb | 64 ++++------------------ 3 files changed, 23 insertions(+), 71 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index d221b36365..2d038dba77 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -160,23 +160,6 @@ module ActiveSupport attr_reader :silence, :options alias :silence? :silence - class << self - private - def retrieve_pool_options(options) - {}.tap do |pool_options| - pool_options[:size] = options[:pool_size] if options[:pool_size] - pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout] - end - end - - def ensure_connection_pool_added! - require "connection_pool" - rescue LoadError => e - $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" - raise e - end - end - # Creates a new cache. The options will be passed to any write method calls # except for :namespace which can be used to set the global # namespace for the cache. diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 2840781dde..cae0d44e7d 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -63,12 +63,21 @@ module ActiveSupport addresses = addresses.flatten options = addresses.extract_options! addresses = ["localhost:11211"] if addresses.empty? - pool_options = retrieve_pool_options(options) + + pool_options = {} + pool_options[:size] = options[:pool_size] if options[:pool_size] + pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout] if pool_options.empty? Dalli::Client.new(addresses, options) else - ensure_connection_pool_added! + begin + require "connection_pool" + rescue LoadError => e + $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) } end end diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index c4cd9c4761..1de98dcd6c 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -20,31 +20,6 @@ require "active_support/core_ext/marshal" module ActiveSupport module Cache - module ConnectionPoolLike - def with - yield self - end - end - - ::Redis.include(ConnectionPoolLike) - - class RedisDistributedWithConnectionPool < ::Redis::Distributed - def add_node(options) - pool_options = {} - pool_options[:size] = options[:pool_size] if options[:pool_size] - pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout] - - if pool_options.empty? - super - else - options = { url: options } if options.is_a?(String) - options = @default_options.merge(options) - pool = ConnectionPool.new(pool_options) { ::Redis.new(options) } - @ring.add_node(pool) - end - end - end - # Redis cache store. # # Deployment note: Take care to use a *dedicated Redis cache* rather @@ -147,7 +122,7 @@ module ActiveSupport private def build_redis_distributed_client(urls:, **redis_options) - RedisDistributedWithConnectionPool.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist| + ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist| urls.each { |u| dist.add_node url: u } end end @@ -197,7 +172,7 @@ module ActiveSupport end def redis - @redis ||= wrap_in_connection_pool(self.class.build_redis(**redis_options)) + @redis ||= self.class.build_redis(**redis_options) end def inspect @@ -236,7 +211,7 @@ module ActiveSupport instrument :delete_matched, matcher do case matcher when String - redis.with { |c| c.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)] } + redis.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)] else raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}" end @@ -254,7 +229,7 @@ module ActiveSupport def increment(name, amount = 1, options = nil) instrument :increment, name, amount: amount do failsafe :increment do - redis.with { |c| c.incrby normalize_key(name, options), amount } + redis.incrby normalize_key(name, options), amount end end end @@ -270,7 +245,7 @@ module ActiveSupport def decrement(name, amount = 1, options = nil) instrument :decrement, name, amount: amount do failsafe :decrement do - redis.with { |c| c.decrby normalize_key(name, options), amount } + redis.decrby normalize_key(name, options), amount end end end @@ -292,7 +267,7 @@ module ActiveSupport if namespace = merged_options(options)[namespace] delete_matched "*", namespace: namespace else - redis.with { |c| c.flushdb } + redis.flushdb end end end @@ -308,21 +283,6 @@ module ActiveSupport end private - def wrap_in_connection_pool(redis_connection) - if redis_connection.is_a?(::Redis) - pool_options = self.class.send(:retrieve_pool_options, redis_options) - - if pool_options.empty? - redis_connection - else - self.class.send(:ensure_connection_pool_added!) - ConnectionPool.new(pool_options) { redis_connection } - end - else - redis_connection - end - end - def set_redis_capabilities case redis when Redis::Distributed @@ -338,7 +298,7 @@ module ActiveSupport # Read an entry from the cache. def read_entry(key, options = nil) failsafe :read_entry do - deserialize_entry redis.with { |c| c.get(key) } + deserialize_entry redis.get(key) end end @@ -349,7 +309,7 @@ module ActiveSupport keys = names.map { |name| normalize_key(name, options) } values = failsafe(:read_multi_mget, returning: {}) do - redis.with { |c| c.mget(*keys) } + redis.mget(*keys) end names.zip(values).each_with_object({}) do |(name, value), results| @@ -381,9 +341,9 @@ module ActiveSupport modifiers[:nx] = unless_exist modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in - redis.with { |c| c.set key, value, modifiers } + redis.set key, value, modifiers else - redis.with { |c| c.set key, value } + redis.set key, value end end end @@ -391,7 +351,7 @@ module ActiveSupport # Delete an entry from the cache. def delete_entry(key, options) failsafe :delete_entry, returning: false do - redis.with { |c| c.del key } + redis.del key end end @@ -400,7 +360,7 @@ module ActiveSupport if entries.any? if mset_capable? && expires_in.nil? failsafe :write_multi_entries do - redis.with { |c| c.mapped_mset(entries) } + redis.mapped_mset(entries) end else super -- cgit v1.2.3 From dc407392cd6527a9902e91efabedbdced43594e1 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Thu, 14 Dec 2017 17:05:13 +0200 Subject: Add support for connection pooling on RedisCacheStore --- activesupport/lib/active_support/cache.rb | 17 +++++++++ .../lib/active_support/cache/mem_cache_store.rb | 13 ++----- .../lib/active_support/cache/redis_cache_store.rb | 40 ++++++++++++++++------ 3 files changed, 48 insertions(+), 22 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 2d038dba77..1ea2d0bbf2 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -160,6 +160,23 @@ module ActiveSupport attr_reader :silence, :options alias :silence? :silence + class << self + private + def retrieve_pool_options(options) + {}.tap do |pool_options| + pool_options[:size] = options.delete(:pool_size) if options[:pool_size] + pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout] + end + end + + def ensure_connection_pool_added! + require "connection_pool" + rescue LoadError => e + $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + end + # Creates a new cache. The options will be passed to any write method calls # except for :namespace which can be used to set the global # namespace for the cache. diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index cae0d44e7d..2840781dde 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -63,21 +63,12 @@ module ActiveSupport addresses = addresses.flatten options = addresses.extract_options! addresses = ["localhost:11211"] if addresses.empty? - - pool_options = {} - pool_options[:size] = options[:pool_size] if options[:pool_size] - pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout] + pool_options = retrieve_pool_options(options) if pool_options.empty? Dalli::Client.new(addresses, options) else - begin - require "connection_pool" - rescue LoadError => e - $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" - raise e - end - + ensure_connection_pool_added! ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) } end end diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index 1de98dcd6c..b8227359a5 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -20,6 +20,15 @@ require "active_support/core_ext/marshal" module ActiveSupport module Cache + module ConnectionPoolLike + def with + yield self + end + end + + ::Redis.include(ConnectionPoolLike) + ::Redis::Distributed.include(ConnectionPoolLike) + # Redis cache store. # # Deployment note: Take care to use a *dedicated Redis cache* rather @@ -172,7 +181,16 @@ module ActiveSupport end def redis - @redis ||= self.class.build_redis(**redis_options) + @redis ||= begin + pool_options = self.class.send(:retrieve_pool_options, redis_options) + + if pool_options.any? + self.class.send(:ensure_connection_pool_added!) + ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) } + else + self.class.build_redis(**redis_options) + end + end end def inspect @@ -211,7 +229,7 @@ module ActiveSupport instrument :delete_matched, matcher do case matcher when String - redis.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)] + redis.with { |c| c.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)] } else raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}" end @@ -229,7 +247,7 @@ module ActiveSupport def increment(name, amount = 1, options = nil) instrument :increment, name, amount: amount do failsafe :increment do - redis.incrby normalize_key(name, options), amount + redis.with { |c| c.incrby normalize_key(name, options), amount } end end end @@ -245,7 +263,7 @@ module ActiveSupport def decrement(name, amount = 1, options = nil) instrument :decrement, name, amount: amount do failsafe :decrement do - redis.decrby normalize_key(name, options), amount + redis.with { |c| c.decrby normalize_key(name, options), amount } end end end @@ -267,7 +285,7 @@ module ActiveSupport if namespace = merged_options(options)[namespace] delete_matched "*", namespace: namespace else - redis.flushdb + redis.with { |c| c.flushdb } end end end @@ -298,7 +316,7 @@ module ActiveSupport # Read an entry from the cache. def read_entry(key, options = nil) failsafe :read_entry do - deserialize_entry redis.get(key) + deserialize_entry redis.with { |c| c.get(key) } end end @@ -309,7 +327,7 @@ module ActiveSupport keys = names.map { |name| normalize_key(name, options) } values = failsafe(:read_multi_mget, returning: {}) do - redis.mget(*keys) + redis.with { |c| c.mget(*keys) } end names.zip(values).each_with_object({}) do |(name, value), results| @@ -341,9 +359,9 @@ module ActiveSupport modifiers[:nx] = unless_exist modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in - redis.set key, value, modifiers + redis.with { |c| c.set key, value, modifiers } else - redis.set key, value + redis.with { |c| c.set key, value } end end end @@ -351,7 +369,7 @@ module ActiveSupport # Delete an entry from the cache. def delete_entry(key, options) failsafe :delete_entry, returning: false do - redis.del key + redis.with { |c| c.del key } end end @@ -360,7 +378,7 @@ module ActiveSupport if entries.any? if mset_capable? && expires_in.nil? failsafe :write_multi_entries do - redis.mapped_mset(entries) + redis.with { |c| c.mapped_mset(entries) } end else super -- cgit v1.2.3 From 08aa8afd24e65525757c6cb9ef2af60b5984c6ac Mon Sep 17 00:00:00 2001 From: fatkodima Date: Mon, 5 Feb 2018 00:35:17 +0200 Subject: RedisCacheStore: fix `#write_multi` mset serialization Closes #31886 Fixes #31884 --- activesupport/lib/active_support/cache/redis_cache_store.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index 1de98dcd6c..b5a2a2a636 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -17,6 +17,7 @@ end require "digest/sha2" require "active_support/core_ext/marshal" +require "active_support/core_ext/hash/transform_values" module ActiveSupport module Cache @@ -360,7 +361,13 @@ module ActiveSupport if entries.any? if mset_capable? && expires_in.nil? failsafe :write_multi_entries do - redis.mapped_mset(entries) + serialized_entries = if options[:raw] + entries.transform_values { |e| e.value.to_s } + else + entries.transform_values { |e| serialize_entry(e) } + end + + redis.mapped_mset(serialized_entries) end else super -- cgit v1.2.3 From f9a8646d8150956524a72af9f85c832bbe84061b Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sun, 4 Feb 2018 22:39:15 -0800 Subject: Redis cache store: consolidate serialization Use `serialize_entry` throughout and introduce `serialize_entries`. --- .../lib/active_support/cache/redis_cache_store.rb | 38 ++++++++++++---------- 1 file changed, 21 insertions(+), 17 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index b5a2a2a636..596541087f 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -70,7 +70,7 @@ module ActiveSupport def write_entry(key, entry, options) if options[:raw] && local_cache - raw_entry = Entry.new(entry.value.to_s) + raw_entry = Entry.new(serialize_entry(entry, raw: true)) raw_entry.expires_at = entry.expires_at super(key, raw_entry, options) else @@ -81,7 +81,7 @@ module ActiveSupport def write_multi_entries(entries, options) if options[:raw] && local_cache raw_entries = entries.map do |key, entry| - raw_entry = Entry.new(entry.value.to_s) + raw_entry = Entry.new(serialize_entry(entry, raw: true)) raw_entry.expires_at = entry.expires_at end.to_h @@ -327,7 +327,7 @@ module ActiveSupport # # Requires Redis 2.6.12+ for extended SET options. def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options) - value = raw ? entry.value.to_s : serialize_entry(entry) + serialized_entry = serialize_entry(entry, raw: raw) # If race condition TTL is in use, ensure that cache entries # stick around a bit longer after they would have expired @@ -342,9 +342,9 @@ module ActiveSupport modifiers[:nx] = unless_exist modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in - redis.set key, value, modifiers + redis.set key, serialized_entry, modifiers else - redis.set key, value + redis.set key, serialized_entry end end end @@ -361,13 +361,7 @@ module ActiveSupport if entries.any? if mset_capable? && expires_in.nil? failsafe :write_multi_entries do - serialized_entries = if options[:raw] - entries.transform_values { |e| e.value.to_s } - else - entries.transform_values { |e| serialize_entry(e) } - end - - redis.mapped_mset(serialized_entries) + redis.mapped_mset(serialize_entries(entries, raw: options[:raw])) end else super @@ -390,15 +384,25 @@ module ActiveSupport end end - def deserialize_entry(raw_value) - if raw_value - entry = Marshal.load(raw_value) rescue raw_value + def deserialize_entry(serialized_entry) + if serialized_entry + entry = Marshal.load(serialized_entry) rescue serialized_entry entry.is_a?(Entry) ? entry : Entry.new(entry) end end - def serialize_entry(entry) - Marshal.dump(entry) + def serialize_entry(entry, raw: false) + if raw + entry.value.to_s + else + Marshal.dump(entry) + end + end + + def serialize_entries(entries, raw: false) + entries.transform_values do |entry| + serialize_entry entry, raw: raw + end end def failsafe(method, returning: nil) -- cgit v1.2.3 From 3b5d940fe5868af0f31989054e8fb7d2d9d11ec9 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Mon, 5 Feb 2018 18:46:55 +0200 Subject: Add missing instrumentation to RedisCacheStore#read_multi --- activesupport/lib/active_support/cache/redis_cache_store.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index 596541087f..c21ade8670 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -187,7 +187,11 @@ module ActiveSupport # fetched values. def read_multi(*names) if mget_capable? - read_multi_mget(*names) + instrument(:read_multi, names, options) do |payload| + read_multi_mget(*names).tap do |results| + payload[:hits] = results.keys + end + end else super end -- cgit v1.2.3 From 83c1ed9a1a11196cab66d9c44a56a902ca0710e4 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Mon, 5 Feb 2018 17:22:18 +0200 Subject: Use Redis#mget for RedisCacheStore#fetch_multi --- activesupport/lib/active_support/cache/redis_cache_store.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index c21ade8670..af14c28408 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -307,6 +307,14 @@ module ActiveSupport end end + def read_multi_entries(names, _options) + if mget_capable? + read_multi_mget(*names) + else + super + end + end + def read_multi_mget(*names) options = names.extract_options! options = merged_options(options) -- cgit v1.2.3 From a467c6bdd5722c09f5a15c0a7c5c40cc99c8be25 Mon Sep 17 00:00:00 2001 From: Jonathan del Strother Date: Wed, 7 Feb 2018 13:06:11 +0000 Subject: Fix yaml deserialization of ActiveSupport::Duration This ensures the duration's @parts hash has a default value, to avoid this regression introduced in 5.1: YAML.load(YAML.dump(10.minutes)) + 1 # => NoMethodError: undefined method `+' for nil:NilClass --- activesupport/lib/active_support/duration.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 39deb2313f..19b2d84fc8 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -334,6 +334,14 @@ module ActiveSupport to_i end + def init_with(coder) #:nodoc: + initialize(coder["value"], coder["parts"]) + end + + def encode_with(coder) #:nodoc: + coder.map = { "value" => @value, "parts" => @parts } + end + # Build ISO 8601 Duration string for this duration. # The +precision+ parameter can be used to limit seconds' precision of duration. def iso8601(precision: nil) -- cgit v1.2.3 From c01c38efbda53b66a4a7d677af523ac8ac493321 Mon Sep 17 00:00:00 2001 From: Joseph Page Date: Mon, 12 Feb 2018 15:39:00 +0100 Subject: Doc: fixes typo `cache:` -> `compress:` [ci skip] Closes #31967 --- activesupport/lib/active_support/cache/redis_cache_store.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index af14c28408..4df0509665 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -150,7 +150,7 @@ module ActiveSupport # # Compression is enabled by default with a 1kB threshold, so cached # values larger than 1kB are automatically compressed. Disable by - # passing cache: false or change the threshold by passing + # passing compress: false or change the threshold by passing # compress_threshold: 4.kilobytes. # # No expiry is set on cache entries by default. Redis is expected to -- cgit v1.2.3 From 0b9bf2e2df2f3e17cad25f4f5e5184fbd0191129 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 15 Feb 2018 18:14:57 +1100 Subject: PERF: symbolize ivar, to reduce dupes I noticed this in my memory profiler report. ``` 153 "@default_url_options" 152 /home/sam/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/activesupport-5.1.4/lib/active_support/core_ext/class/attribute.rb:84 ``` 152 copies of the string `@default_url_options` are retained on the heap in Discourse post boot. Since this is just used for ivar lookups there is no need to use a string. --- activesupport/lib/active_support/core_ext/class/attribute.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 7928efb871..fa33ff945f 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -98,7 +98,7 @@ class Class singleton_class.silence_redefinition_of_method("#{name}?") define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate - ivar = "@#{name}" + ivar = "@#{name}".to_sym singleton_class.silence_redefinition_of_method("#{name}=") define_singleton_method("#{name}=") do |val| -- cgit v1.2.3 From 26821d9b572815a39c8ecb2e19375b2abff68730 Mon Sep 17 00:00:00 2001 From: eileencodes Date: Wed, 20 Dec 2017 16:59:41 -0500 Subject: Add test parallelization to Rails Provides both a forked process and threaded parallelization options. To use add `parallelize` to your test suite. Takes a `workers` argument that controls how many times the process is forked. For each process a new database will be created suffixed with the worker number; test-database-0 and test-database-1 respectively. If `ENV["PARALLEL_WORKERS"]` is set the workers argument will be ignored and the environment variable will be used instead. This is useful for CI environments, or other environments where you may need more workers than you do for local testing. If the number of workers is set to `1` or fewer, the tests will not be parallelized. The default parallelization method is to fork processes. If you'd like to use threads instead you can pass `with: :threads` to the `parallelize` method. Note the threaded parallelization does not create multiple database and will not work with system tests at this time. parallelize(workers: 2, with: :threads) The threaded parallelization uses Minitest's parallel exector directly. The processes paralleliztion uses a Ruby Drb server. For parallelization via threads a setup hook and cleanup hook are provided. ``` class ActiveSupport::TestCase parallelize_setup do |worker| # setup databases end parallelize_teardown do |worker| # cleanup database end parallelize(workers: 2) end ``` [Eileen M. Uchitelle, Aaron Patterson] --- activesupport/lib/active_support/test_case.rb | 86 +++++++++++++++++ .../lib/active_support/testing/parallelization.rb | 102 +++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 activesupport/lib/active_support/testing/parallelization.rb (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index d1f7e6ea09..3588a5ac02 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -11,6 +11,7 @@ require "active_support/testing/isolation" require "active_support/testing/constant_lookup" require "active_support/testing/time_helpers" require "active_support/testing/file_fixtures" +require "active_support/testing/parallelization" module ActiveSupport class TestCase < ::Minitest::Test @@ -39,6 +40,91 @@ module ActiveSupport def test_order ActiveSupport.test_order ||= :random end + + # Parallelizes the test suite. + # + # Takes a `workers` argument that controls how many times the process + # is forked. For each process a new database will be created suffixed + # with the worker number. + # + # test-database-0 + # test-database-1 + # + # If `ENV["PARALLEL_WORKERS"]` is set the workers argument will be ignored + # and the environment variable will be used instead. This is useful for CI + # environments, or other environments where you may need more workers than + # you do for local testing. + # + # If the number of workers is set to `1` or fewer, the tests will not be + # parallelized. + # + # The default parallelization method is to fork processes. If you'd like to + # use threads instead you can pass `with: :threads` to the `parallelize` + # method. Note the threaded parallelization does not create multiple + # database and will not work with system tests at this time. + # + # parallelize(workers: 2, with: :threads) + # + # The threaded parallelization uses Minitest's parallel exector directly. + # The processes paralleliztion uses a Ruby Drb server. + def parallelize(workers: 2, with: :processes) + workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"] + + return if workers <= 1 + + executor = case with + when :processes + Testing::Parallelization.new(workers) + when :threads + Minitest::Parallel::Executor.new(workers) + else + raise ArgumentError, "#{with} is not a supported parallelization exectutor." + end + + self.lock_threads = false if defined?(self.lock_threads) && with == :threads + + Minitest.parallel_executor = executor + + parallelize_me! + end + + # Set up hook for parallel testing. This can be used if you have multiple + # databases or any behavior that needs to be run after the process is forked + # but before the tests run. + # + # Note: this feature is not available with the threaded parallelization. + # + # In your +test_helper.rb+ add the following: + # + # class ActiveSupport::TestCase + # parallelize_setup do + # # create databases + # end + # end + def parallelize_setup(&block) + ActiveSupport::Testing::Parallelization.after_fork_hook do |worker| + yield worker + end + end + + # Clean up hook for parallel testing. This can be used to drop databases + # if your app uses multiple write/read databases or other clean up before + # the tests finish. This runs before the forked process is closed. + # + # Note: this feature is not available with the threaded parallelization. + # + # In your +test_helper.rb+ add the following: + # + # class ActiveSupport::TestCase + # parallelize_teardown do + # # drop databases + # end + # end + def parallelize_teardown(&block) + ActiveSupport::Testing::Parallelization.run_cleanup_hook do |worker| + yield worker + end + end end alias_method :method_name, :name diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb new file mode 100644 index 0000000000..59c8486f41 --- /dev/null +++ b/activesupport/lib/active_support/testing/parallelization.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require "drb" +require "drb/unix" + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + class Server + include DRb::DRbUndumped + + def initialize + @queue = Queue.new + end + + def record(reporter, result) + reporter.synchronize do + reporter.record(result) + end + end + + def <<(o) + @queue << o + end + + def pop; @queue.pop; end + end + + @after_fork_hooks = [] + + def self.after_fork_hook(&blk) + @after_fork_hooks << blk + end + + def self.after_fork_hooks + @after_fork_hooks + end + + @run_cleanup_hooks = [] + + def self.run_cleanup_hook(&blk) + @run_cleanup_hooks << blk + end + + def self.run_cleanup_hooks + @run_cleanup_hooks + end + + def initialize(queue_size) + @queue_size = queue_size + @queue = Server.new + @pool = [] + + @url = DRb.start_service("drbunix:", @queue).uri + end + + def after_fork(worker) + self.class.after_fork_hooks.each do |cb| + cb.call(worker) + end + end + + def run_cleanup(worker) + self.class.run_cleanup_hooks.each do |cb| + cb.call(worker) + end + end + + def start + @pool = @queue_size.times.map do |worker| + fork do + DRb.stop_service + + after_fork(worker) + + queue = DRbObject.new_with_uri(@url) + + while job = queue.pop + klass = job[0] + method = job[1] + reporter = job[2] + result = Minitest.run_one_method(klass, method) + + queue.record(reporter, result) + end + + run_cleanup(worker) + end + end + end + + def <<(work) + @queue << work + end + + def shutdown + @queue_size.times { @queue << nil } + @pool.each { |pid| Process.waitpid pid } + end + end + end +end -- cgit v1.2.3 From 89bcca59e91fa9da941de890012872e8288e77b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 16 Feb 2018 19:28:30 -0500 Subject: Remove usage of strip_heredoc in the framework in favor of <<~ Some places we can't remove because Ruby still don't have a method equivalent to strip_heredoc to be called in an already existent string. --- activesupport/lib/active_support/testing/time_helpers.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 998a51a34c..801ea2909b 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/module/redefine_method" -require "active_support/core_ext/string/strip" # for strip_heredoc require "active_support/core_ext/time/calculations" require "concurrent/map" @@ -112,7 +111,7 @@ module ActiveSupport # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 def travel_to(date_or_time) if block_given? && simple_stubs.stubbing(Time, :now) - travel_to_nested_block_call = <<-MSG.strip_heredoc + travel_to_nested_block_call = <<~MSG Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing. -- cgit v1.2.3 From 971e22f43e16118db6ac3087c5ec75a740dbddca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 16 Feb 2018 21:57:14 -0500 Subject: Define transform_keys! in HashWithIndifferentAccess Make sure that when transforming the keys of a HashWithIndifferentAccess we can still access with indifferent access in Ruby 2.5. Closes #32007. --- activesupport/lib/active_support/hash_with_indifferent_access.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 2e2ed8a25d..eda7ec3940 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -311,6 +311,14 @@ module ActiveSupport dup.tap { |hash| hash.transform_keys!(*args, &block) } end + def transform_keys! + return enum_for(:transform_keys!) { size } unless block_given? + keys.each do |key| + self[yield(key)] = delete(key) + end + self + end + def slice(*keys) keys.map! { |key| convert_key(key) } self.class.new(super) -- cgit v1.2.3 From 1e526788e6b1d3f42f4d8fdca20e588d42838c80 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Fri, 16 Feb 2018 17:14:27 -0800 Subject: Rails 6 requires Ruby 2.3+ --- .../core_ext/module/redefine_method.rb | 25 +++++++--------------- activesupport/lib/active_support/subscriber.rb | 23 ++++++++------------ 2 files changed, 17 insertions(+), 31 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/core_ext/module/redefine_method.rb b/activesupport/lib/active_support/core_ext/module/redefine_method.rb index a0a6622ca4..5bd8e6e973 100644 --- a/activesupport/lib/active_support/core_ext/module/redefine_method.rb +++ b/activesupport/lib/active_support/core_ext/module/redefine_method.rb @@ -1,23 +1,14 @@ # frozen_string_literal: true class Module - if RUBY_VERSION >= "2.3" - # Marks the named method as intended to be redefined, if it exists. - # Suppresses the Ruby method redefinition warning. Prefer - # #redefine_method where possible. - def silence_redefinition_of_method(method) - if method_defined?(method) || private_method_defined?(method) - # This suppresses the "method redefined" warning; the self-alias - # looks odd, but means we don't need to generate a unique name - alias_method method, method - end - end - else - def silence_redefinition_of_method(method) - if method_defined?(method) || private_method_defined?(method) - alias_method :__rails_redefine, method - remove_method :__rails_redefine - end + # Marks the named method as intended to be redefined, if it exists. + # Suppresses the Ruby method redefinition warning. Prefer + # #redefine_method where possible. + def silence_redefinition_of_method(method) + if method_defined?(method) || private_method_defined?(method) + # This suppresses the "method redefined" warning; the self-alias + # looks odd, but means we don't need to generate a unique name + alias_method method, method end end diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index d6dd5474d0..8ad39f7a05 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -54,25 +54,20 @@ module ActiveSupport @@subscribers ||= [] end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :subscriber, :notifier, :namespace - private + attr_reader :subscriber, :notifier, :namespace - def add_event_subscriber(event) # :doc: - return if %w{ start finish }.include?(event.to_s) + def add_event_subscriber(event) # :doc: + return if %w{ start finish }.include?(event.to_s) - pattern = "#{event}.#{namespace}" + pattern = "#{event}.#{namespace}" - # Don't add multiple subscribers (eg. if methods are redefined). - return if subscriber.patterns.include?(pattern) + # Don't add multiple subscribers (eg. if methods are redefined). + return if subscriber.patterns.include?(pattern) - subscriber.patterns << pattern - notifier.subscribe(pattern, subscriber) - end + subscriber.patterns << pattern + notifier.subscribe(pattern, subscriber) + end end attr_reader :patterns # :nodoc: -- cgit v1.2.3 From 2bfef0d21f8e8ba666a0bccc82a722f339923c1d Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Sat, 17 Feb 2018 18:16:15 +0200 Subject: Remove extra conditions in HWIDA since Rails 6 does not support Ruby 2.2 See https://github.com/ruby/ruby/blob/ruby_2_3/NEWS --- .../active_support/hash_with_indifferent_access.rb | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index eda7ec3940..e4afc8af93 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -177,20 +177,18 @@ module ActiveSupport super(convert_key(key), *extras) end - if Hash.new.respond_to?(:dig) - # Same as Hash#dig where the key passed as argument can be - # either a string or a symbol: - # - # counters = ActiveSupport::HashWithIndifferentAccess.new - # counters[:foo] = { bar: 1 } - # - # counters.dig('foo', 'bar') # => 1 - # counters.dig(:foo, :bar) # => 1 - # counters.dig(:zoo) # => nil - def dig(*args) - args[0] = convert_key(args[0]) if args.size > 0 - super(*args) - end + # Same as Hash#dig where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = { bar: 1 } + # + # counters.dig('foo', 'bar') # => 1 + # counters.dig(:foo, :bar) # => 1 + # counters.dig(:zoo) # => nil + def dig(*args) + args[0] = convert_key(args[0]) if args.size > 0 + super(*args) end # Same as Hash#default where the key passed as argument can be @@ -228,7 +226,7 @@ module ActiveSupport # hash.fetch_values('a', 'c') # => KeyError: key not found: "c" def fetch_values(*indices, &block) indices.collect { |key| fetch(key, &block) } - end if Hash.method_defined?(:fetch_values) + end # Returns a shallow copy of the hash. # -- cgit v1.2.3 From 33886e28f1c06b7f252c1db0e433794b00c54a2f Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Sat, 17 Feb 2018 16:00:57 -0700 Subject: Remove require Object#blank? monkey patch Object#blank? used to be used in this file, but it's not anymore. This avoids a monkey-patch, for those who want to use just this isolated feature of ActiveSupport. --- activesupport/lib/active_support/values/time_zone.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index b75f5733b5..1de4748a46 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -2,7 +2,6 @@ require "tzinfo" require "concurrent/map" -require "active_support/core_ext/object/blank" module ActiveSupport # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. -- cgit v1.2.3 From d4eb0dc89ee6b476e2e10869dc282a96f956c6c7 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 17 Feb 2018 13:02:18 -0800 Subject: Rails 6 requires Ruby 2.4.1+ Skipping over 2.4.0 to sidestep the `"symbol_from_string".to_sym.dup` bug. References #32028 --- .../lib/active_support/cache/redis_cache_store.rb | 1 - activesupport/lib/active_support/core_ext/hash.rb | 1 - .../core_ext/hash/transform_values.rb | 31 ++-------------------- .../active_support/core_ext/numeric/conversions.rb | 8 +----- .../active_support/core_ext/object/duplicable.rb | 7 +++-- .../lib/active_support/core_ext/regexp.rb | 4 --- .../active_support/core_ext/string/multibyte.rb | 7 ++--- .../active_support/duration/iso8601_serializer.rb | 1 - activesupport/lib/active_support/rails.rb | 6 ----- activesupport/lib/active_support/xml_mini.rb | 4 --- 10 files changed, 12 insertions(+), 58 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index c5f8c876ba..64bc77cf22 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -17,7 +17,6 @@ end require "digest/sha2" require "active_support/core_ext/marshal" -require "active_support/core_ext/hash/transform_values" module ActiveSupport module Cache diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index e19aeaa983..325581a60a 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -8,4 +8,3 @@ require "active_support/core_ext/hash/indifferent_access" require "active_support/core_ext/hash/keys" require "active_support/core_ext/hash/reverse_merge" require "active_support/core_ext/hash/slice" -require "active_support/core_ext/hash/transform_values" diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb index 4b19c9fc1f..fc15130c9e 100644 --- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb +++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb @@ -1,32 +1,5 @@ # frozen_string_literal: true -class Hash - # Returns a new hash with the results of running +block+ once for every value. - # The keys are unchanged. - # - # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 } # => { a: 2, b: 4, c: 6 } - # - # If you do not provide a +block+, it will return an Enumerator - # for chaining with other methods: - # - # { a: 1, b: 2 }.transform_values.with_index { |v, i| [v, i].join.to_i } # => { a: 10, b: 21 } - def transform_values - return enum_for(:transform_values) { size } unless block_given? - return {} if empty? - result = self.class.new - each do |key, value| - result[key] = yield(value) - end - result - end unless method_defined? :transform_values +require "active_support/deprecation" - # Destructively converts all values using the +block+ operations. - # Same as +transform_values+ but modifies +self+. - def transform_values! - return enum_for(:transform_values!) { size } unless block_given? - each do |key, value| - self[key] = yield(value) - end - end unless method_defined? :transform_values! - # TODO: Remove this file when supporting only Ruby 2.4+. -end +ActiveSupport::Deprecation.warn "Ruby 2.4+ (required by Rails 6) provides Hash#transform_values natively, so requiring active_support/core_ext/hash/transform_values is no longer necessary. Requiring it will raise LoadError in Rails 6.1." diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index f6c2713986..7fcd0d0311 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -129,12 +129,6 @@ module ActiveSupport::NumericWithFormat end end -# Ruby 2.4+ unifies Fixnum & Bignum into Integer. -if 0.class == Integer - Integer.prepend ActiveSupport::NumericWithFormat -else - Fixnum.prepend ActiveSupport::NumericWithFormat - Bignum.prepend ActiveSupport::NumericWithFormat -end +Integer.prepend ActiveSupport::NumericWithFormat Float.prepend ActiveSupport::NumericWithFormat BigDecimal.prepend ActiveSupport::NumericWithFormat diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 9bb99087bc..c78ee6bbfc 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -75,8 +75,11 @@ end class Symbol begin - :symbol.dup # Ruby 2.4.x. - "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0. + :symbol.dup + + # Some symbols couldn't be duped in Ruby 2.4.0 only, due to a bug. + # This feature check catches any regression. + "symbol_from_string".to_sym.dup rescue TypeError # Symbols are not duplicable: diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb index efbd708aee..d92943c7ae 100644 --- a/activesupport/lib/active_support/core_ext/regexp.rb +++ b/activesupport/lib/active_support/core_ext/regexp.rb @@ -4,8 +4,4 @@ class Regexp #:nodoc: def multiline? options & MULTILINE == MULTILINE end - - def match?(string, pos = 0) - !!match(string, pos) - end unless //.respond_to?(:match?) end diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index 07c0d16398..6cceb46507 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -11,12 +11,13 @@ class String # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string. # - # >> "lj".upcase - # => "lj" # >> "lj".mb_chars.upcase.to_s # => "LJ" # - # NOTE: An above example is useful for pre Ruby 2.4. Ruby 2.4 supports Unicode case mappings. + # NOTE: Ruby 2.4 and later support native Unicode case mappings: + # + # >> "lj".upcase + # => "LJ" # # == Method chaining # diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb index bb177ae5b7..84ae29c1ec 100644 --- a/activesupport/lib/active_support/duration/iso8601_serializer.rb +++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/object/blank" -require "active_support/core_ext/hash/transform_values" module ActiveSupport class Duration diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb index 5c34a0abb3..8b727a69ec 100644 --- a/activesupport/lib/active_support/rails.rb +++ b/activesupport/lib/active_support/rails.rb @@ -27,9 +27,3 @@ require "active_support/core_ext/module/delegation" # Defines ActiveSupport::Deprecation. require "active_support/deprecation" - -# Defines Regexp#match?. -# -# This should be removed when Rails needs Ruby 2.4 or later, and the require -# added where other Regexp extensions are being used (easy to grep). -require "active_support/core_ext/regexp" diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index ee2048cb54..d0659aeaae 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -48,10 +48,6 @@ module ActiveSupport "Array" => "array", "Hash" => "hash" } - - # No need to map these on Ruby 2.4+ - TYPE_NAMES["Fixnum"] = "integer" unless 0.class == Integer - TYPE_NAMES["Bignum"] = "integer" unless 0.class == Integer end FORMATTING = { -- cgit v1.2.3 From a4e226fb2d4ae533b6ac668c38b6923b5eb5db0a Mon Sep 17 00:00:00 2001 From: Dharam Gollapudi Date: Sat, 17 Feb 2018 16:01:20 -0800 Subject: Fixes typos Fixes typos --- activesupport/lib/active_support/test_case.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 3588a5ac02..a698b4e61e 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -65,8 +65,8 @@ module ActiveSupport # # parallelize(workers: 2, with: :threads) # - # The threaded parallelization uses Minitest's parallel exector directly. - # The processes paralleliztion uses a Ruby Drb server. + # The threaded parallelization uses Minitest's parallel executor directly. + # The processes parallelization uses a Ruby Drb server. def parallelize(workers: 2, with: :processes) workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"] @@ -78,7 +78,7 @@ module ActiveSupport when :threads Minitest::Parallel::Executor.new(workers) else - raise ArgumentError, "#{with} is not a supported parallelization exectutor." + raise ArgumentError, "#{with} is not a supported parallelization executor." end self.lock_threads = false if defined?(self.lock_threads) && with == :threads -- cgit v1.2.3 From 8454aeeb2b8e7d1255acbaaec4e0ae3c97d55f49 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 17 Feb 2018 15:23:17 -0800 Subject: `String#strip_heredoc` preserves frozenness ```ruby "foo".freeze.strip_heredoc.frozen? # => true ``` Fixes the case where frozen string literals would inadvertently become unfrozen: ```ruby foo = <<-MSG.strip_heredoc la la la MSG foo.frozen? # => false !?? ``` --- activesupport/lib/active_support/core_ext/string/strip.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb index cc26274e4a..6f9834bb16 100644 --- a/activesupport/lib/active_support/core_ext/string/strip.rb +++ b/activesupport/lib/active_support/core_ext/string/strip.rb @@ -20,6 +20,8 @@ class String # Technically, it looks for the least indented non-empty line # in the whole string, and removes that amount of leading whitespace. def strip_heredoc - gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze) + gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze).tap do |stripped| + stripped.freeze if frozen? + end end end -- cgit v1.2.3 From 4940cc49ddb361d584d51bc3eb4675ff8ece4a2b Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Fri, 9 Dec 2016 11:34:35 -0700 Subject: String#truncate_bytes: limit to N bytes without breaking multibyte chars This faithfully preserves grapheme clusters (characters composed of other characters and combining marks) and other multibyte characters. --- .../lib/active_support/core_ext/string/filters.rb | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index 66e721eea3..df0e79afa8 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -78,6 +78,47 @@ class String "#{self[0, stop]}#{omission}" end + # Truncates +text+ to at most bytesize bytes in length without + # breaking string encoding by splitting multibyte characters or breaking + # grapheme clusters ("perceptual characters") by truncating at combining + # characters. + # + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size + # => 20 + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize + # => 80 + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20) + # => "🔪🔪🔪🔪…" + # + # The truncated text ends with the :omission string, defaulting + # to "…", for a total length not exceeding bytesize. + def truncate_bytes(truncate_at, omission: "…") + omission ||= "" + + case + when bytesize <= truncate_at + dup + when omission.bytesize > truncate_at + raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes" + when omission.bytesize == truncate_at + omission.dup + else + self.class.new.tap do |cut| + cut_at = truncate_at - omission.bytesize + + scan(/\X/) do |grapheme| + if cut.bytesize + grapheme.bytesize <= cut_at + cut << grapheme + else + break + end + end + + cut << omission + end + end + end + # Truncates a given +text+ after a given number of words (words_count): # # 'Once upon a time in a world far far away'.truncate_words(4) -- cgit v1.2.3 From 2d95956e8b564236f0c5f98ea18d8535b05344f0 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Mon, 19 Feb 2018 10:24:13 +0000 Subject: Return all mappings for a timezone id in `country_zones` Some timezones like `Europe/London` have multiple mappings in `ActiveSupport::TimeZone::MAPPING` so return all of them instead of the first one found by using `Hash#value`. e.g: # Before ActiveSupport::TimeZone.country_zones("GB") # => ["Edinburgh"] # After ActiveSupport::TimeZone.country_zones("GB") # => ["Edinburgh", "London"] Fixes #31668. --- activesupport/lib/active_support/values/time_zone.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 1de4748a46..9dfaddb825 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -267,11 +267,14 @@ module ActiveSupport country = TZInfo::Country.get(code) country.zone_identifiers.map do |tz_id| if MAPPING.value?(tz_id) - self[MAPPING.key(tz_id)] + MAPPING.inject([]) do |memo, (key, value)| + memo << self[key] if value == tz_id + memo + end else create(tz_id, nil, TZInfo::Timezone.new(tz_id)) end - end.sort! + end.flatten(1).sort! end def zones_map -- cgit v1.2.3 From c1d00d6dbf2837be2ac690d1462f2577e93d617a Mon Sep 17 00:00:00 2001 From: Fumiaki MATSUSHIMA Date: Sun, 9 Oct 2016 17:40:07 +0900 Subject: Remove `AS::Multibyte`'s unicode table --- .../lib/active_support/multibyte/unicode.rb | 287 ++------------------- .../lib/active_support/values/unicode_tables.dat | Bin 1116857 -> 0 bytes 2 files changed, 15 insertions(+), 272 deletions(-) delete mode 100644 activesupport/lib/active_support/values/unicode_tables.dat (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index f923061fae..4f0e1165ef 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -11,7 +11,7 @@ module ActiveSupport NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation - UNICODE_VERSION = "9.0.0" + UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"] # The default normalization used for operations that require # normalization. It can be set to any of the normalizations @@ -21,96 +21,13 @@ module ActiveSupport attr_accessor :default_normalization_form @default_normalization_form = :kc - # Hangul character boundaries and properties - HANGUL_SBASE = 0xAC00 - HANGUL_LBASE = 0x1100 - HANGUL_VBASE = 0x1161 - HANGUL_TBASE = 0x11A7 - HANGUL_LCOUNT = 19 - HANGUL_VCOUNT = 21 - HANGUL_TCOUNT = 28 - HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT - HANGUL_SCOUNT = 11172 - HANGUL_SLAST = HANGUL_SBASE + HANGUL_SCOUNT - - # Detect whether the codepoint is in a certain character class. Returns - # +true+ when it's in the specified character class and +false+ otherwise. - # Valid character classes are: :cr, :lf, :l, - # :v, :lv, :lvt and :t. - # - # Primarily used by the grapheme cluster support. - def in_char_class?(codepoint, classes) - classes.detect { |c| database.boundary[c] === codepoint } ? true : false - end - # Unpack the string at grapheme boundaries. Returns a list of character # lists. # # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]] # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]] def unpack_graphemes(string) - codepoints = string.codepoints.to_a - unpacked = [] - pos = 0 - marker = 0 - eoc = codepoints.length - while (pos < eoc) - pos += 1 - previous = codepoints[pos - 1] - current = codepoints[pos] - - # See http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules - should_break = - if pos == eoc - true - # GB3. CR X LF - elsif previous == database.boundary[:cr] && current == database.boundary[:lf] - false - # GB4. (Control|CR|LF) ÷ - elsif previous && in_char_class?(previous, [:control, :cr, :lf]) - true - # GB5. ÷ (Control|CR|LF) - elsif in_char_class?(current, [:control, :cr, :lf]) - true - # GB6. L X (L|V|LV|LVT) - elsif database.boundary[:l] === previous && in_char_class?(current, [:l, :v, :lv, :lvt]) - false - # GB7. (LV|V) X (V|T) - elsif in_char_class?(previous, [:lv, :v]) && in_char_class?(current, [:v, :t]) - false - # GB8. (LVT|T) X (T) - elsif in_char_class?(previous, [:lvt, :t]) && database.boundary[:t] === current - false - # GB9. X (Extend | ZWJ) - elsif in_char_class?(current, [:extend, :zwj]) - false - # GB9a. X SpacingMark - elsif database.boundary[:spacingmark] === current - false - # GB9b. Prepend X - elsif database.boundary[:prepend] === previous - false - # GB10. (E_Base | EBG) Extend* X E_Modifier - elsif (marker...pos).any? { |i| in_char_class?(codepoints[i], [:e_base, :e_base_gaz]) && codepoints[i + 1...pos].all? { |c| database.boundary[:extend] === c } } && database.boundary[:e_modifier] === current - false - # GB11. ZWJ X (Glue_After_Zwj | EBG) - elsif database.boundary[:zwj] === previous && in_char_class?(current, [:glue_after_zwj, :e_base_gaz]) - false - # GB12. ^ (RI RI)* RI X RI - # GB13. [^RI] (RI RI)* RI X RI - elsif codepoints[marker..pos].all? { |c| database.boundary[:regional_indicator] === c } && codepoints[marker..pos].count { |c| database.boundary[:regional_indicator] === c }.even? - false - # GB999. Any ÷ Any - else - true - end - - if should_break - unpacked << codepoints[marker..pos - 1] - marker = pos - end - end - unpacked + string.scan(/\X/).map(&:codepoints) end # Reverse operation of unpack_graphemes. @@ -120,100 +37,18 @@ module ActiveSupport unpacked.flatten.pack("U*") end - # Re-order codepoints so the string becomes canonical. - def reorder_characters(codepoints) - length = codepoints.length - 1 - pos = 0 - while pos < length do - cp1, cp2 = database.codepoints[codepoints[pos]], database.codepoints[codepoints[pos + 1]] - if (cp1.combining_class > cp2.combining_class) && (cp2.combining_class > 0) - codepoints[pos..pos + 1] = cp2.code, cp1.code - pos += (pos > 0 ? -1 : 1) - else - pos += 1 - end - end - codepoints - end - # Decompose composed characters to the decomposed form. def decompose(type, codepoints) - codepoints.inject([]) do |decomposed, cp| - # if it's a hangul syllable starter character - if HANGUL_SBASE <= cp && cp < HANGUL_SLAST - sindex = cp - HANGUL_SBASE - ncp = [] # new codepoints - ncp << HANGUL_LBASE + sindex / HANGUL_NCOUNT - ncp << HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT - tindex = sindex % HANGUL_TCOUNT - ncp << (HANGUL_TBASE + tindex) unless tindex == 0 - decomposed.concat ncp - # if the codepoint is decomposable in with the current decomposition type - elsif (ncp = database.codepoints[cp].decomp_mapping) && (!database.codepoints[cp].decomp_type || type == :compatibility) - decomposed.concat decompose(type, ncp.dup) - else - decomposed << cp - end + if type == :compatibility + codepoints.pack("U*").unicode_normalize(:nfkd).codepoints + else + codepoints.pack("U*").unicode_normalize(:nfd).codepoints end end # Compose decomposed characters to the composed form. def compose(codepoints) - pos = 0 - eoa = codepoints.length - 1 - starter_pos = 0 - starter_char = codepoints[0] - previous_combining_class = -1 - while pos < eoa - pos += 1 - lindex = starter_char - HANGUL_LBASE - # -- Hangul - if 0 <= lindex && lindex < HANGUL_LCOUNT - vindex = codepoints[starter_pos + 1] - HANGUL_VBASE rescue vindex = -1 - if 0 <= vindex && vindex < HANGUL_VCOUNT - tindex = codepoints[starter_pos + 2] - HANGUL_TBASE rescue tindex = -1 - if 0 <= tindex && tindex < HANGUL_TCOUNT - j = starter_pos + 2 - eoa -= 2 - else - tindex = 0 - j = starter_pos + 1 - eoa -= 1 - end - codepoints[starter_pos..j] = (lindex * HANGUL_VCOUNT + vindex) * HANGUL_TCOUNT + tindex + HANGUL_SBASE - end - starter_pos += 1 - starter_char = codepoints[starter_pos] - # -- Other characters - else - current_char = codepoints[pos] - current = database.codepoints[current_char] - if current.combining_class > previous_combining_class - if ref = database.composition_map[starter_char] - composition = ref[current_char] - else - composition = nil - end - unless composition.nil? - codepoints[starter_pos] = composition - starter_char = composition - codepoints.delete_at pos - eoa -= 1 - pos -= 1 - previous_combining_class = -1 - else - previous_combining_class = current.combining_class - end - else - previous_combining_class = current.combining_class - end - if current.combining_class == 0 - starter_pos = pos - starter_char = codepoints[pos] - end - end - end - codepoints + codepoints.pack("U*").unicode_normalize(:nfc).codepoints end # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars. @@ -266,129 +101,37 @@ module ActiveSupport def normalize(string, form = nil) form ||= @default_normalization_form # See http://www.unicode.org/reports/tr15, Table 1 - codepoints = string.codepoints.to_a case form when :d - reorder_characters(decompose(:canonical, codepoints)) + string.unicode_normalize(:nfd) when :c - compose(reorder_characters(decompose(:canonical, codepoints))) + string.unicode_normalize(:nfc) when :kd - reorder_characters(decompose(:compatibility, codepoints)) + string.unicode_normalize(:nfkd) when :kc - compose(reorder_characters(decompose(:compatibility, codepoints))) + string.unicode_normalize(:nfkc) else raise ArgumentError, "#{form} is not a valid normalization variant", caller - end.pack("U*".freeze) + end end def downcase(string) - apply_mapping string, :lowercase_mapping + string.downcase end def upcase(string) - apply_mapping string, :uppercase_mapping + string.upcase end def swapcase(string) - apply_mapping string, :swapcase_mapping - end - - # Holds data about a codepoint in the Unicode database. - class Codepoint - attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping - - # Initializing Codepoint object with default values - def initialize - @combining_class = 0 - @uppercase_mapping = 0 - @lowercase_mapping = 0 - end - - def swapcase_mapping - uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping - end - end - - # Holds static data from the Unicode database. - class UnicodeDatabase - ATTRIBUTES = :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252 - - attr_writer(*ATTRIBUTES) - - def initialize - @codepoints = Hash.new(Codepoint.new) - @composition_exclusion = [] - @composition_map = {} - @boundary = {} - @cp1252 = {} - end - - # Lazy load the Unicode database so it's only loaded when it's actually used - ATTRIBUTES.each do |attr_name| - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{attr_name} # def codepoints - load # load - @#{attr_name} # @codepoints - end # end - EOS - end - - # Loads the Unicode database and returns all the internal objects of - # UnicodeDatabase. - def load - begin - @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, "rb") { |f| Marshal.load f.read } - rescue => e - raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable") - end - - # Redefine the === method so we can write shorter rules for grapheme cluster breaks - @boundary.each_key do |k| - @boundary[k].instance_eval do - def ===(other) - detect { |i| i === other } ? true : false - end - end if @boundary[k].kind_of?(Array) - end - - # define attr_reader methods for the instance variables - class << self - attr_reader(*ATTRIBUTES) - end - end - - # Returns the directory in which the data files are stored. - def self.dirname - File.expand_path("../values", __dir__) - end - - # Returns the filename for the data file for this version. - def self.filename - File.expand_path File.join(dirname, "unicode_tables.dat") - end + string.swapcase end private - def apply_mapping(string, mapping) - database.codepoints - string.each_codepoint.map do |codepoint| - cp = database.codepoints[codepoint] - if cp && (ncp = cp.send(mapping)) && ncp > 0 - ncp - else - codepoint - end - end.pack("U*") - end - def recode_windows1252_chars(string) string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) end - - def database - @database ||= UnicodeDatabase.new - end end end end diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat deleted file mode 100644 index f7d9c48bbe..0000000000 Binary files a/activesupport/lib/active_support/values/unicode_tables.dat and /dev/null differ -- cgit v1.2.3 From 63d530c5e68a8cf53603744789f53ccbc7ac1a0e Mon Sep 17 00:00:00 2001 From: eileencodes Date: Wed, 21 Feb 2018 16:34:39 -0500 Subject: Call `YAML.load` correctly We should call methods with `.method_name` not `::method_name`. Fix two instances of `YAML::load` I found in favor of `YAML.load`. --- activesupport/lib/active_support/xml_mini.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index d0659aeaae..e42eee07a3 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -79,7 +79,7 @@ module ActiveSupport end, "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) }, "string" => Proc.new { |string| string.to_s }, - "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, + "yaml" => Proc.new { |yaml| YAML.load(yaml) rescue yaml }, "base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) }, "binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) }, "file" => Proc.new { |file, entity| _parse_file(file, entity) } -- cgit v1.2.3 From d7f11cee0327e4c7a437442e9653f60ae08e36cb Mon Sep 17 00:00:00 2001 From: John Fragoulis Date: Fri, 23 Feb 2018 13:08:20 +0200 Subject: Correct ActiveSupport::Deprecation::Behavior#behavior= documentation The callback parameters need to reflect changes after https://github.com/rails/rails/pull/28800 --- activesupport/lib/active_support/deprecation/behaviors.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 581db5f449..66d6f3225a 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -85,7 +85,7 @@ module ActiveSupport # ActiveSupport::Deprecation.behavior = :stderr # ActiveSupport::Deprecation.behavior = [:stderr, :log] # ActiveSupport::Deprecation.behavior = MyCustomHandler - # ActiveSupport::Deprecation.behavior = ->(message, callstack) { + # ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) { # # custom stuff # } def behavior=(behavior) -- cgit v1.2.3 From 1077ae96b34b5a1dfbf10ee0c40b1ceb1eb6b30b Mon Sep 17 00:00:00 2001 From: Gabriel Sobrinho Date: Tue, 6 Feb 2018 12:16:42 -0200 Subject: Caching: MemCache and Redis stores use local cache for multi-reads Fixes #31909. Closes #31911. --- .../lib/active_support/cache/strategy/local_cache.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index aaa9638fa8..e17308f83e 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -54,6 +54,10 @@ module ActiveSupport @data[key] end + def read_multi_entries(keys, options) + Hash[keys.map { |name| [name, read_entry(name, options)] }.keep_if { |_name, value| value }] + end + def write_entry(key, value, options) @data[key] = value true @@ -116,6 +120,19 @@ module ActiveSupport end end + def read_multi_entries(keys, options) + return super unless local_cache + + local_entries = local_cache.read_multi_entries(keys, options) + missed_keys = keys - local_entries.keys + + if missed_keys.any? + local_entries.merge!(super(missed_keys, options)) + else + local_entries + end + end + def write_entry(key, entry, options) if options[:unless_exist] local_cache.delete_entry(key, options) if local_cache -- cgit v1.2.3