Commit Graph

7618 Commits

Author SHA1 Message Date
Xavier Noria
92e088a4d5 stub_const polishing 2023-04-21 22:19:39 +02:00
Jean Boussier
d78397916e Module#delegate directly define methods as private
Fix: https://github.com/rails/rails/issues/47999

Useful for `method_added` hooks, but also should perform
very sligtly better for large list of delegators.
2023-04-20 23:38:09 +02:00
Jean Boussier
138a9d2031
Merge pull request #47982 from nobu/patch-3
Reduce captures in `ActiveSupport::Inflector#underscore`
2023-04-20 18:03:05 +02:00
Nobuyoshi Nakada
4c0cee83dc Reduce captures in ActiveSupport::Inflector#underscore 2023-04-20 18:01:53 +02:00
Guillermo Iguaran
c2ec6e7b88 Add ActiveSupport::Cache::MemCacheStore#inspect
Make the output more readable.
This is a follow up of https://github.com/rails/rails/pull/47944
2023-04-19 21:02:32 -07:00
Jean Boussier
8d9eaf366b
Merge pull request #47944 from the-spectator/inspect_for_cache_store
Implement #inspect for file & null cache store
2023-04-19 23:35:12 +02:00
Jonathan Hefner
a2a6331451 Add ActiveSupport::MessagePack
`ActiveSupport::MessagePack` is a serializer that integrates with the
`msgpack` gem to serialize a variety of Ruby objects.  `AS::MessagePack`
supports several types beyond the base types that `msgpack` supports,
including `Time` and `Range`, as well as Active Support types such as
`AS::TimeWithZone` and `AS::HashWithIndifferentAccess`.

Compared to `JSON` and `Marshal`, `AS::MessagePack` can provide a
performance improvement and message size reduction.  For example, when
used with `MessageVerifier`:

  ```ruby
  # frozen_string_literal: true

  require "benchmark/ips"
  require "active_support/all"
  require "active_support/message_pack"

  marshal_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: Marshal)
  json_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: JSON)
  asjson_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: ActiveSupport::JSON)
  msgpack_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: ActiveSupport::MessagePack)

  ActiveSupport::Messages::Metadata.use_message_serializer_for_metadata = true
  expiry = 1.year.from_now
  data = { bool: true, num: 123456789, string: "x" * 50 }

  Benchmark.ips do |x|
    x.report("Marshal") do
      marshal_verifier.verify(marshal_verifier.generate(data, expires_at: expiry))
    end

    x.report("JSON") do
      json_verifier.verify(json_verifier.generate(data, expires_at: expiry))
    end

    x.report("AS::JSON") do
      asjson_verifier.verify(asjson_verifier.generate(data, expires_at: expiry))
    end

    x.report("MessagePack") do
      msgpack_verifier.verify(msgpack_verifier.generate(data, expires_at: expiry))
    end

    x.compare!
  end

  puts "Marshal size: #{marshal_verifier.generate(data, expires_at: expiry).bytesize}"
  puts "JSON size: #{json_verifier.generate(data, expires_at: expiry).bytesize}"
  puts "MessagePack size: #{msgpack_verifier.generate(data, expires_at: expiry).bytesize}"
  ```

  ```
  Warming up --------------------------------------
               Marshal     1.206k i/100ms
                  JSON     1.165k i/100ms
              AS::JSON   790.000  i/100ms
           MessagePack     1.798k i/100ms
  Calculating -------------------------------------
               Marshal     11.748k (± 1.3%) i/s -     59.094k in   5.031071s
                  JSON     11.498k (± 1.4%) i/s -     58.250k in   5.066957s
              AS::JSON      7.867k (± 2.4%) i/s -     39.500k in   5.024055s
           MessagePack     17.865k (± 0.8%) i/s -     89.900k in   5.032592s

  Comparison:
           MessagePack:    17864.9 i/s
               Marshal:    11747.8 i/s - 1.52x  (± 0.00) slower
                  JSON:    11498.4 i/s - 1.55x  (± 0.00) slower
              AS::JSON:     7866.9 i/s - 2.27x  (± 0.00) slower

  Marshal size: 254
  JSON size: 234
  MessagePack size: 194
  ```

