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

Allow Turbo Stream actions to be provided as keyword arguments #374

Conversation

marcoroth
Copy link
Member

@marcoroth marcoroth commented Aug 22, 2022

This pull request allows methods within the Turbo::Streams::TagBuilder class to be used with keywords arguments. This also allows users to build helpers for custom action helpers when the target is not required, as seen in #373.

It also adds tests cases for all seven default actions and the action and action_all helper methods to make sure we support all possible variants.

The amount of times I've seen people struggle with the target and targets attribute is super high. I'm also running into it from time to time. I always found it confusing to have a regular action helper for target and a *_all method for targets.

What this pull request proposes is to use keywords argument to make it more readable and clear which attribute is being used.

turbo_stream.append(target: "messages", content: "Append")
turbo_stream.append(targets: "messages", content: "Append")

# => <turbo-stream action="append" target="messages"><template>Append</template></turbo-stream>
# => <turbo-stream action="append" targets="messages"><template>Append</template></turbo-stream>

This change will not break backwards compatibility, as it accounts for the arguments to be passed as positional or keyword arguments. So this is also still valid:

turbo_stream.append("messages", "Append")
turbo_stream.append_all("messages", "Append")

# => <turbo-stream action="append" target="messages"><template>Append</template></turbo-stream>
# => <turbo-stream action="append" targets="messages"><template>Append</template></turbo-stream>

The other problem the keywords argument solve is that you don't need to remember which argument is which and in which order you need to pass them. With keywords arguments you can pass them in any order. This might not be a big issue for the default actions, but is really beneficial for custom actions.

This also helps when building your own helpers for custom action without having to reinvent the wheel. Previously you had to do something like:

module TurboStreamActionsHelper
  def morph(target, content, children_only = true)
    turbo_stream_action_tag(
      :morph,
      target: target,
      template: content,
      children_only: children_only
    )
  end

  def morph_all(targets, content, children_only = true)
    turbo_stream_action_tag(
      :morph,
      targets: targets,
      template: content,
      children_only: children_only
    )
  end
end

With this pull request you can now make use of the action method and also get the target vs targets for free, without having to declare two methods in your helper for the custom action. The other benefit you get from this is that you can now pass in a block for the rendering or use the rendering mechanism which are built-into the default actions. So one can now implement a helper for a custom action like this:

module TurboStreamActionsHelper
  def morph(target: nil, targets: nil, content: nil, **kwargs, &block)
    action :morph, target: target, targets: targets, content: content, attributes: kwargs, &block)
  end
end

Which allows this action to be used in all variations:

<%= turbo_stream.morph(target: "posts", content: "Posts") %>
<%= turbo_stream.morph(targets: ".post", content: "Post") %>

<%= turbo_stream.morph(target: dom_id(@post), partial: "posts/post", locals: { post: @post } ) %>

<%= turbo_stream.morph(target: dom_id(@post)) do %>
  <h1><%= @post.title %></h1>
<% end %>

To achieve this previously it required quite a sophisticated helper method. This pull request really simplifies this a lot!

Related: #375

@dhh
Copy link
Member

dhh commented Sep 13, 2022

Wouldn't this be a breaking change?

@marcoroth
Copy link
Member Author

I guess that depends if we treated action and action_all as part of the public API or not. Technically there was no reason to call those methods directly. Just now with 7.2 and the introduction of Custom Actions people now have a reason to call those methods directly.

…all`

This allows the `action` and `action_all` methods to be used to build 
custom action helpers when the `target` is not required, as seen in 
hotwired#373.
@marcoroth marcoroth force-pushed the use-keyword-arguments-for-stream-action-methods branch 2 times, most recently from 5a58fa2 to c4ea11b Compare March 29, 2023 02:12
@marcoroth
Copy link
Member Author

I made this change backwards compatible by allowing the arguments to be either passed as positional arguments or keyword arguments.

@marcoroth marcoroth force-pushed the use-keyword-arguments-for-stream-action-methods branch from c4ea11b to 9202d9c Compare March 29, 2023 02:33
@marcoroth marcoroth changed the title Use keyword arguments for TagBuilder#action and #action_all Allow Turbo Stream actions to be provided as keyword arguments Mar 29, 2023
@dhh
Copy link
Member

dhh commented Jun 20, 2023

Hmm, I appreciate where this is coming from, but I don't actually like ending up with two simultaneous API styles for these invocations. Possible that it would have been preferential to go with kwargs originally, but given that we didn't, I don't think there's enough value to going there now, ending up with forked styles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants