Free up #initialize for command subclasses - #167
Conversation
#initialize for command subclasses
Free up `#initialize` in command subclasses so they can use it for their own purposes only. `stdin:`, `stdout:`, and `stderr:` are still injectable dependencies, but these are handled by `.new`, which sets them as instance variables directly, before forwarding all other args onto `#initialize`. This not only reduces boilerplate for authors of command subclasses, but also decreases the risk of bugs. Without this, every command subclass that needs its own initialize would also need to remember to include `**args` and then call `super(**args)`, otherwise the streams would never be set.
9b6d522 to
be06df2
Compare
Do away with protected methods, make `#with_streams` public, and make `#set_Streams` private.
This formalises the behaviour we introduced where the `stderr:`, `stdin:`, `stdout:` args were assigned to ivars without subclass `#initialize` methods needing to worry about receiving those as `**args` and forwarding them to `super`. Now our users can take advantage of this behavior. It’s common for a CLI app to require some “standard” dependencies be provided to every command class through their `#initialize` methods. These can now be set up like so: ``` # In a CLI app’s base command class # `inflector:` is a standard dependency def self.auto_assign_keywords = super + %i[inflector] # Supply default values here private def auto_assign(inflector: Dry::Inflector.new, **kwargs) super(**kwargs) @inflector = inflector end ``` This will make `inflector:` work as an argument to `.new`, and assign its value to an ivar automatically (via `#auto_assign`) before `#initialize` is called.
|
Thanks for checking this out, folks! As it turns out, I discovered an additional need while continuing the hanami-cli updates: making it it possible for users of Dry CLI to specify their own "auto-assigned" keywords like we have here with To support this, I've introduced an "auto-assigned keywords" feature. See it in the last commit above. The lowdown: This feature formalises the behaviour we introduced where the Now our users can take advantage of this behavior. It’s common for a CLI app to require some “standard” dependencies be provided to every command class through their # In a CLI app’s base command class
# `inflector:` is a standard dependency
def self.auto_assign_keywords = super + %i[inflector]
# Supply default values here
private def auto_assign(inflector: Dry::Inflector.new, **kwargs)
super(**kwargs)
@inflector = inflector
endThe above will make We're using this for the def self.auto_assign_keywords = super + %i[fs]
# ...
private
def auto_assign(fs: nil, **kwargs)
super(**kwargs)
@fs = fs
endThis means we don't have to worry about adding This is an example of Dry CLI working as "a framework for a framework", allowing CLI apps to implement their own common baseline behaviour without having to foist any boilerplate on their own internal command classes. |
alassek
left a comment
There was a problem hiding this comment.
I have felt the awkwardness of this problem myself, and I think this is a reasonable approach.
| # A subclass adding its own should add to this list and assign them in {#auto_assign}: | ||
| # | ||
| # ``` | ||
| # def self.auto_assign_keywords = super + %i[inflector] | ||
| # | ||
| # private def auto_assign(inflector: Dry::Inflector.new, **kwargs) | ||
| # super(**kwargs) | ||
| # @inflector = inflector | ||
| # end | ||
| # ``` |
There was a problem hiding this comment.
Since this would be trivial enough to metaprogram auto_assign, is your intention here to make it difficult on purpose to discourage overuse?
Free up
#initializein command subclasses so they can use it for their own purposes only.stdin:,stdout:, andstderr:are still injectable dependencies, but these are handled by.new, which sets them as instance variables directly, before forwarding all other args onto#initialize.Aside from reducing boilerplate for users in their command subclasses, this also decreases the risk of bugs. Without this, every command subclass that adds its own
#initializeneeds to remember to accept**argsand then callsuper(**args), otherwise the streams would never be set.I thought of this change as part of updating hanami-cli to use the new streams provided as part of dry-cli v2. Now that I've made this change here, my next step will be to show what the updated hanami-cli commands look like. Stand by for that, but in the meantime, I'm still interested in feedback on this change in isolation :)