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

New link helpers #419

Merged
merged 4 commits into from
Nov 26, 2023
Merged

New link helpers #419

merged 4 commits into from
Nov 26, 2023

Conversation

peteryates
Copy link
Member

@peteryates peteryates commented May 29, 2023

This PR introduces a new set of link helpers that will be available as an option but will eventually become default be the default. The legacy 100% Rails-compatible helpers will still be available for the time being by including the helper module as described below.

The current helpers are closely tied to Rails' implementation which after 18 years is showing its age. Because it pre-dates keyword arguments and can take many formats of link, the code is complex and difficult to extend.

The new implementation:

  • is clean, all of the logic is handled by Rails' class_names helper
  • does minimal argument juggling
  • supports opening links in new tabs
  • does not support the old-style verbose non-resource-orientated govuk_link_to("Show profile", controller: "profiles", action: "show", id: @profile) format. I haven't seen much evidence of this style in use and Rails' documentation advises against using it

Multiple versions of link helpers

For a while there will be two versions of the link helpers:

  1. the new ones GovukLinkHelper
  2. the old ones GovukRailsCompatibileLinkHelper

I originally planned to make choosing between them a config option but due to the order the gem is required and then the intializers run, it's a pain. Instead, the new ones will automatically be included and if the old ones are wanted they can be manually included in ApplicationHelper:

module ApplicationHelper
  include GovukRailsCompatibileLinkHelper
end

This will make the old method variants override the new ones.

I will begin adding some warnings to the old ones before they are dropped.

Final things to do

  • add some tests ensuring additional arguments (both options and html_options) can still be passed through to the Rails helpers
  • visually_hidden_prefix and visually_hidden_suffix keyword arguments?

@netlify
Copy link

netlify bot commented May 29, 2023

Deploy Preview for govuk-components ready!

Name Link
🔨 Latest commit 640125c
🔍 Latest deploy log https://app.netlify.com/sites/govuk-components/deploys/648731e8327eef0009320a77
😎 Deploy Preview https://deploy-preview-419--govuk-components.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@peteryates peteryates force-pushed the new-link-helpers branch 7 times, most recently from 4ea9703 to b0f9d4e Compare June 11, 2023 19:10
@peteryates peteryates marked this pull request as ready for review June 11, 2023 19:12
@frankieroberto
Copy link
Collaborator

@peteryates I tried to look at this diff, but there’s a lot of changes so it's quite hard to follow (at least for me).

Would you mind summarising the main changes to the helpers that this PR proposes? Maybe a before & after would help?

@peteryates
Copy link
Member Author

@frankieroberto yeah, good call. I added a replacement helper that has the same methods in it and I think the fact I changed the order has made the diff more difficult to digest than it should be. I'll see if I can improve it and write a full summary.

Essentially though the old helpers follow a format like this:

  def govuk_link_to(name = nil, options = nil, extra_options = {}, &block)
    extra_options = options if block_given?
    html_options = build_html_options(extra_options)

    if block_given?
      link_to(name, html_options, &block)
    else
      link_to(name, options, html_options)
    end
  end

As you can see here, options and extra_options are both hashes that are 'juggled' depending on whether link text or a block is provided. This makes the code hard to follow, especially once you look in build_html_options which goes to great lengths to merge the provided options with the ones required by the design system.

It's very brittle and caught me out when I tried to add the new_tab functionality as a keyword arg #363 some time ago. @HettieS and I tried the preview release with Register and it broke half the test suite because different versions of Ruby treat keyword args and argument hashes differently - so I retracted it entirely.

This change switches everything over to keyword arguments, allowing the code to be much simpler. Most projects shouldn't notice any change. It might bite some that are using the old style Rails format, but the Rails docs have advised against using them for years and I barely see any in the wild so I don't image it's widespread. They will be able to continue using the old ones for the time being.

fail(ArgumentError, "invalid styles #{invalid_styles.to_sentence}. Valid styles are #{LINK_STYLES.keys.to_sentence}")
def govuk_link_classes(inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false)
if [text_colour, inverse, muted].count(true) > 1
fail("links can be either text_colour, inverse or muted - not combinations of the three")
Copy link
Member Author

@peteryates peteryates Jun 12, 2023

Choose a reason for hiding this comment

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

This could definitely be worded better. Is it even needed at all? 🤔

@frankieroberto
Copy link
Collaborator

@peteryates ah ok, so it’d still be the case that the first argument would be the link text and the second one the path/url, but after that you’d use keyword arguments for any extra attributes or options?

@peteryates
Copy link
Member Author

Yeah - exactly that. Then any GOV.UK specific ones will be picked up and used to assign the relevant classes/attributes and any extra ones (kwargs) will get passed through to the underlying Rails helper.

Essentially it's switching away from having to extract the GOV.UK specific stuff from a generic hash of arguments.

@peteryates
Copy link
Member Author

This is likely to be one of the features of v5.0.0 which will be released following govuk-frontend 5.

Copy link

netlify bot commented Nov 2, 2023

Deploy Preview for govuk-components ready!

Name Link
🔨 Latest commit ceba1d0
🔍 Latest deploy log https://app.netlify.com/sites/govuk-components/deploys/6563817a5d58540008bc6bb5
😎 Deploy Preview https://deploy-preview-419--govuk-components.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@peteryates peteryates mentioned this pull request Nov 16, 2023
@peteryates
Copy link
Member Author

@frankieroberto - in terms of reviewing this, the diff will be confusing because the change moves the old link helpers to a different file and adds new implementations in their place.

The method names are the same so if people want to use the originals they can manually include the module, but the new ones will be default. The code is much cleaner in the new ones so they should be easy to review on their own.

