Commit Graph

8560 Commits

Author SHA1 Message Date
Ryuta Kamizono
6515d6985d Use bind_call(obj, ...) instead of bind(obj).call(...)
`bind_call(obj, ...)` is a faster alternative to `bind(obj).call(...)`.

https://bugs.ruby-lang.org/issues/15955

Also, enable `Performance/BindCall` cop to detect those in the future.
2021-02-08 17:01:23 +09:00
aweglteo
74b16dfe4f Remove check for UnboundMethod#method_defined? bind_call
Rails 7.0 requires Ruby 2.7+ now so these checks should't be needed.
2021-02-08 16:12:17 +09:00
Ryuta Kamizono
18aec64934
Merge pull request #41365 from ricardotk002/deprecate-range-cover
Use native Range#cover? which accepts a Range argument since Ruby 2.6
2021-02-08 12:46:10 +09:00
Ricardo Díaz
6af2355203 Use native Range#cover? which accepts Range arguments since Ruby 2.6
Commit: 9ca7389272
Discussion: https://bugs.ruby-lang.org/issues/14473

It seems to be compatible with the original ActiveSupport's
implementation, at least based on the test suite.

It also works faster:

```
Warming up --------------------------------------
 Ruby's Range#cover?     1.196M i/100ms
ActiveSupport's Range#cover?
                       396.369k i/100ms
Calculating -------------------------------------
 Ruby's Range#cover?     11.889M (± 1.7%) i/s -     59.820M in   5.033066s
ActiveSupport's Range#cover?
                          3.951M (± 1.2%) i/s -     19.818M in   5.017176s

Comparison:
 Ruby's Range#cover?: 11888979.3 i/s
ActiveSupport's Range#cover?:  3950671.0 i/s - 3.01x  (± 0.00) slower
```

Benchmark script:

```ruby
require "minitest/autorun"
require "benchmark/ips"

module ActiveSupportRange
  def active_support_cover?(value)
    if value.is_a?(::Range)
      is_backwards_op = value.exclude_end? ? :>= : :>
      return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end)
      # 1...10 covers 1..9 but it does not cover 1..10.
      # 1..10 covers 1...11 but it does not cover 1...12.
      operator = exclude_end? && !value.exclude_end? ? :< : :<=
      value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
      cover?(value.first) && (self.end.nil? || value_max.public_send(operator, last))
    else
      cover?
    end
  end
end

class BugTest < Minitest::Test
  def test_range_cover
    Range.prepend(ActiveSupportRange)

    range = (1..10000)

    Benchmark.ips do |x|
      x.report("Ruby's Range#cover?") do
        range.cover?((100..20))
      end

      x.report("ActiveSupport's Range#cover?") do
        range.active_support_cover?((100..20))
      end

      x.compare!
    end
  end
end
```
2021-02-07 22:21:17 -05:00
Ryuta Kamizono
493d357b83
Merge pull request #41357 from andrehjr/dedup-redundant-frozen-check
Remove redundant check for frozen keys on deep dup
2021-02-08 12:10:44 +09:00
Ryuta Kamizono
507c1895a4
Merge pull request #41366 from ricardotk002/remove-ruby-2-5-code
Remove checks for Module#method_defined? arity
2021-02-08 11:54:12 +09:00
André Luis Leal Cardoso Junior
ce01aafb82 Removes Symbol#start_with? and Symbol#end_with? since they are defined in Ruby 2.7
This commit just removes the implementation of those methods and leaves the aliases for `starts_with` and `ends_with?`.
2021-02-07 21:47:25 -03:00
Ricardo Díaz
0ba14c7430 Remove checks for Module#method_defined? arity
The method signature has changed in Ruby 2.6:

https://bugs.ruby-lang.org/issues/14944

Rails 7.0 requires Ruby 2.7+ now and these checks/monkey patches
shouldn't be needed.
2021-02-07 19:30:43 -05:00
Ryuta Kamizono
a33334ada1
Merge pull request #41359 from andrehjr/remove-2-6-ruby-code
Removing Ruby 2.6 monkeypatch from active_support/core_ext/uri
2021-02-08 03:46:07 +09:00
André Luis Leal Cardoso Junior
52363d9586 Removing monkeypatched URI#unescape since main branch follows ruby 2.7 2021-02-07 08:20:17 -03:00
Ricardo Díaz
93cbc30f34 Use Enumerator#all? and Enumerator#any? with classes instead of iterations
These methods have changed in Ruby 2.5 to be more akin to grep:

https://bugs.ruby-lang.org/issues/11286

Using classes seems to be faster (and a bit more expressive) than iterating over
the collection items:

```
Warming up --------------------------------------
    #all? with class   504.000  i/100ms
     #all? with proc   189.000  i/100ms
Calculating -------------------------------------
    #all? with class      4.960k (± 1.6%) i/s -     25.200k in   5.082049s
     #all? with proc      1.874k (± 2.8%) i/s -      9.450k in   5.047866s

Comparison:
    #all? with class:     4959.9 i/s
     #all? with proc:     1873.8 i/s - 2.65x  (± 0.00) slower
```

Benchmark script:

```ruby
require "minitest/autorun"
require "benchmark/ips"

class BugTest < Minitest::Test
  def test_enumerators_with_classes
    arr = (1..10000).to_a << nil

    assert_equal arr.all?(Integer), arr.all? { |v| v.is_a?(Integer) }

    Benchmark.ips do |x|
      x.report("#all? with class") do
        arr.all?(Integer)
      end

      x.report("#all? with proc") do
        arr.all? { |v| v.is_a?(Integer) }
      end

      x.compare!
    end
  end
end
```
2021-02-07 01:29:50 -05:00
Ryuta Kamizono
39e49edaf9 Remove the code for older Rubies RUBY_VERSION < "2.7" in the codebase 2021-02-07 12:20:18 +09:00
Ryuta Kamizono
f9cc3c8b1c Use arguments forwarding in the delegate code generation 2021-02-07 12:08:16 +09:00
André Luis Leal Cardoso Junior
96c52a9d3c Remove redundant check for frozen keys on deep dup
On Ruby 2.7+ all keys are frozen, so no need for the extra check. It seems slightly faster.

