diff options
Diffstat (limited to 'activesupport/lib/active_support/core_ext')
12 files changed, 132 insertions, 130 deletions
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| diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 17733d955c..f01d01e6aa 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -3,50 +3,37 @@ module Enumerable # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements # when we omit an identity. + + # We can't use Refinements here because Refinements with Module which will be prepended + # doesn't work well https://bugs.ruby-lang.org/issues/13446 + alias :_original_sum_with_required_identity :sum + private :_original_sum_with_required_identity + + # Calculates a sum from the elements. # - # We tried shimming it to attempt the fast native method, rescue TypeError, - # and fall back to the compatible implementation, but that's much slower than - # just calling the compat method in the first place. - if Enumerable.instance_methods(false).include?(:sum) && !((?a..?b).sum rescue false) - # We can't use Refinements here because Refinements with Module which will be prepended - # doesn't work well https://bugs.ruby-lang.org/issues/13446 - alias :_original_sum_with_required_identity :sum - private :_original_sum_with_required_identity - # Calculates a sum from the elements. - # - # payments.sum { |p| p.price * p.tax_rate } - # payments.sum(&:price) - # - # The latter is a shortcut for: - # - # payments.inject(0) { |sum, p| sum + p.price } - # - # It can also calculate the sum without the use of a block. - # - # [5, 15, 10].sum # => 30 - # ['foo', 'bar'].sum # => "foobar" - # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5] - # - # The default sum of an empty list is zero. You can override this default: - # - # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) - def sum(identity = nil, &block) - if identity - _original_sum_with_required_identity(identity, &block) - elsif block_given? - map(&block).sum(identity) - else - inject(:+) || 0 - end - end - else - def sum(identity = nil, &block) - if block_given? - map(&block).sum(identity) - else - sum = identity ? inject(identity, :+) : inject(:+) - sum || identity || 0 - end + # payments.sum { |p| p.price * p.tax_rate } + # payments.sum(&:price) + # + # The latter is a shortcut for: + # + # payments.inject(0) { |sum, p| sum + p.price } + # + # It can also calculate the sum without the use of a block. + # + # [5, 15, 10].sum # => 30 + # ['foo', 'bar'].sum # => "foobar" + # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5] + # + # The default sum of an empty list is zero. You can override this default: + # + # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) + def sum(identity = nil, &block) + if identity + _original_sum_with_required_identity(identity, &block) + elsif block_given? + map(&block).sum(identity) + else + inject(:+) || 0 end end @@ -133,27 +120,21 @@ class Range #:nodoc: end end -# Array#sum was added in Ruby 2.4 but it only works with Numeric elements. -# -# We tried shimming it to attempt the fast native method, rescue TypeError, -# and fall back to the compatible implementation, but that's much slower than -# just calling the compat method in the first place. -if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false) - # Using Refinements here in order not to expose our internal method - using Module.new { - refine Array do - alias :orig_sum :sum - end - } +# Using Refinements here in order not to expose our internal method +using Module.new { + refine Array do + alias :orig_sum :sum + end +} - class Array - def sum(init = nil, &block) #:nodoc: - if init.is_a?(Numeric) || first.is_a?(Numeric) - init ||= 0 - orig_sum(init, &block) - else - super - end +class Array #:nodoc: + # Array#sum was added in Ruby 2.4 but it only works with Numeric elements. + def sum(init = nil, &block) + if init.is_a?(Numeric) || first.is_a?(Numeric) + init ||= 0 + orig_sum(init, &block) + else + super end end end 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/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 4310df3024..426e34128f 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -114,6 +114,24 @@ class Module # invoice.customer_name # => 'John Doe' # invoice.customer_address # => 'Vimmersvej 13' # + # If you want the delegated method to be a private method, + # use the <tt>:private</tt> option. + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :first_name, to: :profile + # delegate :date_of_birth, :religion, to: :profile, private: true + # + # def age + # Date.today.year - date_of_birth.year + # end + # end + # + # User.new.age # 2 + # User.new.first_name # Tomas + # User.new.date_of_birth # NoMethodError: private method `date_of_birth' called for #<User:0x00000008221340> + # User.new.religion # NoMethodError: private method `religion' called for #<User:0x00000008221340> + # # If the target is +nil+ and does not respond to the delegated method a # +Module::DelegationError+ is raised. If you wish to instead return +nil+, # use the <tt>:allow_nil</tt> option. @@ -151,7 +169,7 @@ class Module # Foo.new("Bar").name # raises NoMethodError: undefined method `name' # # The target method must be public, otherwise it will raise +NoMethodError+. - def delegate(*methods, to: nil, prefix: nil, allow_nil: nil) + def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil) unless to raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)." end @@ -173,7 +191,7 @@ class Module to = to.to_s to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to) - methods.map do |method| + method_names = methods.map do |method| # Attribute writer methods only accept one argument. Makes sure []= # methods still accept two arguments. definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block" @@ -213,6 +231,9 @@ class Module module_eval(method_def, file, line) end + + private(*method_names) if private + method_names end # When building decorators, a common pattern may emerge: 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/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/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 <tt>bytesize</tt> 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 <tt>:omission</tt> string, defaulting + # to "…", for a total length not exceeding <tt>bytesize</tt>. + 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 (<tt>words_count</tt>): # # 'Once upon a time in a world far far away'.truncate_words(4) 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/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 |