Additionally, `ActiveSupport::MessagePack::CacheSerializer` is a
serializer that is suitable for use as an `ActiveSupport::Cache` coder.
`AS::MessagePack::CacheSerializer` can serialize `ActiveRecord::Base`
instances, including loaded associations.  Like `AS::MessagePack`, it
provides a performance improvement and payload size reduction:

  ```ruby
  # frozen_string_literal: true

  require "benchmark/ips"
  require "active_support/message_pack"

  ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")

  ActiveRecord::Schema.define do
    create_table :posts, force: true do |t|
      t.string :body
      t.timestamps
    end

    create_table :comments, force: true do |t|
      t.integer :post_id
      t.string :body
      t.timestamps
    end
  end

  class Post < ActiveRecord::Base
    has_many :comments
  end

  class Comment < ActiveRecord::Base
    belongs_to :post
  end

  post = Post.create!(body: "x" * 100)
  2.times { post.comments.create!(body: "x" * 100) }
  post.comments.load
  cache_entry = ActiveSupport::Cache::Entry.new(post)

  Rails70Coder = ActiveSupport::Cache::Coders::Rails70Coder
  CacheSerializer = ActiveSupport::MessagePack::CacheSerializer

  Benchmark.ips do |x|
    x.report("Rails70Coder") do
      Rails70Coder.load(Rails70Coder.dump(cache_entry))
    end

    x.report("CacheSerializer") do
      CacheSerializer.load(CacheSerializer.dump(cache_entry))
    end

    x.compare!
  end

  puts "Rails70Coder size: #{Rails70Coder.dump(cache_entry).bytesize}"
  puts "CacheSerializer size: #{CacheSerializer.dump(cache_entry).bytesize}"
  ```

  ```
  Warming up --------------------------------------
          Rails70Coder   329.000  i/100ms
       CacheSerializer   492.000  i/100ms
  Calculating -------------------------------------
          Rails70Coder      3.285k (± 1.7%) i/s -     16.450k in   5.008447s
       CacheSerializer      4.895k (± 2.4%) i/s -     24.600k in   5.028803s

  Comparison:
       CacheSerializer:     4894.7 i/s
          Rails70Coder:     3285.4 i/s - 1.49x  slower

  Rails70Coder size: 808
  CacheSerializer size: 593
  ```

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2023-04-17 11:56:06 -05:00
Jonathan Hefner
0b5505e826 Fix typos for AS::Notifications::Fanout::Handle [ci-skip] 2023-04-16 18:26:50 -05:00
Jonathan Hefner
2f9b55330b Fix monospace formatting [ci-skip]
Follow-up to #47916.

RDoc requires `<tt>` in this case.
2023-04-16 18:15:01 -05:00
Jean Boussier
b71b08722b
Merge pull request #47912 from nashby/delegate-implicit-block
Fix delegation to a method with implicit block.
2023-04-16 13:33:32 +02:00
Étienne Barrié
b3c3bb6792 Configure serialization of metadata per MessageVerifier object 2023-04-15 17:14:58 -05:00
Akshay Birajdar
7da4f7ffd4 Implement #inspect for file & null cache store 2023-04-15 09:09:58 +05:30
Jonathan Hefner
50189c46d3 Simplify reference links [ci-skip]
These references will be properly linked even without the fully
qualified module name.
2023-04-11 16:36:53 -05:00
Jonathan Hefner
2c371c67b7 Linkify code references [ci-skip]
Follow-up to #47354.
2023-04-11 16:28:31 -05:00
Vasiliy Ermolovich
bbb13631f8 Fix delegation to a method with implicit block.
When 8b2f57dc6f was
introduced it broke delegation of a method with implicit block like this:

```ruby
class Foo
  def self.bar
    yield
  end
end

class Bar
  delegate :bar, to: Foo
end

Bar.new.bar { } # raises LocalJumpError: no block given (yield)
```

It happens because it's impossible to detect a method with implicit block and
we rely on method's parameters when we generate delegated methods.

I've found only one solution to this issue: we always generate a method that explicitly accepts
block. It kind of makes sense since evey Ruby method accepts block implicitly anyway.

Fixes https://github.com/rails/rails/issues/47624
2023-04-10 19:28:26 +02:00
Alex Ghiculescu
f4aa7ad9a6 Cache: warning if expires_in is given an incorrect value
In Rails 7, if you do `Rails.cache.write(key, value, expires_in: 1.minute.from_now)`, it will work. The actual expiration will be much more than a minute away, but it won't raise. (The correct code is `expires_in: 1.minute` or `expires_at: 1.minute.from_now`.)

Since https://github.com/rails/rails/pull/45892 the same code will error with:

```
NoMethodError: undefined method `negative?' for 2008-04-24 00:01:00 -0600:Time
    /Users/alex/Code/rails/activesupport/lib/active_support/cache.rb:743:in `merged_options'
    /Users/alex/Code/rails/activesupport/lib/active_support/cache.rb:551:in `write'
```

To make it a bit easier to upgrade to Rails 7.1, this PR introduces a better error if you pass a `Time` object to `expires_in:`

```
ArgumentError: expires_in parameter should not be a Time. Did you mean to use expires_at? Got: 2023-04-07 14:47:45 -0600
    /Users/alex/Code/rails/activesupport/lib/active_support/cache.rb:765:in `handle_invalid_expires_in'
    /Users/alex/Code/rails/activesupport/lib/active_support/cache.rb:745:in `merged_options'
    /Users/alex/Code/rails/activesupport/lib/active_support/cache.rb:551:in `write'
```
2023-04-09 09:57:47 -06:00
Rafael Mendonça França
2f08f9ac2c
Merge pull request #47354 from etiennebarrie/deprecate-ActiveSupport-Deprecation-usage
Deprecate ActiveSupport::Deprecation singleton usage
2023-04-06 10:20:38 -04:00
Petrik de Heus
dc0f20595d
Merge pull request #47717 from p8/docs/include-readmes
Include READMEs in main framework pages of the API documentation
2023-03-30 16:43:14 +02:00
Rafael Mendonça França
ddd6f8ecfe
Merge pull request #47771 from a5-stable/add-more-storage-units
Add zettabyte to `number_to_human_size`
2023-03-29 11:52:45 -04:00
fatkodima
ba2a26e083 Fix Enumerable#in_order_of to preserve duplicates 2023-03-29 15:15:14 +03:00
Rafael Mendonça França
920b930856
Use compute_if_absent to make sure we don't get race conditions 2023-03-28 17:38:45 +00:00
Rafael Mendonça França
689fafa1a1
Merge pull request #47736 from rails/thread-safety
Fix Fanout thread safety
2023-03-28 13:37:55 -04:00
Jean Boussier
49f6870db0
Merge pull request #47788 from ghiculescu/frozen-inflections
Ensure `Inflector` methods return un-frozen strings
2023-03-28 17:36:01 +02:00
Alex Ghiculescu
130c675069 Ensure Inflector methods return un-frozen strings
3d00c8b97f introduced a regression:

```ruby
ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym("API")
end
```

If you have a controller called `APIController`, you may get a crash backtrace that looks like this:

```
FrozenError: can't modify frozen String: "API"
    rails (b8a399cd6ffe) actionpack/lib/action_dispatch/http/request.rb:89:in `controller_class_for'
    rails (b8a399cd6ffe) actionpack/lib/action_dispatch/request/utils.rb💯in `action_encoding_template'
    rails (b8a399cd6ffe) actionpack/lib/action_dispatch/request/utils.rb:85:in `encode'
    rails (b8a399cd6ffe) actionpack/lib/action_dispatch/request/utils.rb:45:in `set_binary_encoding'
    rails (b8a399cd6ffe) actionpack/lib/action_dispatch/http/parameters.rb:68:in `path_parameters='
```

And you can see the issue in action like this:

```ruby
Tanda @ Rails 7.1.0.alpha (test) :004 > "API".freeze.underscore.frozen?
false # this is expected
Tanda @ Rails 7.1.0.alpha (test) :004 > "API".freeze.underscore.camelize.frozen?
false # this is *not* expected, and is what causes the above code to crash
```

I think the correct behaviour is for the inflector to always return non-frozen strings, even if it's using a cached frozen string as is the case in 3d00c8b97f

Other semi-related PRs: https://github.com/rails/rails/pull/41174, https://github.com/rails/rails/pull/41881
cc @amatsuda @byroot
2023-03-28 08:50:54 -06:00
a5-stable
b9a5a4bed8 add zetta to number_to_human_size 2023-03-28 19:50:13 +09:00
zzak
784f1c75a0
Merge pull request #47548 from the-spectator/tagged_logging_example
[ci-skip] Improve tagged logging with block doc
2023-03-28 16:54:55 +09:00
zzak
3d9edd4ced
Merge pull request #47774 from zzak/fix-race-condition-in-evented-file-update-checker
Fix race condition in evented file update checker
2023-03-27 09:49:28 +09:00
zzak
754442a0cb
Use original start instead of restart here 2023-03-27 07:59:06 +09:00
Gert Goet
23674c5853 Bring truncate* docs in line with signatures 2023-03-25 15:49:07 +01:00
Jean Boussier
c650ef4230 Improve reliability of EventedFileUpdateCheckerTest fork test
`Listen.to` starts a bunch of background threads that need to perform
some work before they are able to receive events, but it doesn't block
until they are ready, which expose us to a race condition.

With `wait_for_state(:processing_events)` we can ensure that it's ready on
Linux, however on macOS, the Darwin backend has a second background thread
we can't wait on.

As a workaround we wait a bit after the fork to allow that thread to
reach it's listning state.
2023-03-24 14:24:07 +01:00
Aaron Patterson
ea2870c86e
Fix Fanout thread safety
Fanout is a singleton shared among threads, so all of its hash maps
should be concurrent hash maps otherwise we'll see errors like this:

```
RuntimeError: can't add a new key into hash during iteration
```
2023-03-22 12:49:54 -07:00
Jean Boussier
e55ccf0f10 Mark RaiseWarnings as :nodoc:
I didn't expect RDoc to document this code.
2023-03-22 18:38:23 +01:00
Petrik
7c94708d24 Include READMEs in main framework pages of the API documentation
Currently when opening the main framework pages there is no introduction
to the framework. Instead we only see a whole lot of modules and the
`gem_version` and `version` methods.

By including the READMEs using the `:include:` directive each frameworks
has a nice introduction.
For markdown READMEs we need to add the :markup: directive.

[ci-skip]

Co-authored-by: zzak <zzakscott@gmail.com>
2023-03-21 21:16:28 +01:00
Jean Boussier
210932662b Also undefine with on Symbol 2023-03-16 11:04:19 +00:00
Jean Boussier
dc6bfecdef Undefine with on NilClass and other common immediate types
with isn't usable on immediates, so we might as well undefine the
method in common immediate classes to avoid potential confusion.
2023-03-16 10:29:54 +00:00
Akshay Birajdar
f14cbc5967 [ci-skip] Add yielded logger instance for tagged logging with block example 2023-03-15 01:14:46 +05:30
Étienne Barrié
a7feaaf338 Use the singleton instance as ActiveSupport deprecator 2023-03-14 17:33:48 +01:00
Étienne Barrié
a3061e902e Document deprecators for library authors 2023-03-14 17:33:48 +01:00
Étienne Barrié
c8eeac52f8 Deprecate ActiveSupport::Deprecation.instance 2023-03-14 17:33:48 +01:00
Étienne Barrié
0f0aa86091 Apply instance delegators to application deprecators when available
This restores the 7.0 behavior of the instance delegator impacting all
deprecations from Rails.
2023-03-14 17:33:48 +01:00
Étienne Barrié
e5af9c298a Deprecate ActiveSupport::Deprecation instance delegation
All methods called on ActiveSupport::Deprecation are delegated to the
singleton instance, but now that each framework has its own Deprecation
instance, it's no longer necessary for internal deprecations.

For external code, instead of using ActiveSupport::Deprecation.warn,
they should create their own instance of the class and call warn on it.
2023-03-14 17:32:52 +01:00
Akira Matsuda
217411564e Implement SafeBuffer#bytesplice
[CVE-2023-28120]
2023-03-13 12:13:31 -07:00
Étienne Barrié
33c5a60c40 Deprecate DeprecatedConstantProxy without a deprecator 2023-03-13 13:33:16 +01:00
Étienne Barrié
8f6eda6bfe Deprecate DeprecatedInstanceVariableProxy without a deprecator 2023-03-13 13:33:16 +01:00
Étienne Barrié
83df54cced Deprecate DeprecatedObjectProxy without a deprecator 2023-03-13 13:33:16 +01:00
Étienne Barrié
8171ee0722 Deprecate deprecation assertions without a deprecator 2023-03-13 13:33:16 +01:00
Étienne Barrié
6ce3dd9daf Deprecate deprecate_constant without a deprecator 2023-03-13 13:33:16 +01:00
Étienne Barrié
675cf7928d Deprecate Module.deprecate without a deprecator 2023-03-13 13:32:40 +01:00
Petrik
c3984c250e Fix small typo in Object#With rdoc [ci-skip] 2023-03-06 17:23:37 +01:00
Jean Boussier
1884323574 Implement Object#with
Use case

A very common pattern in Ruby, especially in testing is to save the value of an attribute, set a new value, and then restore the old value in an `ensure` clause.

e.g. in unit tests

```ruby
def test_something_when_enabled
  enabled_was, SomeLibrary.enabled = SomeLibrary.enabled, true
  # test things