```ruby

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "activesupport", github: "rails/rails", branch: "main"
  gem "benchmark-ips"
  gem "benchmark-memory", require: "benchmark/memory"
end

require 'active_support/core_ext/object/deep_dup'

class Hash
  def deep_dup_skip_frozen_check
    hash = dup
    each_pair do |key, value|
      if ::String === key || ::Symbol === key
        hash[key] = value.deep_dup
      else
        hash.delete(key)
        hash[key.deep_dup] = value.deep_dup
      end
    end
    hash
  end
end

data_symbols = Hash[('aaa'..'zzz').map {|k| [k.to_sym, k]}]
data_strings = Hash[('aaa'..'zzz').map {|k| [k, k]}]

Benchmark.ips do |x|
  x.report("data_symbols.deep_dup")                  { data_symbols.deep_dup  }
  x.report("data_symbols.deep_dup_skip_frozen_check") { data_symbols.deep_dup_skip_frozen_check }
  x.compare!
end

Benchmark.ips do |x|
  x.report("data_strings.deep_dup")                  { data_strings.deep_dup  }
  x.report("data_strings.deep_dup_skip_frozen_check") { data_strings.deep_dup_skip_frozen_check }
  x.compare!
end

```

Result:

```
Warming up --------------------------------------
data_symbols.deep_dup
                        17.000  i/100ms
data_symbols.deep_dup_skip_frozen_check
                        18.000  i/100ms
Calculating -------------------------------------
data_symbols.deep_dup
                        177.807  (±12.4%) i/s -    884.000  in   5.067841s
data_symbols.deep_dup_skip_frozen_check
                        192.254  (± 3.1%) i/s -    972.000  in   5.060775s

Comparison:
data_symbols.deep_dup_skip_frozen_check:      192.3 i/s
data_symbols.deep_dup:      177.8 i/s - same-ish: difference falls within error

Warming up --------------------------------------
data_strings.deep_dup
                        18.000  i/100ms
data_strings.deep_dup_skip_frozen_check
                        19.000  i/100ms
Calculating -------------------------------------
data_strings.deep_dup
                        184.347  (± 2.7%) i/s -    936.000  in   5.081570s
data_strings.deep_dup_skip_frozen_check
                        197.728  (± 2.0%) i/s -      1.007k in   5.095122s

Comparison:
data_strings.deep_dup_skip_frozen_check:      197.7 i/s
data_strings.deep_dup:      184.3 i/s - 1.07x  (± 0.00) slower
```

Co-authored-by: Daniel Pepper <pepper.daniel@gmail.com>
2021-02-06 14:28:57 -03:00
Rafael França
7f2d44f019
Merge pull request #41347 from ricardotk002/use-string-grapheme-clusters
Use String#grapheme_clusters instead of String#scan
2021-02-05 16:58:59 -05:00
Ricardo Díaz
95f8bfea79 Fix examples for ActiveSupport::Multibyte::Chars methods [ci skip]
They show the wrong return values. These were recalculated in Ruby
2.7.1 and Rails 7.0.0.alpha.
2021-02-05 16:45:29 -05:00
Ricardo Díaz
8d0ca1a141 Use String#grapheme_clusters and String#each_grapheme_cluster
Both methods were introduced in Ruby 2.5 and are faster than scanning
unicode graphemes with String#scan

```
Warming up --------------------------------------
           scan(/X/)    43.127k i/100ms
   grapheme_clusters   103.348k i/100ms
Calculating -------------------------------------
           scan(/X/)    427.853k (± 2.4%) i/s -      2.156M in   5.042967s
   grapheme_clusters      1.045M (± 0.8%) i/s -      5.271M in   5.042360s

Comparison:
   grapheme_clusters:  1045353.5 i/s
           scan(/X/):   427852.8 i/s - 2.44x  (± 0.00) slower
```

Benchmark script:

```ruby
require "minitest/autorun"
require "benchmark/ips"

class BugTest < Minitest::Test
  def test_grapheme_clusters
    string = [0x0924, 0x094D, 0x0930].pack("U*") # "त्र"
    # string = [0x000D, 0x000A].pack("U*") # cr lf
    # string = "こにちわ"

    assert string.scan(/\X/) == string.grapheme_clusters

    Benchmark.ips do |x|
      x.report("scan(/\X/)") do
        string.scan(/\X/)
      end

      x.report("grapheme_clusters") do
        string.grapheme_clusters
      end

      x.compare!
    end
  end
end
```

String#grapheme_clusters had a bug with CRLF which was fixed in Ruby
2.6: https://bugs.ruby-lang.org/issues/15337

Now that Rails requires Ruby 2.7+, it shouldn't be an issue.
2021-02-05 13:29:56 -05:00
David Heinemeier Hansson
9cb09411e1
Enumerable#in_order_of (#41333)
* Add Enumerable#in_order_of

* Explain behavior further

* Use Enumerable#in_order_of

* Use Ruby 2.7 #filter_map

* Update activesupport/lib/active_support/core_ext/enumerable.rb

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

* No need for explaining variable

