-
-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Pass keyword dependencies if super constructor accepts arguments with the splat operator #48
Conversation
8c0efd1
to
8f864ec
Compare
@timriley surprisingly, it works :) I created a dedicated class for handling method parameters so it's easier to track intentions throughout the code |
8f864ec
to
575c94a
Compare
Hah, aaaand I was wrong. I have rails controllers with injections and this doesn't work anymore. The case looks like this module SomeMixin
def initialize(*)
super
end
end
class BaseClass
def initialize
#
end
end
class ChildClass < BaseClass
include SomeMixin
end
class MyAppClass < ChildClass
include ImportModule['dependency']
end Personally, I don't care, I can easily work around it with adding a mixin to ChildClass.include Module.new { def initialize; super end } However, this means the change is not backward compatible. |
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.
@flash-gordon This is good stuff, thanks for putting the time and thought into it!
I'm looking at that rails controller meta-example of yours and trying to work out exactly why it's failing... is it because it ends up trying to pass the dependencies upwards into a place where they're not wanted/expected (due to the SomeMixin#initialize(*)
)? I'm interested, which #initialize
params signature in the chain actually causes things to break?
Either way, if this is due to Rails controllers working in some non-conventional way, I agree: I don't think we should bend over backwards to support them. If it turned out lots of people wanted easier support at some case, we could ship a special strategy in a companion gem or something.
Left a couple of code questions for you, otherwise.
super() | ||
end | ||
RUBY | ||
instance_mod.class_exec(dependency_map) do |dependency_map| |
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.
Do we actually need to pass dependency_map
into class_exec and its block if we're not explicitly using it there? With this new factoring it seems we're using it via the assign_dependencies
and slice_kwargs
methods made available to that block through regular closure behaviour?
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.
nice catch, fixed
end | ||
RUBY | ||
instance_mod.class_exec(dependency_map) do |dependency_map| | ||
define_method :initialize do |**kwargs| |
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 previously here we were building an initialize method with a full set of named keyword params, and now we're just accepting a kwsplat. I just wanted to check, @flash-gordon, was this change just so we could reuse the assign_dependencies
/slice_kwargs
helpers across both initialise-builders here, or was there some other reason specifically for making this version of #define_initialize_with_keywords
work?
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.
exactly this, otherwise we would come up with two define_initialize_with_keywords
with unclear differences between them. Now we at least have east-to-grasp logic there
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.
Yep, I think clarity of internal implementation is probably the better thing to optimise for here 👍
@@ -26,6 +26,9 @@ def self.remove_constants | |||
end | |||
|
|||
RSpec.configure do |config| | |||
config.disable_monkey_patching! | |||
config.filter_run_when_matching :focus |
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.
This actually caused an error for me:
undefined method `filter_run_when_matching' for #<RSpec::Core::Configuration:0x00007fe0062dfb48> (NoMethodError)
Did you mean? filter_run_excluding
Perhaps we should specify a particular version of rspec in our Gemfile
?
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.
That's surprising. Fixed this by raising the rspec version in gemspec.
|
||
it "passes dependencies assuming the parent class can take them" do | ||
instance = child_class.new | ||
|
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.
Perhaps we could add this to make it clear the standard behaviour is still in place?
expect(instance.one).to eq "dep 1" |
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.
✅
lib/dry/auto_inject.rb
Outdated
def self.super_method(klass, method) | ||
method = klass.instance_method(method) | ||
method unless method.owner.equal?(klass) | ||
def self.super_parameters(klass, method_name) |
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.
I love this! (and MethodParameters
too!)
@timriley the reason it failed is the mixin is a url helper, it throws in some instance variables in the constructor and doesn't really care about its arguments, hence I think it's a valid use case but at the same it's quite easy to work around the issue with having include Module.new {
def initialize(*)
super()
end
} in |
@flash-gordon I wonder if we could actually fix this by making the opening of Kwarg's super_parameters = Dry::AutoInject.super_parameters(klass, :initialize).each do |ps|
# Look upwards past `def foo(*)` methods until we get an explicit list of parameters
break ps unless ps.pass_through?
end Wouldn't that allow us to see through your mixin's initializer to the one with 0 arity and then decide to pass nothing up to it? |
@timriley that's a very good point, I'll try it out |
This also * bumps the minimal required ruby version to 2.2.0 because of using Method#super_method * fixes the behavior of AutoInject.super_parameters (renamed to MethodParameters.of), it didn't work properly. I think, I unlearned how to write loops :( * adds specs for MethodParameters.of
build fails on 2.4.3 but works on 2.4.5 🤔🤔🤔 |
https://bugs.ruby-lang.org/issues/13973 the fix is in ruby 2.4.4 and above, I'll make a workaround for lower versions |
done, this somehow fixed jruby as well 🤷♀️ |
This was broken in d330299 but works again now.
This improves compatibility when e.g. test doubles are passed as dependencies, which in the context of the test may not appreciate having extra methods like `#nil?` called on them.
@flash-gordon Have tested this in my current app here and it's working great. I hope you don't mind, but I've just pushed a couple of extra commits here which will allow this branch to properly close off a few bug reports. 6d82cf8 adds test to demonstrate we resolve #46 as part of your changes here. a49c1cc tweaks the conditional assignment of deps just a little (made possible now we're just taking a |
Have updated the description to note those issue resolutions there too. Happy for you to merge this, @flash-gordon! Thanks! 🎉 |
This is outstanding work @flash-gordon <3 |
Now the keyword strategy will pass dependencies to an existing
initialize
method if it accepts an arbitrary number of arguments. This enables seamless integration with ROM's repositories. The required changes in ROM have been applied already so we're going to have it in ROM 5.Resolves #46, Resolves #49