In making this change we lose the old Rails way of creating links, but that's actively discouraged by the Rails docs and you don't really see it any more (although we do have some in ECF!)

@frankieroberto
Copy link
Collaborator

@peteryates thanks, I'll try to review! Would you mind updating the PR description to show what the old and new link syntax looks like? That might make it a bit clearer.

@peteryates
Copy link
Member Author

Have done, and here's a comparison of old vs new:

# old
link_to("Show profile", controller: "profiles", action: "show", id: @profile)

# new (restful)
link_to("Show profile", profile_path(@profile))

@frankieroberto
Copy link
Collaborator

Have done, and here's a comparison of old vs new:

Thanks! I've not seen anyone use the old style for ages! Presumably both examples should be govuk_link_to not link_to?

Will anyone using the old style get an error, or will it just render a link but in an unexpected way?

@peteryates
Copy link
Member Author

Presumably both examples should be govuk_link_to not link_to?

I was just showing the Rails defaults when demonstrating the formats, but I've chagnged it to govuk_link_to in the PR description 👍🏽

Will anyone using the old style get an error, or will it just render a link but in an unexpected way?

They'll get an ArgumentError: unknown keyword: :controller or similar.

Copy link
Collaborator

@frankieroberto frankieroberto left a comment

Choose a reason for hiding this comment

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

Left some comments/suggestions - but feel free to ignore. Looks good!

app/helpers/govuk_link_helper.rb Outdated Show resolved Hide resolved
Comment on lines +6 to +20
def govuk_link_classes(*styles, default_class: "#{brand}-link")
if (invalid_styles = (styles - link_styles.keys)) && invalid_styles.any?
fail(ArgumentError, "invalid styles #{invalid_styles.to_sentence}. Valid styles are #{link_styles.keys.to_sentence}")
end

[default_class] + link_styles.values_at(*styles).compact
end

def govuk_button_classes(*styles, default_class: "#{brand}-button")
if (invalid_styles = (styles - button_styles.keys)) && invalid_styles.any?
fail(ArgumentError, "invalid styles #{invalid_styles.to_sentence}. Valid styles are #{button_styles.keys.to_sentence}")
end

[default_class] + button_styles.values_at(*styles).compact
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should these be private methods? Can't think of why you'd use them outside of a link or button helper.

If not, maybe move them below the other methods, and add a brief comment to explain what they do? It threw me a bit seeing these alongside the more obvious helper methods below.

Copy link
Member Author

Choose a reason for hiding this comment

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

So these are exposed to make it easier for devs to use Rails' other link helpers, like link_to_if and link_to_unless, link_to_unless_current, etc.

Comment on lines +23 to +30
extra_options = options if block_given?
html_options = build_html_options(extra_options)

if block_given?
link_to(name, html_options, &block)
else
link_to(name, options, html_options)
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you can avoid the if conditions here and always pass block on to the rails helpers, as those do their own if block_given? checks?

Suggested change
extra_options = options if block_given?
html_options = build_html_options(extra_options)
if block_given?
link_to(name, html_options, &block)
else
link_to(name, options, html_options)
end
extra_options = options if block_given?
html_options = build_html_options(extra_options) || nil
link_to(name, options, html_options, &block)

Copy link
Member Author

Choose a reason for hiding this comment

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

Going to leave these as they're in the legacy implementation which will soon be dropped.

guide/lib/helpers/class_helpers.rb Outdated Show resolved Hide resolved
describe "#govuk_link_to" do
let(:link_text) { 'Onwards!' }
let(:link_url) { '/some/link' }
let(:link_params) { { controller: :some_controller, action: :some_action } }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Confused by this - thought you’d deprecated this method, and now always require a path to be set instead of setting a controller and action?

Copy link
Member Author

Choose a reason for hiding this comment

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

This covers the old helpers which are still present (in GovukRailsCompatibileLinkHelper) for teams working on codebases that won't support the new style.

These are clean implementations of that significantly simplify the
codebase and allow us to easily support features of the design
system, like opening links in new tabs. The new methods are:

* govuk_link_to
* govuk_button_to
* govuk_button_link_to
* govuk_link_classes
* govuk_button_classes
* govuk_breadcrumb_link_to

They drop support for Rails' legacy-style link building where the
action, controller and id arguments were passed in. Rails discourages
their use in the API docs[0]:

> Because it relies on url_for, link_to supports both older-style
> controller/action/id arguments and newer RESTful routes. Current Rails
> style favors RESTful routes whenever possible, so base your application
> on resources and use

[0] https://api.rubyonrails.org/v7.0/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
Nanoc's link helpers expect classes to be provided in a string but now
we're using arrays internally throughout so it makes sense to use the
Rails version.
Now we're using the new link helper we need to use keyword arguments
instead of hashes or arrays.
Co-authored-by: Frankie Roberto <frankie@frankieroberto.com>
@peteryates peteryates added this pull request to the merge queue Nov 26, 2023
Merged via the queue into main with commit aed6a5e Nov 26, 2023
13 checks passed
@peteryates peteryates deleted the new-link-helpers branch November 26, 2023 17:52
github-merge-queue bot pushed a commit that referenced this pull request Dec 8, 2023
Following the release of [GOV.UK Design System
v5.0](https://github.com/alphagov/govuk-design-system/milestone/16),
govuk-components v5.0.0 will be released. The headline features are:

* support for GOV.UK Design System v5.0
* inclusion of [the task list
component](alphagov/govuk-frontend#2261) #425
* all new link helpers #419
* update header to use new SVG which combines the crown and the GOV.UK
test #466

Release notes will be drafted in #469.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

2 participants