Skip to content
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

Add strict helpers #1987

Open
wants to merge 25 commits into
base: introduce-component-local-config
Choose a base branch
from

Conversation

reeganviljoen
Copy link
Collaborator

closses #1976

What are you trying to accomplish?

Add a config.view_component.strict_helpers_enabled mode to view component which will throw an ViewComponent::StrictHelperError when helpers.<some_helper> is used.

This

What approach did you choose and why?

Anything you want to highlight for special attention from reviewers?

Copy link
Member

@joelhawksley joelhawksley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thought on naming.

As a bigger question: what happens if one turns this on for their app and then wants to use a gem that provides ViewComponents that use helpers?

@@ -224,7 +232,7 @@ def controller
# @return [ActionView::Base]
def helpers
raise HelpersCalledBeforeRenderError if view_context.nil?

raise StrictHelperError if ViewComponent::Base.config.strict_helpers_enabled
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this feature might be less confusing if we just had it be config.helpers_enabled and defaulting it to true?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joelhawksley I think that's a good idea, when I have some time I will apply this feedback

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joelhawksley I have changed the config to this, if you could review it again it would be a massive

@joelhawksley
Copy link
Member

@reeganviljoen thoughts on my comment from my review above?

As a bigger question: what happens if one turns this on for their app and then wants to use a gem that provides ViewComponents that use helpers?

@reeganviljoen
Copy link
Collaborator Author

reeganviljoen commented Mar 6, 2024

One thought on naming.

As a bigger question: what happens if one turns this on for their app and then wants to use a gem that provides ViewComponents that use helpers?

@joelhawksley I don't think this will break any existing gems, unless the user enables the config, however if the user does, it might not work unless the gem documents which helpers are used so the user can add them themselves with the use_helpers method

I do think this is a positive improvement however, as just adding helpers increases coupling to global state, which I feel is bad and is against the whole point of view_component. If a gem wants to add helpers they should let the user decide what helpers and how much coupling to global state they want

@boardfish
Copy link
Collaborator

boardfish commented Mar 7, 2024

If a gem providing ViewComponents uses helpers, I'm not sure we should mandate that it's subject to strict_helpers in the same way as the rest of the app.

Gems providing components that use helpers would need to be updated to use the new syntax, and waiting on providers to do that doesn't feel like an ideal experience for devs using ViewComponent to me. It's absolutely something gem maintainers should do post-haste, but I think configuration could make the transition period easier for consumers of these gems.

I can see us going a few different ways with this:

  1. Gems adhere to their own configuration. I don't think there's a layer of config in place to do this just yet – gems still inherit from app config.
  2. Enabling strict_helpers enforces this for gems too. Making this available as an option would be good, but I don't think it should be the only one.
  3. We expose a lambda for strict_helpers. This could allow folks the flexibility to allowlist on a gem-by-gem basis, or report strict helper violations (e.g. to an error reporting platform/in logs). I'd like to see this become something we do more often potentially. If we introduce configuration that controls a breaking change, this would be good to allow folks to understand (through their test suite or at runtime) where changes need to be made.

I'm sure there are options beyond that, but that's what comes to mind right now. Any thoughts?

@joelhawksley
Copy link
Member

@boardfish thanks for sharing your concerns- I generally agree with you.

I think we might be better off having this be a macro folks can set on a component, likely on their ApplicationComponent.

@reeganviljoen
Copy link
Collaborator Author

reeganviljoen commented Mar 14, 2024

@joelhawksley @boardfish noted, I wills see if I can work on something to macro based system working

@danini-the-panini
Copy link

danini-the-panini commented Jul 4, 2024

I think we might be better off having this be a macro folks can set on a component, likely on their ApplicationComponent.

I am on board with this ☝🏻

Could we also allow it to be optionally a deprecation warning instead of an error, that way it won't break things but it will give devs the tool to migrate their existing components. This could be useful for e.g. very large code bases where it is infeasible to migrate all components at once

@danini-the-panini
Copy link

Another useful feature might be allowing to pass in a block that gets called every time helpers is referenced, so that devs can hook into their error tracker e.g. Sentry

@joelhawksley
Copy link
Member

@reeganviljoen just checking in- are you planning to complete this work?

@reeganviljoen
Copy link
Collaborator Author

@joelhawksley Absolutely, just been verry busy

@reeganviljoen
Copy link
Collaborator Author

@joelhawksley what do you think about @danini-the-panini comment, I think its a good idea, I could work that into our current instrumentation setup in a seperate PR

@reeganviljoen
Copy link
Collaborator Author

reeganviljoen commented Aug 7, 2024

@joelhawksley I think that this api is complete as the main concern was gem compatibility but the recent addition to use_helpers provides a path to working. with gems in a strict mode

@joelhawksley
Copy link
Member

@reeganviljoen thanks for cleaning up this PR. For now, I'm going to mark it as blocked on @boardfish's work here: #1945 (comment) ❤️

@boardfish
Copy link
Collaborator

This might actually be ideal as the first component-local config option due to the small scope of the PR. I'll see if I can grab some time this weekend to figure out if I can use this to make a start.

