Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Ruby 3 keyword arguments + add method to remove adapter #51

Merged
merged 3 commits into from
Oct 24, 2022

Conversation

Mange
Copy link
Contributor

@Mange Mange commented Jul 7, 2022

What is the purpose of this pull request?

This fixes two bugs I've been having in my project using this.

Removing adapters

The first bug is that code reloading in Rails causes double registration of adapters. Code reloading could be disabled by including the class earlier and/or making the adapter only register once at startup, but then things fail in development while making changes to the class.

My current workaround was to manually access the Isolator.adapters array and removing the entry before calling Isolator.isolate.

Ruby 3 keyword arguments

The second bug came up when upgrading to Ruby 3, as one of my classes that are isolated uses keyword arguments. In Ruby 3 a method like def x(*args) will no longer accept keyword arguments, and the modded method uses a construct like that to catch arguments.

What changes did you make?

I added the following methods:

  • Isolator.has_adapter? - to check if an adapter is there or not. Uses same key normalization logic as isolate.
  • Isolator.remove_adapter - to remove an adapter and restore the modded class.

I changed some tests:

  • Added BDD-like tests for dynamic isolation of custom classes to explain the feature some more.
  • Reworked some isolation tests to rely on less global state in the form of mystery guests.

I changed some behavior:

  • The details_message callback does not require arguments to be accepted anymore, taking only (object).
  • If details_message accepts three arguments, then it gets (object, args, kwargs).
  • For existing codebases where details_message accepts exactly 2 arguments, it will get (object, args + [kwargs]) to be backwards compatible.
  • Removed Isolator.add_patch_method and added a similar private class method instead. I don't think it was intended to be public.

Is there anything you'd like reviewers to focus on?

Restoring logic

Restoring the mod is very difficult since you cannot remove an included/prepended module in Ruby. The closest I could do was to remove the method on the dynamically-generated module so it becomes an empty method that doesn't do anything.
That means that there's no clean restoration, and by checking the ancestor chain you could tell a class was isolated before. If you keep adding and removing isolation on a class it will keep getting more and more empty modules in its inheritance chain.
I regarded this as a small problem, and I suspect that the most common case is for code reloading in development, at which point the classes will also be recreated each time and having heir ancestor chains cleared implicitly.

Please verify this assumption.

Testing structure

The tests I added follow RSpec/BDD style, which most other tests do not. I was very unsure about the style the tests are supposed to be written in. Please let me know if you want them structured differently.

Placement of has_adapter? and remove_adapter

I placed remove_adapter close to isolate since they are a pair, but has_adapter? next to the adapter definition. I'm not sure why isolate is defined in its own module, so I might be placing things in the wrong place.

Is this a good place to have the new methods?

Removal of Isolator.add_patch_method

I don't think this was intended to be a public method. Was it a mistake to remove it?

Bigger PR

Should I split this PR into two smaller ones instead? I joined this up because it followed naturally for me (in order to fix the kwargs issue, I needed tests, and in order to make isolated tests I needed to be able to restore things, etc.).

I'll use my own fork to get my project moving again for now, but if you want two different PRs I will close this and create new branches with more focused changes and open them in series instead.

Checklist

  • I've added tests for this change
  • I've added a Changelog entry
  • I've updated a documentation

Copy link
Owner

@palkan palkan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

LGTM 👍

lib/isolator/isolate.rb Show resolved Hide resolved
Being able to check for and remove adapters is useful since adding an
adapter that is already registered leads to an error.

Removing an adapter will restore the old behavior of the target again.
Ruby 3 no longer captures keyword arguments in `*args`, which meant that
keyword arguments got removed when the method was isolated.

This change works the same in Ruby 2.6, 2.7, and 3.0.

This should not be a breaking change, since all `details_message` procs
that only accept two arguments will get the keyword arguments added as a
hash to the end of the `args` parameter. If you implement a callback
that receives three arguments, then kwargs will be emitted as the third
argument instead.
@Mange
Copy link
Contributor Author

Mange commented Oct 21, 2022

Heh, better late than never. 😅

I also rebased on latest master to remove merge conflicts in the changelog.

@palkan
Copy link
Owner

palkan commented Oct 24, 2022

Thanks!

@palkan palkan merged commit 0127dba into palkan:master Oct 24, 2022
@ghiculescu
Copy link

Is this an issue, or is it intended? #70

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants