-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Recommend calling super in initialize #809
Comments
@headius Sounds good to me. Would you like to formulate the guideline yourself? |
Sure I'll see if I can put together a PR. |
👍 Please extend this rule to |
And maybe a note that typically |
What about when you explicitly are not wanting to invoke the superclass's behavior? |
Do you have a good example where that would be the case? As with all rules, some can be bent or broken, but as a general principle I would imagine this would be due to a weakness of the superclass... |
@marcandre I would agree that using inheritance improperly could be one such reason not to call the parent initializer. Brevity is also a reason. There is no sense in calling super on a subclass of ViewComponent::Base because it doesn't do anything: https://github.com/github/view_component/blob/master/lib/view_component/base.rb#L98 |
@jcoyne Indeed, this method has no body. Yet. By not wanting to invoke I still think it's a good rule of thumb (and especially in external dependencies like |
there is a super common pattern that breaks this purpose: # frozen_string_literal: true
class ApplicationService
def self.call(*args, **kwargs, &block)
new(*args, **kwargs, &block).__send__ :perform
end
end # frozen_string_literal: true
class SomeService < ApplicationService
def initialize(a_param:)
@a_param = a_param
end
private
def perform
puts "I'm an useless service that puts the param: #{@a_param}"
end
end SomeService.call(a_param: 'something') Why would I like to call super in SomeService initializer? EDIT: fortunately all my services objects are inside app/services so I added this to rubocop.yml Lint/MissingSuper:
Exclude:
- 'app/services/**/*' |
This is required to make the latest version of standard happy See rubocop/ruby-style-guide#809 and rubocop/rubocop#8376 for context
* Show less months in the dropdown * A bit less top padding for charts * tests for to_s * Adjust month formatting to Mon-Year * Update standard, apply fixes that can auto apply * Ensure we call super so parent classes can initialize This is required to make the latest version of standard happy See rubocop/ruby-style-guide#809 and rubocop/rubocop#8376 for context * More linting fixes * Bump cache versions Just incase the date formatting change happens to mess w/ our cache keys * Fix the keys to match the new period formatting * Updated overview card chart spacing Co-authored-by: claudiovallejo <[email protected]>
@silva96 In this particular example, it does nothing and perhaps the super call is not needed. The problem is that you don't know this is the case if the superclass is part of another library or maintained by someone else. The base class may add some initialization logic in the future that would not get run by your subclass. It is worth pointing out that the Java language has required all constructors to call a superconstructor since the beginning for exactly this reason: not calling the superclass constructor breaks the guarantee that it will run when instances of that class or its subclasses are instantiated. Java code requires that the subclass invoke a superclass constructor directly, or else a call to the superclass's default (no-args) constructor will be inserted for you. |
Good Point |
We need to slurp all remaining options in the constructor, so that we can pass them on to the wrapper tag. However, ViewComponent::Base#initialize is defined as def initialize(*); end which messes up keyword argument slurping using the double splat operator. Given the following component constructor def initialize(foo:, **rest) super p rest end I'd expect the behavior to be ThatComponent.new(foo: 1) # {} given that the foo keyword argument is explicitly defined. However the actual behavior is ThatComponent.new(foo: 1) # {:foo => 1} If we don't call super we get the expected behavior: def initialize(foo:, **rest) p rest end ThatComponent.new(foo: 1) # {} However, not calling super is a bit of an anti-pattern (as discussed in rubocop/ruby-style-guide#809), so it would be beneficial to be able to call it still. We cannot get by with slurping unknown keyword arguments before calling super, though: def initialize(foo:, **rest) p rest super p rest end somehow still messes with the local variable: ThatComponent.new(foo: 1) # {} # {:foo => 1}
Many users forget to call
super
frominitialize
, which can lead to tricky bugs if the superclass needs to initialize its own state.Perhaps this should be in the style guide?
I'm not sure if it should be a hard failure, since classes that extend Object or BasicObject or a few other core types don't technically need to invoke
super
, but the safest pattern would be to always do so.The text was updated successfully, but these errors were encountered: