-
Notifications
You must be signed in to change notification settings - Fork 445
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
.with_collection
supports Components with splatted KeywordArgs
#1560
.with_collection
supports Components with splatted KeywordArgs
#1560
Conversation
In a few projects, I wind up using keyword args on a Base class so that I don't have to define an `initialize` on every child class. For example: ```ruby class ApplicationComponent < ViewComponent::Base def initialize(**kwargs) super kwargs.each do |k, v| send(:"#{k}=", v) end end end class ProductComponent < ApplicationComponent attr_accessor :product end class OrderComponent < ApplicationComponent attr_accessor :order end ``` However, ViewComponent doesn't recognize that a keyword args is an implicit `def initialize(product:)` or `def initialize(order:)`; which causes it to raise an `ArgumentError` for an unaccepted parameter. This causes Components with Keyword Args in their initialize to pass the initialze parameter name check. Open Questions: - [ ] Are there side-effects of this I'm not aware of? - [ ] Does this create subtle design pressure that encourages cruft? Thoughts?
Woops, I must have forgotten to include the empty |
.with_collection
supports Components whose initialize
method takes KeywordArgs
a8b54b3
to
47fd21a
Compare
test/sandbox/test/collection_test.rb
Outdated
collection = ComponentWithKeywordArgs.with_collection(@products) | ||
render_inline(collection) |
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.
collection = ComponentWithKeywordArgs.with_collection(@products) | |
render_inline(collection) | |
render_inline(ComponentWithKeywordArgs.with_collection(@products)) |
Can you add an assertion that something is rendered correctly?
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.
Done!
49034fe
to
33a4daf
Compare
👋🏻 it looks like you do have a legit failure in CI ❤️ |
Woops! I ran them locally but must have misread the output. That's what I get for shoveling all my open-source stuff to end-of-business! |
I appear to have forgotten that the with_collection leverages the components name to figure out the name of the argument it uses for the collection; rather than the name of the items in the collection (which is how it works in Rails).
docs/CHANGELOG.md
Outdated
@@ -10,6 +10,10 @@ nav_order: 5 | |||
|
|||
## main | |||
|
|||
* `Component.with_collection` supports components that accept keyword arguments. |
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.
Can this be changed to "accept splatted keyword arguments"? I believe defined keyword args are not a problem.
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 much clearer!
lib/view_component/base.rb
Outdated
@@ -591,7 +591,7 @@ def validate_collection_parameter!(validate_default: false) | |||
parameter = validate_default ? collection_parameter : provided_collection_parameter | |||
|
|||
return unless parameter | |||
return if initialize_parameter_names.include?(parameter) | |||
return if initialize_parameter_names.include?(parameter) || initialize_parameter_names.include?(: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.
Does this imply that the splatted argument has to have the name kwargs
? That is, would this work with def initialize(**other_args)
?
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.
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.
Excellent catch! I've updated to check for :kwrest
in the flattened initialize parameters!
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.
Thanks for making that change.
While working on [supporting splatted keyword args](ViewComponent#1560) I discovered that the linter was failing me on changes I didn't make. When I ran standardrb locally, it was quite content, so I was a bit confused. Then I read [.github/workflows/lint.yml] and realized that view_component runs a `bundle update` prior to running standard; which bumps the standardrb version and the corresponding rubocop set. This wasintroduced by @camertron in [e7eec98](ViewComponent@e7eec98) 7+ months ago to fix an error in CI. It's unclear to me why `bundle install` was not working, so I decided that rather than swapping that back I would trust @camertron's instincts and roll-forward with the update.
While working on [supporting splatted keyword args](ViewComponent#1560) I discovered that the linter was failing me on changes I didn't make. When I ran standardrb locally, it was quite content, so I was a bit confused. Then I read [.github/workflows/lint.yml] and realized that view_component runs a `bundle update` prior to running standard; which bumps the standardrb version and the corresponding rubocop set. This wasintroduced by @camertron in [e7eec98](ViewComponent@e7eec98) 7+ months ago to fix an error in CI. It's unclear to me why `bundle install` was not working, so I decided that rather than swapping that back I would trust @camertron's instincts and roll-forward with the update. I am not including a change to the Changelog because this seems internal and unnecessary to communicate to package consumers.
Once more into the fray! The lint failures appear to be unrelated to my changes; so I made a patch to resolve those as well: #1576 |
.with_collection
supports Components whose initialize
method takes KeywordArgs.with_collection
supports Components whose initialize
method takes splatted KeywordArgs
.with_collection
supports Components whose initialize
method takes splatted KeywordArgs.with_collection
supports Components with splatted KeywordArgs
OK look slike the 3.1 build is not happy will poke at it 🔜 |
While working on [supporting splatted keyword args](#1560) I discovered that the linter was failing me on changes I didn't make. When I ran standardrb locally, it was quite content, so I was a bit confused. Then I read [.github/workflows/lint.yml] and realized that view_component runs a `bundle update` prior to running standard; which bumps the standardrb version and the corresponding rubocop set. This wasintroduced by @camertron in [e7eec98](e7eec98) 7+ months ago to fix an error in CI. It's unclear to me why `bundle install` was not working, so I decided that rather than swapping that back I would trust @camertron's instincts and roll-forward with the update. I am not including a change to the Changelog because this seems internal and unnecessary to communicate to package consumers.
In Ruby 3.1, it appears that the `ViewComponent::Base#initialize` method has an implicit key-rest argument, named `:**`. I thought it may be because we define it with a `def initialize(*); end;` which could be implicitly adding in the rest of the splattable parameter? However, playing around with that syntax doesn't seem to be the cause: ```ruby class A; def initialize(*); end; end class B; end A.instance_method(:initialize).parameters B.instance_method(:initialize).parameters ```
OK, looks like we are back to green on CI; and I have resolved the test failure for ruby 3.1. |
@zspencer can you fix the merge conflicts? ❤️ |
@joelhawksley - I have several times at this point... They'll keep coming up when y'all merge other stuff, so for me to do it feels like a dog chasing its tail a bit. |
@joelhawksley - You'll need to approve it in order for auto-merge to work, I think. |
That'll do it! Thanks @zspencer ❤️ |
While working on [supporting splatted keyword args](ViewComponent#1560) I discovered that the linter was failing me on changes I didn't make. When I ran standardrb locally, it was quite content, so I was a bit confused. Then I read [.github/workflows/lint.yml] and realized that view_component runs a `bundle update` prior to running standard; which bumps the standardrb version and the corresponding rubocop set. This wasintroduced by @camertron in [e7eec98](ViewComponent@e7eec98) 7+ months ago to fix an error in CI. It's unclear to me why `bundle install` was not working, so I decided that rather than swapping that back I would trust @camertron's instincts and roll-forward with the update. I am not including a change to the Changelog because this seems internal and unnecessary to communicate to package consumers.
…wComponent#1560) * Initial support for Components with KeywordArgs In a few projects, I wind up using keyword args on a Base class so that I don't have to define an `initialize` on every child class. For example: ```ruby class ApplicationComponent < ViewComponent::Base def initialize(**kwargs) super kwargs.each do |k, v| send(:"#{k}=", v) end end end class ProductComponent < ApplicationComponent attr_accessor :product end class OrderComponent < ApplicationComponent attr_accessor :order end ``` However, ViewComponent doesn't recognize that a keyword args is an implicit `def initialize(product:)` or `def initialize(order:)`; which causes it to raise an `ArgumentError` for an unaccepted parameter. This causes Components with Keyword Args in their initialize to pass the initialze parameter name check. Open Questions: - [ ] Are there side-effects of this I'm not aware of? - [ ] Does this create subtle design pressure that encourages cruft? Thoughts? * Render nothing just to get past the explode boom * Lint lint lint * Standardrb vs Ruby 2.5: FIGHT! * Add Changelog per CI * Update docs/CHANGELOG.md Co-authored-by: Joel Hawksley <[email protected]> * Assert rendering against the collection works * The Kwarg is the Component name 🙈 I appear to have forgotten that the with_collection leverages the components name to figure out the name of the argument it uses for the collection; rather than the name of the items in the collection (which is how it works in Rails). * Clarify that this is for Splatted Keyword Args Co-authored-by: [email protected] * Keyword argument detection is more resilient * Don't count un-named splatted keyword args In Ruby 3.1, it appears that the `ViewComponent::Base#initialize` method has an implicit key-rest argument, named `:**`. I thought it may be because we define it with a `def initialize(*); end;` which could be implicitly adding in the rest of the splattable parameter? However, playing around with that syntax doesn't seem to be the cause: ```ruby class A; def initialize(*); end; end class B; end A.instance_method(:initialize).parameters B.instance_method(:initialize).parameters ``` * Make it actually private Co-authored-by: Joel Hawksley <[email protected]>
While working on [supporting splatted keyword args](ViewComponent#1560) I discovered that the linter was failing me on changes I didn't make. When I ran standardrb locally, it was quite content, so I was a bit confused. Then I read [.github/workflows/lint.yml] and realized that view_component runs a `bundle update` prior to running standard; which bumps the standardrb version and the corresponding rubocop set. This wasintroduced by @camertron in [e7eec98](ViewComponent@e7eec98) 7+ months ago to fix an error in CI. It's unclear to me why `bundle install` was not working, so I decided that rather than swapping that back I would trust @camertron's instincts and roll-forward with the update. I am not including a change to the Changelog because this seems internal and unnecessary to communicate to package consumers.
…wComponent#1560) * Initial support for Components with KeywordArgs In a few projects, I wind up using keyword args on a Base class so that I don't have to define an `initialize` on every child class. For example: ```ruby class ApplicationComponent < ViewComponent::Base def initialize(**kwargs) super kwargs.each do |k, v| send(:"#{k}=", v) end end end class ProductComponent < ApplicationComponent attr_accessor :product end class OrderComponent < ApplicationComponent attr_accessor :order end ``` However, ViewComponent doesn't recognize that a keyword args is an implicit `def initialize(product:)` or `def initialize(order:)`; which causes it to raise an `ArgumentError` for an unaccepted parameter. This causes Components with Keyword Args in their initialize to pass the initialze parameter name check. Open Questions: - [ ] Are there side-effects of this I'm not aware of? - [ ] Does this create subtle design pressure that encourages cruft? Thoughts? * Render nothing just to get past the explode boom * Lint lint lint * Standardrb vs Ruby 2.5: FIGHT! * Add Changelog per CI * Update docs/CHANGELOG.md Co-authored-by: Joel Hawksley <[email protected]> * Assert rendering against the collection works * The Kwarg is the Component name 🙈 I appear to have forgotten that the with_collection leverages the components name to figure out the name of the argument it uses for the collection; rather than the name of the items in the collection (which is how it works in Rails). * Clarify that this is for Splatted Keyword Args Co-authored-by: [email protected] * Keyword argument detection is more resilient * Don't count un-named splatted keyword args In Ruby 3.1, it appears that the `ViewComponent::Base#initialize` method has an implicit key-rest argument, named `:**`. I thought it may be because we define it with a `def initialize(*); end;` which could be implicitly adding in the rest of the splattable parameter? However, playing around with that syntax doesn't seem to be the cause: ```ruby class A; def initialize(*); end; end class B; end A.instance_method(:initialize).parameters B.instance_method(:initialize).parameters ``` * Make it actually private Co-authored-by: Joel Hawksley <[email protected]>
@@ -636,6 +636,11 @@ def iteration_argument_present? | |||
|
|||
private | |||
|
|||
def splatted_keyword_argument_present? | |||
initialize_parameters.flatten.include?(:keyrest) && | |||
!initialize_parameters.include?([:keyrest, :**]) # Un-named splatted keyword args don't count! |
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.
Is there a specific reason for not accepting unnamed splatted arguments? This limitation creates some problems with initializers, which forward their arguments to helpers, e.g. dry-initialize, which uses def initialize(...); __dry_initializer_initialize__(...); end
(...
= actual Ruby code) or any kind of custom helper, like def initialize(**); do_init(**); end;
(Sorry for "gravedigging", I'll open a separate issue, if that is more appropriate...)
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 can't remember my exact reasoning, but it was more a limitation-in-my-capacity and not necessarily a limitation-by-design.
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.
@daniel-rikowski @zspencer can we please open up a new issue where this can be discussed further
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'd also be interested in a PR with a failing test for the use case you want 😄
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.
What are you trying to accomplish?
I'd like to be lazy and not define
initialize
on every view component when they inherit from a parent class that accepts keyword arguments in order for the component to work with `with_collection.For example:
However, ViewComponent doesn't recognize that a keyword args is an implicit
def initialize(product:)
ordef initialize(order:)
; which causes it to raise anArgumentError
for an unaccepted parameter.What approach did you choose and why?
I made it so Components with Keyword Args in their initialize pass the initialze parameter name check. This seemed like a small enough change?
Anything you want to highlight for special attention from reviewers?=