* Add CHANGELOG entry

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2021-02-05 17:28:50 +01:00
Ryuta Kamizono
7dd4dde53d Remove all version checking for older Rubies in tests
Code cleanup for tests.
2021-02-05 13:21:45 +09:00
Rafael Mendonça França
1b455e2e9d
Rails 6.2 is now Rails 7.0
We have big plans for the next version of Rails and that
require big versions.
2021-02-04 16:47:16 +00:00
Rafael Mendonça França
6487836af8
Rails 7 requires Ruby 2.7 and prefer Ruby 3+
The code cleanup is comming in later commits but this
already remove support to Ruby < 2.7.
2021-02-04 16:34:53 +00:00
Alex Ghiculescu
0e0c81e092 Handle Symbol arguments in ActiveSupport::Inflector.camelize (v2)
Better implementation of https://github.com/rails/rails/pull/41313

Now, only `false` and `:lower` as args will return a downcased first character (before https://github.com/rails/rails/pull/41313 only `false` did). Everything else returns an upcased first character, which is the default.

Update activesupport/lib/active_support/inflector/methods.rb

Co-authored-by: Ryuta Kamizono <kamipo@gmail.com>
2021-02-02 16:11:28 -07:00
Alex Ghiculescu
cd3eac1df3 Accept a Symbol argument in ActiveSupport::Inflector.camelize
This was raised on the discussion forum: https://discuss.rubyonrails.org/t/inconsistency-between-string-camelize-and-activesupportinflector-camelize

`String#camelize` takes a symbol (`:upper` or `:lower`) as an argument. But `ActiveSupport::Inflector.camelize` takes a bool. This can result in surprising behavior if you assume the methods are interchangeable, and call `ActiveSupport::Inflector.camelize('active_support', :lower)` (the `:lower` argument is truthy so the return value is upcased).

This PR changes `ActiveSupport::Inflector.camelize` to match `String#camelize` behavior. It will now return an upcased string if you provide `true` or `:upper` as an argument, otherwise it will be downcased.
2021-02-02 11:24:41 -07:00
Kasper Timm Hansen
a2a5385b89
Merge pull request #41303 from Shopify/underscore-allocations
Optimize underscore inflector
2021-02-02 13:05:23 +01:00
Jean Boussier
8e0f88d558 Optimize humanize inflector
There a mixed bag of small optimizations here:

  - Since underscores are converted to spaces and that we remove all leading underscores. By changing the order of operations we can leverage the faster `.lstrip!`.
  - Corrected the gsub regexp to no longer match empty strings (`+` vs `*`).
  - Modify the matched strings in place to save on allocations

```ruby
require 'benchmark/ips'
require 'active_support/all'

module ActiveSupport
  module Inflector
    def humanize2(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
      result = lower_case_and_underscored_word.to_s.dup

      inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }

      result.tr!("_", " ")
      result.lstrip!
      unless keep_id_suffix
        result.delete_suffix!(" id")
      end

      result.gsub!(/([a-z\d]+)/i) do |match|
        match.downcase!
        inflections.acronyms[match] || match
      end

      if capitalize
        result.sub!(/\A\w/) do |match|
          match.upcase!
          match
        end
      end

      result
    end
  end
end

%w(foo foo_bar_id ____foo_bar).each do |str|
  puts "== Comparing with #{str.inspect} (#{RUBY_VERSION}) =="
  unless ActiveSupport::Inflector.humanize(str) == ActiveSupport::Inflector.humanize2(str)
    raise "#{ActiveSupport::Inflector.humanize2(str)} != #{ActiveSupport::Inflector.humanize(str)}"
  end

  Benchmark.ips do |x|
    x.report('humanize') { ActiveSupport::Inflector.humanize(str) }
    x.report('humanize2') { ActiveSupport::Inflector.humanize2(str) }
    x.compare!
  end
  puts
end
```

```
== Comparing with "foo" (2.7.2) ==
Warming up --------------------------------------
            humanize    25.593k i/100ms
           humanize2    29.256k i/100ms
Calculating -------------------------------------
            humanize    263.989k (± 1.9%) i/s -      1.331M in   5.043110s
           humanize2    299.883k (± 2.2%) i/s -      1.521M in   5.075478s

Comparison:
           humanize2:   299882.5 i/s
            humanize:   263989.1 i/s - 1.14x  (± 0.00) slower

== Comparing with "foo_bar_id" (2.7.2) ==
Warming up --------------------------------------
            humanize    18.187k i/100ms
           humanize2    25.678k i/100ms
Calculating -------------------------------------
            humanize    183.702k (± 1.5%) i/s -    927.537k in   5.050326s
           humanize2    250.470k (± 2.5%) i/s -      1.258M in   5.026682s

Comparison:
           humanize2:   250469.6 i/s
            humanize:   183702.3 i/s - 1.36x  (± 0.00) slower

== Comparing with "____foo_bar" (2.7.2) ==
Warming up --------------------------------------
            humanize    18.577k i/100ms
           humanize2    24.686k i/100ms
Calculating -------------------------------------
            humanize    188.868k (± 1.5%) i/s -    947.427k in   5.017524s
           humanize2    255.650k (± 1.8%) i/s -      1.284M in   5.022833s

Comparison:
           humanize2:   255649.8 i/s
```
2021-02-02 12:16:16 +01:00
Jean Boussier
58147cd2b4 Optimize underscore inflector
```ruby
require 'benchmark/ips'
require 'active_support/all'

ActiveSupport::Inflector.inflections do |inflect|
    inflect.acronym('RESTful')
end

module ActiveSupport
  module Inflector
    def underscore2(camel_cased_word)
      return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
      word = camel_cased_word.to_s.gsub("::", "/")
      word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
      word.gsub!(/([A-Z\d]+)([A-Z][a-z])|([a-z\d])([A-Z])/) do
        if first_match = $1
          first_match << '_' << $2
        else
          $3 << '_' << $4
        end
      end
      word.tr!("-", "_")
      word.downcase!
      word
    end
  end
end

%w(RESTfulController Foo FooBar Foo::BarBaz FOOBar::EggSpam).each do |str|
  puts "== Comparing with #{str.inspect} (#{RUBY_VERSION}) =="
  unless ActiveSupport::Inflector.underscore(str) == ActiveSupport::Inflector.underscore2(str)
    raise "#{ActiveSupport::Inflector.underscore2(str)} != #{ActiveSupport::Inflector.underscore(str)}"
  end

  Benchmark.ips do |x|
    x.report('underscore') { ActiveSupport::Inflector.underscore(str) }
    x.report('underscore2') { ActiveSupport::Inflector.underscore2(str) }
    x.compare!
  end
  puts
end
```

```
== Comparing with "RESTfulController" (2.7.2) ==
Warming up --------------------------------------
          underscore    16.527k i/100ms
         underscore2    16.118k i/100ms
Calculating -------------------------------------
          underscore    165.223k (± 0.9%) i/s -    826.350k in   5.001882s
         underscore2    163.251k (± 0.9%) i/s -    822.018k in   5.035736s

Comparison:
          underscore:   165222.7 i/s
         underscore2:   163250.9 i/s - same-ish: difference falls within error

== Comparing with "Foo" (2.7.2) ==
Warming up --------------------------------------
          underscore    66.965k i/100ms
         underscore2    72.961k i/100ms
Calculating -------------------------------------
          underscore    669.221k (± 0.8%) i/s -      3.348M in   5.003557s
         underscore2    729.815k (± 1.1%) i/s -      3.721M in   5.099188s

Comparison:
         underscore2:   729815.0 i/s
          underscore:   669221.3 i/s - 1.09x  (± 0.00) slower

== Comparing with "FooBar" (2.7.2) ==
Warming up --------------------------------------
          underscore    26.869k i/100ms
         underscore2    26.446k i/100ms
Calculating -------------------------------------
          underscore    268.071k (± 0.7%) i/s -      1.343M in   5.011813s
         underscore2    264.400k (± 0.6%) i/s -      1.322M in   5.001317s

Comparison:
          underscore:   268071.4 i/s
         underscore2:   264400.1 i/s - 1.01x  (± 0.00) slower

== Comparing with "Foo::BarBaz" (2.7.2) ==
Warming up --------------------------------------
          underscore    20.611k i/100ms
         underscore2    20.609k i/100ms
Calculating -------------------------------------
          underscore    205.671k (± 1.3%) i/s -      1.031M in   5.011478s
         underscore2    204.304k (± 1.2%) i/s -      1.030M in   5.044446s

Comparison:
          underscore:   205670.8 i/s
         underscore2:   204304.1 i/s - same-ish: difference falls within error

== Comparing with "FOOBar::EggSpam" (2.7.2) ==
Warming up --------------------------------------
          underscore    15.348k i/100ms
         underscore2    18.932k i/100ms
Calculating -------------------------------------
          underscore    154.686k (± 0.9%) i/s -    782.748k in   5.060656s
         underscore2    188.706k (± 0.8%) i/s -    946.600k in   5.016580s

Comparison:
         underscore2:   188705.6 i/s
          underscore:   154685.7 i/s - 1.22x  (± 0.00) slower
```
2021-02-02 11:46:46 +01:00
Jean Boussier
73bc476cd0 Reduce allocations in camelize inflector
This method is the 4th biggest source of allocation during our application
boot (~300k allocations).

The goal of this PR was mostly to reduce allocations, but it also translate
to a 5-17% speedup.

The main change is leveraging `capitalize!` rather than `capitalize`.

The second important change is to merge the two `gsub!` together.

```ruby
require 'benchmark/ips'
require 'active_support/all'

module ActiveSupport
  module Inflector
    def camelize2(term, uppercase_first_letter = true)
      string = term.to_s
      if uppercase_first_letter
        string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match }
      else
        string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match }
      end
      string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
        if $1
          "::#{inflections.acronyms[$2] || $2.capitalize! || $2}"
        else
          inflections.acronyms[$2] || $2.capitalize! || $2
        end
      end
      string
    end
  end
end

%w(foo foo_bar foo/bar_baz).each do |str|
  puts "== Comparing with #{str.inspect} (#{RUBY_VERSION}) =="
  Benchmark.ips do |x|
    x.report('camelize') { ActiveSupport::Inflector.camelize(str) }
    x.report('camelize2') { ActiveSupport::Inflector.camelize2(str) }
    x.compare!
  end
  puts
end
```

Results
```
== Comparing with "foo" (2.7.2) ==
Warming up --------------------------------------
            camelize    53.646k i/100ms
           camelize2    56.797k i/100ms
Calculating -------------------------------------
            camelize    532.012k (± 1.3%) i/s -      2.682M in   5.042620s
           camelize2    567.317k (± 1.3%) i/s -      2.840M in   5.006633s

Comparison:
           camelize2:   567317.4 i/s
            camelize:   532012.3 i/s - 1.07x  (± 0.00) slower

== Comparing with "foo_bar" (2.7.2) ==
Warming up --------------------------------------
            camelize    24.010k i/100ms
           camelize2    25.398k i/100ms
Calculating -------------------------------------
            camelize    239.036k (± 1.1%) i/s -      1.200M in   5.022811s
           camelize2    254.229k (± 1.3%) i/s -      1.295M in   5.095855s

Comparison:
           camelize2:   254228.8 i/s
            camelize:   239036.4 i/s - 1.06x  (± 0.00) slower

== Comparing with "foo/bar_baz" (2.7.2) ==
Warming up --------------------------------------
            camelize    17.116k i/100ms
           camelize2    19.628k i/100ms
Calculating -------------------------------------
            camelize    169.210k (± 1.0%) i/s -    855.800k in   5.058138s
           camelize2    199.385k (± 2.1%) i/s -      1.001M in   5.022935s

Comparison:
           camelize2:   199384.7 i/s
            camelize:   169210.0 i/s - 1.18x  (± 0.00) slower
```
2021-02-01 18:38:17 +01:00
Ryuta Kamizono
059024ea79
Merge pull request #41260 from inopinatus/filter_parameters_doc
Add example of the regexp parameter filter type [ci skip]
2021-01-29 09:53:10 +09:00
Kuldeep Aggarwal
3c975d85f1
Use string for example
In the docs [here](https://apidock.com/rails/v6.0.0/ActiveSupport/MessageVerifier) under the `Making messages expire` section, it was a little unclear what is `doowad` & `parcel` which ideally be a +string+ or a variable but in the complete documentation, we haven't used the reference of any variables except +token+, so these 2 should be a string.
2021-01-28 18:19:46 -05:00
Josh Goodall
36895d2d55 Regexp example for filter parameters.
Supply an explanatory example for the Regexp type of filter, which is
mentioned in the documentation of the initializer but without any
illustration.
2021-01-29 05:26:20 +11:00
Brandon Fish
7f2c6a87fe Update test helper to call parallelize according to fork support 2021-01-25 13:50:41 -06:00
Rafael França
15ed2cbe32
Merge pull request #40392 from shioyama/remove_respond_to_unsafe_method
Don't bother checking if strings respond to string methods
2021-01-21 19:00:35 -05:00
Rafael Mendonça França
077c66d5d6
Rename master to main in all code references 2021-01-19 20:46:33 +00:00
Jean Boussier
a65b8a00cb Define Singleton#duplicable? and return false 2021-01-12 16:54:54 +01:00
Rafael França
c2f5e1783a
Merge pull request #41014 from dbussink/specific-sha256-usage
Move away from using the Digest::SHA2 class
2021-01-08 18:04:01 -05:00
Dirkjan Bussink
ba9207f301
Change the default digest for new apps to SHA256
As mentioned in
https://github.com/rails/rails/pull/40770#issuecomment-748347066 we
should default to SHA256 where SHA1 is used today. This switches over
the ActiveSupport::Digest to use SHA256 for new applications.

It also updates the constants to always refer to and use the OpenSSL
constants as well, as also discussed in that PR.
2021-01-08 12:07:20 +01:00
Dirkjan Bussink
447e28347e
Allow configuration of the digest class used in the key generator
This change allows for configuration of the hash digest that is used in
the key generator for key derivation.

SHA1 is an outdated algorithm and security auditors tend to frown on
its usage. By allowing this to be configured, it becomes possible to
move to a more up to date hash mechanism.

While I don't think this has any current relevant security implications,
especially not with a proper random secret base, moving away from SHA1
makes conversations with auditors and FIPS compliance checks easier
since the best answer is always that an approved algorithm is used.

A rotation can be built using this change with an approach like the
following for encrypted cookies:

```ruby
Rails.application.config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA256

Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
  salt = Rails.application.config.action_dispatch.authenticated_encrypted_cookie_salt
  secret_key_base = Rails.application.secrets.secret_key_base

  key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1)
  key_len = ActiveSupport::MessageEncryptor.key_len
  secret = key_generator.generate_key(salt, key_len)

  cookies.rotate :encrypted, secret
end
```

This turns the default into using SHA256 but also still accepts secrets
derived using SHA1.

The defaults for new apps is here changed to use SHA256. Existing apps
will keep using SHA1.
2021-01-07 14:28:01 +01:00
Dirkjan Bussink
e54d8c5a99
Move away from using the Digest::SHA2 class
The Digest::SHA2 class implicitly ends up using SHA256 by default, but
there's no OpenSSL version of the same class (OpenSSL::Digest::SHA2 does
not exist).

To make it easier to force OpenSSL for hash usage and not use deprecated
OpenSSL APIs from Digest on older Ruby versions before 3.0, an approach
like https://github.com/ruby/openssl/pull/377 might be used.

That approach won't work with this SHA2 class though, so it shouldn't be
used. The discussion in https://github.com/rails/rails/pull/40770 also
indicates that in general the intent is to move to OpenSSL constants
anyway, so this makes that move a lot easier as well.

Lastly, this updates the cache key usage in the Redis cache store. This
store seems to be the only one not using the configured
ActiveSupport::Digest class like all other cache usage. Switching it
from the not recommended SHA2 class to the ActiveSupport one makes it
more consistent with the rest. This also updates the cache key name to
not call out the implementation of the hash, and updates memcache in a
similar way (where it was still referring to MD5 which is not guaranteed
to be used for ActiveSupport::Digest).
2021-01-07 14:27:35 +01:00
Mike Dalessio
76bfda8f72
Remove leading whitespace from the XML under test
This test was originally written with the intention of asserting that
a runtime error related to XXE will be raised by the parser. However,
because initial whitespace is present before the doctype,
XmlMini_NokogiriSAX::HashBuilder has been raising an unrelated error
in this test.

Related to #41015

---

Using Nokogiri v1.10.10, the error being raised without this change
is:

> XML declaration allowed only at the start of the document

but with this change we see the expected exception from the libxml2
SAX parser:

> Entity 'a' not defined

Using Nokogiri v1.11.0, in which error handling is broken (see
sparklemotion/nokogiri#2168), without this change we see an exception
being raised by HashBuilder because `characters` is called before
`start_element` and so the content hash isn't initialized (see

The error being raised with this change is:

> Parse stack not empty!

which is not the error we want (because of
sparklemotion/nokogiri#2168), but the test still passes.

Using Nokogiri with the fix from sparklemotion/nokogiri#2169, the
error being raised without this change is:

> XML declaration allowed only at the start of the document

but with this change will be:

> Entity 'a' not defined

and we're back to the expected behavior.
2021-01-05 22:29:25 -05:00
Ryuta Kamizono
2b0b5a75c0 Bump license years to 2021 [ci skip] 2021-01-01 12:21:20 +09:00
Alec Clarke
f5afc1a5ad Clarify use of rescue_from [ci skip]
`rescue_from` works for rescuing exceptions in controller actions, but it's not specific to ActionController.

This change updates the docs to clarify the specifics of how `rescue_from` is used and that its use goes beyond controller actions.

Note: I believe the original addition of this documentation was added as part of the move from [`ActionController::Rescue` to `ActiveSupport::Rescuable`](259a7a844b (diff-2276a3674b84e1262c94cc36346f9fbd8d00e0a4542bb991a8846c06d86335b1R12))
2020-12-30 11:51:13 -05:00
Rafael França
50073df569
Merge pull request #40929 from ritikesh/redis_info
add ActiveSupport::Cache::RedisCacheStore#info
2020-12-29 14:29:14 -05:00
Ritikesh
c8934f3c02 add stats method on RedisCacheStore similar to MemCacheStore 2020-12-29 13:22:24 +05:30
Rafael França
12a3ac1e08
Merge pull request #40964 from mrpinsky/expires-in-aliases
Support aliases to expires_in for cache stores
2020-12-28 20:34:32 -05:00
Nate Pinsky
fd8c36707f Support aliases to expires_in for cache stores
The `expires_in` option is easy to misremember or mistype as `expire_in`
or `expired_in`, with potentially harmful results. If a developer wants
to cache a value for only 2 seconds but mistakenly types
`expire_in: 2.seconds`, that value will instead be cached for the
default time, likely 6 hours, meaning that users of the site will see
the same data for much longer than they should, and the only recovery
(short of waiting for the 6 hours to elapse) is to manually expire all
relevant keys. This commit allows cache stores to recognize these common
typos as aliases by normalizing them before consuming the options.

In general, we should be careful about adding too many aliases for
options to the cache stores, since each option key used by the base
Cache::Store class is one fewer key that each cache implementation can
customize for itself. This case was approved because of the similarity
of the aliases to the root key and the potential damage caused by
mistaking them.

Fixes #39850.
2020-12-28 17:15:43 -08:00
Rafael Mendonça França
dbf6dbb147
Add CHANGELOG entry for #39626 2020-12-29 00:30:40 +00:00
Rafael França
78bf8f83b2
Merge pull request #39626 from vipulnsward/as-notification-args
Raise error when passing invalid arguments on subscription instead of failing silently
2020-12-28 19:30:02 -05:00
Rafael Mendonça França
e7d207f02f
Change IPAddr#to_json to match the behavior of the json gem
Returning the string representation instead of the instance
variables of the object.

    Before:

    ```ruby
    IPAddr.new("127.0.0.1").to_json
    # => "{\"addr\":2130706433,\"family\":2,\"mask_addr\":4294967295}"
    ```

    After:

    ```ruby
    IPAddr.new("127.0.0.1").to_json
    # => "\"127.0.0.1\""
    ```

Fixes #40932.
2020-12-28 05:46:09 +00:00
John Bampton
9a2b6667e3 Fix spelling 2020-12-27 04:09:49 +10:00
Ivan Giuliani
66f8a2ea7f Include file path in invisible space warning
While the warning is useful in itself, it doesn't tell you what file is
specifically causing the issue which can make resolving it harder than
it should be. As we've got the path already, we can simply include the
location of the problematic file in the warning message.
2020-12-23 10:07:38 +00:00
Ritikesh
5d1eeea84b consume dalli's cache_nils configuration as ActiveSupport::Cache's skip_nil when using MemCacheStore. 2020-12-18 13:10:33 +05:30
Michael Ziwisky
74ab4d930d require "time" where we depend on Time#xmlschema
The docs for Time#xmlschema note "You must require 'time' to use this
method." -- see
https://ruby-doc.org/stdlib-2.7.2/libdoc/time/rdoc/Time.html#method-i-xmlschema

Apparently in most cases, by the time `core_ext/time/conversions.rb` is
loaded, "time" has already been required, however that is not a
guarantee. If it isn't, you'll get a "NameError (undefined method
`xmlschema' for class `Time')". A simple repro is to launch `irb` and
do:

    > require 'active_support'
    > require 'active_support/core_ext/date_time'

This can even happen on some systems with just a:

    > require 'active_support'
    > require 'active_support/core_ext'

That is because `active_support/core_ext.rb` uses `Dir.glob` to
enumerate and then require all ruby files in the `core_ext` directory,
but "the order in which the results are returned [from Dir.glob]
depends on your system" -- see
https://ruby-doc.org/core-2.7.2/Dir.html#method-c-glob

Therefore this commit also sorts those results to make the load order
deterministic and system-agnostic.
2020-12-16 00:50:05 -08:00
T.J. Schuck
fe861bbdd2 Fix code formatting
The `+` is insufficient for the parens inside — needs the full `<tt>` treatment.

[ci skip]
2020-12-15 10:54:26 -05:00
Rafael França
aeea158d95
Merge pull request #40774 from stevecrozz/inflection-locale-defaults-and-fallbacks
Fix :en-GB pluralization test (day -> days)
2020-12-09 13:45:34 -05:00
Stephen Crosby
fc61648249
Fix :en-GB pluralization test (day -> days) 2020-12-09 10:33:20 -08:00
Rafael França
d3ccc920e7
Merge pull request #38659 from stevecrozz/inflection-locale-defaults-and-fallbacks
Inflection support default_locale and fallbacks
2020-12-09 13:30:34 -05:00
Stephen Crosby
ea27ff3d62
Inflection support default_locale and fallbacks 2020-12-09 10:06:05 -08:00
Rafael França
eebde10693
Merge pull request #40759 from orhantoy/broadcast-tagged-logging
Clone to keep extended Logger methods for tagged logger
2020-12-08 14:01:53 -05:00
KapilSachdev
a908d06c85
feat(rubocop): Add Style/RedundantRegexpEscape
- This cop will help in removing unnecessary escaping inside Regexp literals.
2020-12-08 18:57:09 +00:00
Rafael França
ccefd5ce7f
Merge pull request #40201 from Shopify/symbol-name
Use Symbol#name if available in HashWithIndifferentAccess
2020-12-08 13:45:16 -05:00
Orhan Toy
70af536b5d Clone to keep extended Logger methods for tagged logger
`#dup` resets the extended Logger methods that could come from enabling broadcasting. That would mean if we create a tagged logger from a Logger with broadcasting enabled (usually to stdout), the new tagged logger will not perform broadcasting.
2020-12-07 14:43:37 +01:00
Rafael Mendonça França
59f7f5889e
Start Rails 6.2 development 🎉 2020-12-03 01:35:29 +00:00
Rafael França
366df0397f
Merge pull request #40721 from d12/optimize_hash_with_indifferent_access_initializer
55% speedup for HashWithIndifferentAccess.new when no args provided
2020-12-01 14:56:56 -05:00
Nathaniel Woodthorpe
0841cdf268 Optimize HashWithIndifferentAccess.new when no args are provided 2020-12-01 13:25:11 -05:00
Akira Matsuda
6f9d4a000b
Merge pull request #40663 from amatsuda/keep_safe_buffer
Let AS::SafeBuffer#[] and * return value be an instance of SafeBuffer in Ruby 3.0
2020-12-01 17:42:37 +09:00
Akira Matsuda
4cb20843eb Mark scrub as an unsafe method on SafeBuffer 2020-12-01 17:40:17 +09:00
Nathaniel Woodthorpe
f5e5976388 Fix the return value of #deep_transform_keys from a Hash to a HashWithIndifferentAccess 2020-11-30 17:13:53 -05:00
alvir
b96f4ae1d5 Use application time zone when gets time as String. 2020-11-26 11:35:23 +03:00
Bibek Sharma Chapagain
fde2d644b1
Grammer correction on ActiveSupport en.yml 2020-11-22 14:12:26 +11:00
Akira Matsuda
a4d2493b26 Let AS::SafeBuffer#[] and * return value be an instance of SafeBuffer in Ruby 3
Ruby 3 introduces an incompatibility that String methods return String instances when called on a subclass instance.
https://bugs.ruby-lang.org/issues/10845
https://github.com/ruby/ruby/pull/3701
2020-11-21 16:32:18 +09:00
Petrik
332a2909d4 Fix ForkTracker on ruby <= 2.5.3
Making the fork method private by calling `private :fork` raises a
"no superclass method `fork'" error when calling super in a subclass on
ruby <= 2.5.3. The error doesn't occur on ruby 2.5.4 and higher.
Making the method private by redefining doesn't raise the error.

The possible fix on 2.5.4 is 75aba10d7a

The error can be reproduced with the following script on ruby 2.5.3:
```
class Cluster
  def start
    fork { puts "forked!" }
  end
end

module CoreExt
  def fork(*)
    super
  end
end

module CoreExtPrivate
  include CoreExt
  private :fork
end

::Object.prepend(CoreExtPrivate)
Cluster.new.start
```

Fixes #40603
2020-11-17 21:22:12 +01:00
Akshay Birajdar
888d8c7063 [ci skip] Fix rdoc formatting 2020-11-17 18:49:19 +05:30
Jonathan Hefner
b20ac9a1d3 Document Regexp#multiline? [ci-skip]
`Regexp#multiline?` has been publicized in the Active Support Core
Extensions guide for a while now.  This commit adds matching API docs.
2020-11-11 16:03:02 -06:00
Daniel Colson
4e646bb281
Allow subscribing with a single argument callable
Fixes #39976

Prior to this commit it was possible to pass a single argument block to
`ActiveSupport::Notifications.subscribe`, rather than 5 separate
arguments:

```rb
ActiveSupport::Notifications.subscribe('some_event') do |event|
  puts "Reacting to #{event.name}"
end
```

But it was not possible to do the same with a lambda, since the lambda
parameter is a required (`:req`) parameter, but we were checking only
for an optional (`:opt`) parameter.

```rb
listener = ->(event) do
  puts "Reacting to #{event.name}"
end

ActiveSupport::Notifications.subscribe('some_event', &listener)
```

It was also not possible to do this with a custom callable object, since
the custom callable does not respond directly to `:parameters` (although
it's `:call` method object does).

```rb
class CustomListener
  def call(event)
    puts "Reacting to #{event.name}"
  end
end

ActiveSupport::Notifications.subscribe('some_event', CustomListener.new)
```

Prior to this commit these examples would have raised `ArgumentError:
wrong number of arguments (given 5, expected 1)`.

With this commit the single argument lambda and custom callable work
like the single argument block.
2020-11-09 22:26:21 -05:00
Rafael França
e71b4a5e04
Merge pull request #40588 from etiennebarrie/activesupport-currentattributes-testhelper-teardown
Allow access to CurrentAttributes in test teardown
2020-11-09 16:04:42 -05:00
Étienne Barrié
0400be279b Allow access to CurrentAttributes in test teardown 2020-11-09 15:15:42 -05:00
Rafael Mendonça França
0a59de2d2a
Don't require event to be passed to read_entry
This will make sure this method is backward compatible with stores that
don't pass the intrumentation payload to the method.
2020-11-09 20:11:36 +00:00
Rafael França
53e97f0fa0
Merge pull request #40490 from kirs/cache-instrument-store-local
Instrument cache entries from local cache
2020-11-05 16:36:50 -05:00
Kir Shatrov
c88205613b Instrument cache entries from local cache 2020-11-04 23:12:21 +00:00
maxgurewitz
55501549cb disable compression for MemoryStore's by default
- Compression has reduced effectiveness for MemoryStore, which does not
send data over a network.
2020-11-04 12:20:24 -05:00
Eugene Kenny
bc524f16ee
Merge pull request #40517 from eugeneius/depend_on_message
Use LoadError#original_message if available in depend_on
2020-11-03 18:12:24 +00:00
Tahsin Hasan
a52ca5fddc Create unit test to use to_time for timestamp in string 2020-11-03 18:51:43 +06:00
Rafael Mendonça França
8389f9902c
Preparing for 6.1.0.rc1 release 2020-11-02 21:12:47 +00:00
Eugene Kenny
94ab712585 Use LoadError#original_message if available in depend_on
did_you_mean 1.5.0 will add suggestions to `LoadError`. This means that
`LoadError#message` will now return a new string on each invocation, and
mutating the result will no longer modify the error's message.
2020-11-02 20:54:29 +00:00
Rafael Mendonça França
81ee5dcdf4
Merge pull request #39538.
Closes #39538.
2020-11-02 20:42:40 +00:00
Vipul A M
fdfac8760f
Although libraries support both formats of sign before and after DIGITS(ex: https://github.com/moment/luxon/pull/683, https://github.com/moment/moment/issues/2408), many do not.
For example PG refers to https://www.ietf.org/rfc/rfc3339.txt when converting(Ref: https://www.postgresql.org/docs/current/datatype-datetime.html)

According to the ref there is no explicit mention of allowing sign before the parts, which reads as below:

 Durations:

    dur-second        = 1*DIGIT "S"
    dur-minute        = 1*DIGIT "M" [dur-second]
    dur-hour          = 1*DIGIT "H" [dur-minute]
    dur-time          = "T" (dur-hour / dur-minute / dur-second)
    dur-day           = 1*DIGIT "D"
    dur-week          = 1*DIGIT "W"
    dur-month         = 1*DIGIT "M" [dur-day]
    dur-year          = 1*DIGIT "Y" [dur-month]
    dur-date          = (dur-day / dur-month / dur-year) [dur-time]

    duration          = "P" (dur-date / dur-time / dur-week)

We should not attempt to move sign forward in this case.
2020-11-02 20:41:48 +00:00
Rafael Mendonça França
36fe7b8d8f
Ruby 2.7.2 still have the same bug 2020-10-30 21:33:09 +00:00
Rafael Mendonça França
0b72dd37c1
Fix warning in Rubies that already have the method 2020-10-30 21:01:33 +00:00
Rafael França
fcb5f9035f
Merge pull request #39828 from tgxworld/backward_patches_for_time_floor_ceil
Add `Time#ceil` and `Time#floor` core extensions.
2020-10-30 16:01:55 -04:00
Eugene Kenny
9509bc23c2 Fixup test for TimeWithZone Time.at precision
This test doesn't involve a DateTime, and can be shortened a bit.
2020-10-30 12:35:03 +00:00
Eugene Kenny
a6636b0d1c
Merge pull request #40448 from BKSpurgeon/fix-timezone-rounding-bug
Fix: timezone bug - rounding problem
2020-10-30 12:00:55 +00:00
Rafael França
43daedcb7d
Merge pull request #39550 from vipulnsward/expiry-fix
Fix parsing of jsonified expiry date on AS::Message::Metadata for old json format
2020-10-29 21:47:40 -04:00
Ryuta Kamizono
8512213a39 Fix deprecation will be removed version s/Rails 6.1/Rails 6.2/ 2020-10-30 10:11:29 +09:00
Rafael Mendonça França
a7faef6869
Remove deprecated ActiveSupport::Notifications::Instrumenter#end= 2020-10-30 00:26:03 +00:00
Rafael Mendonça França
6cbc7842f0
Deprecate ActiveSupport::Multibyte::Unicode.default_normalization_form 2020-10-30 00:26:03 +00:00
Rafael Mendonça França
2c6f5c0b8a
Remove deprecated methods in ActiveSupport::Multibyte::Unicode 2020-10-30 00:26:02 +00:00
Rafael Mendonça França
8f14d5ad4b
Remove deprecated ActiveSupport::Multibyte::Chars#consumes? and ActiveSupport::Multibyte::Chars#normalize 2020-10-30 00:26:01 +00:00
Rafael Mendonça França
6a4151f026
Remove deprecated file active_support/core_ext/range/include_range 2020-10-30 00:25:59 +00:00
Rafael Mendonça França
96a0cb6938
Remove deprecated file active_support/core_ext/hash/transform_values 2020-10-30 00:25:58 +00:00
Rafael Mendonça França
36eebfe184
Remove deprecated file active_support/core_ext/hash/compact 2020-10-30 00:25:57 +00:00