ensure
  SomeLibrary.enabled = enabled_was
end
```

Or sometime in actual APIs:

```ruby
def with_something_enabled
  enabled_was = @enabled
  @enabled = true
  yield
ensure
  @enabled = enabled_was
end
```

There is no inherent problem with this pattern, but it can be easy to make a mistake, for instance the unit test example:

```ruby
def test_something_when_enabled
  some_call_that_may_raise
  enabled_was, SomeLibrary.enabled = SomeLibrary.enabled, true
  # test things
ensure
  SomeLibrary.enabled = enabled_was
end
```

In the above if `some_call_that_may_raise` actually raises, `SomeLibrary.enabled` is set back to `nil` rather than its original value. I've seen this mistake quite frequently.

Object#with

I think it would be very useful to have a method on Object to implement this pattern in a correct and easy to use way.

NB: `public_send` is used because I don't think such method should be usable if the accessors are private.

With usage:

```ruby
def test_something_when_enabled
  SomeLibrary.with(enabled: true) do
    # test things
  end
end
```

```ruby
GC.with(measure_total_time: true, auto_compact: false) do
  # do something
end
```

Lots of tests in Rails's codebase could be simplified, e.g.:
  - Changing `Thread.report_on_exception`: 2d2fdc941e/activerecord/test/cases/connection_pool_test.rb (L583-L595)
  - Changing a class attribute: 2d2fdc941e/activerecord/test/cases/associations/belongs_to_associations_test.rb (L136-L150)
2023-03-06 09:02:41 +01:00