Error when a parent class recieves arbitrary number of arguments and a parent of the parent is not
flash-gordon opened this issue · comments
It's better to show on modules actually:
module Shiny
def initialize(*)
super
@foo = 1
end
end
Inject = Dry::AutoInject(one: 1)
class Foo
include Shiny
include Inject[:one]
end
Foo.new
__END__
[8] pry(main)> wtf?
Exception: ArgumentError: wrong number of arguments (given 1, expected 0)
--
0: (pry):3:in `initialize'
1: (pry):3:in `initialize'
2: /Users/gordon/dev/dry-auto_inject/lib/dry/auto_inject/injection.rb:123:in `initialize'
@timriley do you think the case is valid? I think about removing all injected arguments prior to call super method as a default behavior, what do you think?
Oh, and one more issue. Currently .new
accepts arbitrary number of arguments but silently ignores superfluous.
@flash-gordon Very interesting! I was about to say that this is probably not about, that Shiny#initialize
should have some understanding of its circumstances and pass the appropriate args to super (or no args, via super()
).
However, I did notice that Shiny#initialize
's parameters is different to what I've been checking for so far. It returns [[:rest]]
, instead of [[:rest, :args]]
, which is what you get for a method that accepts a named splat argument like *args
.
When there's no name for the splat, then it's clear the method has no intention to use them, so I think we should probably handle this case specially. When we know the super #initialize
has *
args, then we should probably just pass none of the dependencies to it, the same as this case. What do you think?
Yes, I thought about it and it does the job in that case, we can be sure that Shiny won't do super()
because it makes no sense as far as I can imagine. But the issue can be also resolved with something like this:
@@ -67,7 +67,7 @@ module Dry
def self.new(*args)
names = #{dependency_map.inspect}
deps = names.values.map.with_index { |identifier, i| args[i] || container[identifier] }
- super(*deps)
+ super(*deps, *args[deps.size..-1])
end
RUBY
end
@@ -110,12 +110,10 @@ module Dry
# @api private
def define_constructor_with_args(klass)
super_method = Dry::AutoInject.super_method(klass, :initialize)
- super_params = if super_method.nil? || super_method.parameters.empty?
+ super_params = if super_method.nil?
''
- elsif super_method.parameters.any? { |type, _| type == :rest }
- '*args'
else
- "*args[0..#{super_method.parameters.length - 1}]"
+ "*args[#{dependency_map.size}..-1]"
end
I suggest it because I see no reason why parent class should know
about dependencies of child classes. Could you explain the reason to me because auto-inject is very awesome and I want to use it properly :)
It's worth to mention about one more case:
[1] pry(main)> Inject = Dry::AutoInject(one: 1, two: 2)
=> #<Dry::AutoInject::Injector:0x007f999281eda8 @container={:one=>1, :two=>2}, @options={}>
[2] pry(main)> class Foo
[2] pry(main)* include Inject[:one]
[2] pry(main)* end
=> Foo
[3] pry(main)> class Bar < Foo
[3] pry(main)* include Inject[:two]
[3] pry(main)* end
=> Bar
[4] pry(main)> Bar.new
=> #<Bar:0x007f9993321de0 @one=2, @two=2>
[5] pry(main)>
:)
BTW it came to my mind that we can provide different strategies for sequential parameters.