-
-
Notifications
You must be signed in to change notification settings - Fork 395
Implement MatcherDelegator using the BlankSlate pattern #1434
Implement MatcherDelegator using the BlankSlate pattern #1434
Conversation
Fails even on 2.2, but we don’t care much, since Rails 7.1 is Ruby 2.7+, right? Please don’t hesitate to ping if you need me to approve CI. |
Thanks @pirj ! (Actually I'm now thinking I may be able to open a PR on my fork to get CI running without approval 🤦 ).
Well, Rails 7.2 is what trigger the issue at hand, but the same kind of issue can be caused by applications monkey patches, etc. And ideally I'd avoid too big of a |
Oh, interesting, you couldn't copy methods in modules pre 2.2:
I suppose the solution is to go with the "BlankSlate" pattern for all versions, that also avoid running into constant resolution issues. |
83bdc24
to
bb9fb97
Compare
This allows to minimize the amount of method inherited by the delegator object, and protects RSpec from new methods being introduced by future Ruby versions or monkey patched in by user code or libraries. On modern Rubies there is `BasicObject` for this purpose, that's what `delegate.rb` uses for instance, and is considered best practice for delegators and other proxy objects. However since RSpec still support Ruby 1.8 so it's not an option. Additionally `BasicObject` doesn't inherit from `Object` so all constant resolutions in a `BasicObject` descendant must be fully qualified which is tedious, but more importantly changing this now would probably break third party code that inherit from `MatcherDelegator`.
d74e12b
to
3d97c7e
Compare
Ok, I pushed a new version that passes CI on my fork except for the |
:__id__, :__send__, :object_id, | ||
|
||
# Methods that are explicitly undefined in some subclasses. | ||
:==, :===, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I had to keep these two only to not break:
# Decorator used for matchers that have special implementations of
# operators like `==` and `===`.
# @private
class AliasedMatcherWithOperatorSupport < AliasedMatcher
# We undef these so that they get delegated via `method_missing`.
undef ==
undef ===
end
I think we could remove them from BaseDelegator
and just make AliasedMatcherWithOperatorSupport
and empty class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
===
is the base method matchers use to match on, so if they are explicitly undefined its likely to fall back to delegated ones
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I meant by this comment, is that I left them in the BlankSlate to minimize the diff.
But IMO they should be removed from the BlankSlate and AliasedMatcherWithOperatorSupport
should stop undefining them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:class, :respond_to?, :__method__, :method, :dup, | ||
:clone, :initialize_dup, :initialize_copy, :initialize_clone, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that these are the methods required to pass the test suite, they don't all necessarily make sense to keep. e.g. method
for instance is redundant with __method__
and a cleanup of the test suite would allow to get rid of it.
Also, I'm unfamiliar with RSpec, but if there are other delegators of the same type, it may make sense to proactively convert them to a similar pattern. |
So it fails on that branch too, but I still think it's unrelated.
This happens because before cherry-picking things inside Active Support you must first I'll open another PR there, but In the meantime I think this one is ready. |
Upstream PR: ruby-protobuf/protobuf#431 |
@pirj anything else I can do? |
👋 any news here? |
Thanks for this, I think this is a good improvement for us as a protection against monkey patching Object, I'm not quite sure when I'll release this yet as I'm debating the level of change this justifies and reflecting on where we could extend this logic too. (Apologies for the delayed merge then delayed comment, I was originally letting Phil handle it whilst being busy with |
This allows to minimize the amount of method inherited by the delegator object, and protects RSpec from new methods being introduced by future Ruby versions or monkey patched in by user code or libraries.
On modern Rubies there is
BasicObject
for this purpose, that's whatdelegate.rb
uses for instance, and is considered best practice for delegators and other proxy objects. However since RSpec still support Ruby 1.8 so it's not an option.Additionally
BasicObject
doesn't inherit fromObject
so all constant resolutions in aBasicObject
descendant must be fully qualified which is tedious, but more importantly changing this now would probably break third party code that inherit fromMatcherDelegator
.