@boardfish
Copy link
Collaborator

Had a very scrappy crack at this in 35017a9 – it looks possible, and we could probably even ship that with a few more tests behind it and some cleanup (still need to document things and get the use of the private variable names vs. public method names straight). Going forward, though, we probably want to make a nice internal DSL that'll support migrating these config options across slowly but surely.

@reeganviljoen
Copy link
Collaborator Author

@boardfish I aprreciate the help, if you want to pair on this issue I be happy to help.

@boardfish
Copy link
Collaborator

There's perhaps an even cleaner solution in ActiveSupport::Configurable that I've now found - we were previously including this but hadn't perhaps used it to its full extent as far as I'm aware. I've got a busy week ahead but I'd like to look at wrapping it up as soon as I can to unblock you - I'd appreciate any early feedback anyone has on the PR.

@reeganviljoen
Copy link
Collaborator Author

reeganviljoen commented Oct 8, 2024

@boardfish I have rebased of of #2124 and made that the base branch for this to make sure there is no weird diff issues

@reeganviljoen reeganviljoen changed the base branch from main to introduce-component-local-config October 8, 2024 18:50
@boardfish
Copy link
Collaborator

boardfish commented Oct 9, 2024

Awesome ✨ In #2092, the changes to Base#inherited introduce component-local config – if you add those to this PR, where you're currently checking the helpers_ config options against ViewComponent::Base, you should instead be able to use component_config on the component instance. You'll also need to update some of the tests so that they don't stub the global config and instead use the value from the component's local config.

@boardfish boardfish force-pushed the introduce-component-local-config branch from 161cee3 to 6378768 Compare October 10, 2024 11:18
@boardfish
Copy link
Collaborator

boardfish commented Oct 11, 2024

On reflection, I'm thinking that the implementation I've got here (to be included in this branch) probably might need to change a touch – that makes all global options available on component-local config, when in reality changing any settings other than the ones related to helpers won't do anything. I expect that might cause some confusion if left in.

Perhaps it would be good to have a component_defaults key in the global config which things that inherit from ViewComponent::Base will use an inheritable_copy of instead.

BlakeWilliams and others added 5 commits October 11, 2024 09:27
* Fix duplicate template error bug

This change fixes the bug reported where a component will report that it
has both a template and a `call` method defined despite only having an
HTML template.

I was able to reproduce this bug by pulling in Avo (like reported in the
initial bug report) and running `vegeta` (to trigger parallel requests
since I had a hunch this was a race condition):

```
echo "GET http://localhost:3000/avo/resources/cars/1" | vegeta attack -duration=2s
```

This reliably reproduced the error:

```
ActionView::Template::Error (Template file and inline render method found for Avo::CoverPhotoComponent. There can only be a template file or inline render method per component.
Template file and inline render method found for variant '' in Avo::CoverPhotoComponent. There can only be a template file or inline render method per variant.
Templates:
```

I added _a lot_ of debug logs to understand the race condition that I
thought was occurring, and realized that multiple threads are calling
`gather_templates` _and_ mutating the `@templates` array at the
same.When looking at the old compiler code and realized that this likely
isn't new behavior. This led the investigation towards how we collect
and surface errors or otherwise might modify templates. It turns out
there's a difference in the new and old compiler code after the
refactor:

```ruby
\# old
def template_errors
  @__vc_template_errors ||=

\# new
def gather_template_errors(raise_errors)
  errors = []
```

_We're not memoizing the errors like we used to_. This is more correct
behavior, but explains how a race condition would make this error case
much more difficult to occur in older versions of the compiler.

This change brings us back to the old behavior by memoizing the errors
we collect in `gather_template_errors` but the `@templates` ivar is
still being mutated. I don't want to change _too much_ in this PR, but a
subsequent change might be wrapping the entire `compile` method in the
`redefinition_lock` (and renaming it to `compile_lock`) to avoid similar
issues in the future.

I did not include a test because it's dfficult to reproduce this race
condition reliably in the test environment.

I believe this _should_ close out ViewComponent#2114

* Fix tests, raise errors for compiled components with errors

* There isn't always a default template when there are errors.

* Make standard happy

ugh

* Remove debug code

* Fail compilation when errors present

* Revert default template change

* Add informative comment

* Add changelog

---------

Co-authored-by: Joel Hawksley <[email protected]>
@boardfish boardfish force-pushed the rv-add-strict-helpers branch 3 times, most recently from 70bed0a to dbfc516 Compare October 11, 2024 08:44
@boardfish
Copy link
Collaborator

Made those changes myself, but there are a few icky commits left in – I figure that if the base PR is merged, those will be easier to clean up.

@reeganviljoen
Copy link
Collaborator Author

@boardfish much thanks will take a look tonight

@boardfish boardfish force-pushed the introduce-component-local-config branch 2 times, most recently from e9592cb to 37e6d0d Compare October 20, 2024 09:37
@boardfish boardfish force-pushed the introduce-component-local-config branch 2 times, most recently from 2dcc88d to 62e0960 Compare October 21, 2024 10:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants