Posted in

如何在 Rails 应用中使用 Turbo 与 RubyMine_AI阅读总结 — 包阅AI

包阅导读总结

1. `RubyMine`、`Turbo`、`Rails`、`Hotwire`、`Turbo Streams`

2. 本文主要介绍如何在 RubyMine 中的 Rails 应用里使用 Turbo,包括 Hotwire 和 Turbo 的概念,重点讲解了 Turbo Frames 和 Turbo Streams 的用法,并通过示例应用展示了其实际操作。

3.

– RubyMine 团队致力于为 Ruby 和 Rails 提供新技术支持,本文介绍 Hotwire 框架中的 Turbo 及相关特性。

– Hotwire 简介:通过发送 HTML 而非 JSON 简化 Web 开发,由 Turbo、Stimulus 和 Strada 等框架组成。

– Turbo 简介:提供实时页面局部更新方式,加速链接点击和表单提交,包含 Turbo Streams 和 Turbo Frames 两个核心概念。

– 示例应用教程

– 使用 Turbo Frames 实现原位编辑,如编辑微帖子。

– 使用 Turbo Streams 添加取消关注链接,实时更新用户关注计数和删除相关帖子。

– 指出 RubyMine 为 Turbo 提供代码洞察支持,如代码完成和导航。

思维导图:

文章地址:https://blog.jetbrains.com/ruby/2024/07/how-to-use-turbo-in-your-rails-apps-with-rubymine/

文章来源:blog.jetbrains.com

作者:Darya Sharkova

发布时间:2024/7/5 14:03

语言:英文

总字数:2886字

预计阅读时间:12分钟

评分:81分

标签:RubyMine,ruby,Ruby on Rails,涡轮


以下为原文内容

本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com

RubyMine

How to use Turbo in your Rails apps with RubyMine

Read this post in other languages:

Hello everyone!

The RubyMine team is constantly striving to provide support for new technologies for Ruby and Rails. One of the most exciting recent additions to Rails is undoubtedly Hotwire, so we’ve prepared an overview of this suite of frameworks and a tutorial on how to use the most important Turbo and Stimulus features in your Rails app with RubyMine. This post covers Turbo; to learn more about Stimulus support, stay tuned for our next blog post.

Hotwire and Turbo

What is Hotwire?

Hotwire simplifies web development by sending HTML instead of JSON over the wire (hence the name: it stands for “HTML over the wire”). This reduces the amount of JavaScript written and sent to the browser and keeps template rendering on the server. Hotwire is made up of several frameworks: Turbo, Stimulus, and Strada. In this post, we’ll take a look at Turbo.

What is Turbo?

Turbo provides simple ways of delivering live partial updates to the page without having to write any JavaScript at all. It also speeds up all link clicks and form submissions in your app thanks to Turbo Drive. In this tutorial, we’ll focus on splitting the pages into separate contexts that can be updated in real time without reloading the full page. For this, we’ll use the other two core concepts of Turbo: Turbo Streams and Turbo Frames.

What are Turbo Frames?

Turbo Frames let you decompose the page into independent contexts. If you wrap a part of your Document Object Module (DOM) inside a Turbo Frame, you can update just that frame without having to reload the rest of the page.

What are Turbo Streams?

Turbo Streams can be used to update parts of the DOM without reloading the entire page. A server can send a Turbo Stream message instead of HTML of the entire page; the message will contain HTML fragments, the target element ID, and the action that will be applied to them (add to the target, remove from the target, update the target, etc.: for a full list, please consult the documentation). Much like Turbo Frames, Turbo Streams let you deliver real-time partial page changes. In some ways, the functionality of Turbo Streams is more robust: they let you change multiple parts of DOM and not just the corresponding frame, as well as perform a variety of updates, such as appending or removing elements.

The turbo-rails gem is shipped by default with Rails 7, so you can start using it in your applications right away!

RubyMine, an IDE for Ruby and Rails developers, provides code insight support for Turbo, such as code completion and navigation, which we encourage you to use as you follow this tutorial.

Tutorial: how to use Turbo in Rails apps with RubyMine

In this tutorial, we’ll show you how to use the basic building blocks of Turbo. We’ll use a sample Rails application that allows users to make accounts, create microposts, follow each other, and read the microposts in a feed.

Clone the sample Rails app

Follow these steps to clone our sample app and get it running:

  1. Check out the sample application athttps://github.com/JetBrains/sample_rails_app_7th_ed/tree/hotwire_setup(make sure to switch to thehotwire_setupbranch once you’ve cloned the project). For further information on how to set up a Git repository, please seethe documentation.
  2. Specify the Ruby interpreter and install gems.
Alice’s feed in the sample app

How to use Turbo Frames

We can currently delete posts in this sample app. Let’s add editing, too.

  1. Open the file _micropost.html.erb and add an “Edit” link next to the “Delete” link in the timestamp.
<span class="timestamp">  Posted <%= time_ago_in_words(micropost.created_at) %> ago.  <% if current_user?(micropost.user) %>    <%= link_to "delete", micropost, data: { "turbo-method": :delete,                                            turbo_confirm: "You sure?" } %>    <%= link_to "edit", edit_micropost_path(micropost) %>  <% end %></span>

2. Add edit and update methods to the micropost controller (don’t worry – the routes have already been added to routes.rb).

def edit  @micropost = Micropost.find(params[:id])enddef update  @micropost = Micropost.find(params[:id])  if @micropost.update(micropost_params)    redirect_to root_url  else    render :edit, status: :unprocessable_entity  endend

The edit button will redirect us to the micropost/[id]/edit route, which will want to load the view template. Click on the gutter icon next to def edit to create a template file edit.html.erb.

Click the gutter icon to create a view template next to a controller action

3. Add the following code to your microposts/edit.html.erb file:

<%= render 'shared/micropost_form'%>

In order to edit a post, we simply need to render the existing partial shared/micropost_form which we already use for creating microposts. If we click on Edit now, we’ll be redirected to a new page.

The Edit button takes us to a new page

But what we’d like to do here is to have in-place editing: instead of loading a new page, let’s replace the existing micropost body with a form, update the text, save it – and stay on the same page throughout.

To do that, we can utilize Turbo Frames.

With Turbo Frames, we’ll be able to load the form in place of the micropost, update its content, and then render the updated micropost without having to reload the rest of the page.

4. Add a turbo_frame_tag view helper to _micropost.html.erb:

<%= turbo_frame_tag micropost do %>  <li id="micropost-<%= micropost.id %>">    # keep the rest of the file content as it was   </li><% end %>

We’ll be able to use this frame to specify which parts of HTML should be replaced when the server processes a request. A frame should have a unique ID which is passed as an argument to turbo_frame_tag. It can be a string or a symbol, but in our case, we need it to be unique to make sure we’re working with the right frame, so it needs to depend on the specific record. Luckily, Turbo Frames are very convenient to use: we can just pass the micropost object, and turbo_frame_tag will automatically apply the dom_id helper to it and use that as the frame ID.

Since we’d like to replace our micropost with a form to edit it, we’ll need edit.html.erb to contain a matching Turbo Frame.

5. In microposts/edit.html.erb, add the turbo_frame_tag helper:

<%= link_to "Back", root_path %><%= turbo_frame_tag @micropost do %>  <%= render "shared/micropost_form", micropost: @micropost %><% end %>

Please note that the frame IDs passed to the turbo_frame_tag helper should match on both pages.

Now, when we click on the Edit link, the controller responds to the microposts/[id]/edit request with a Turbo Frame. The content of this new Turbo Frame replaces the old content, and we now see a micropost form instead of the micropost content and can edit its text. When we click on Post and send a request to the microposts/[id]/update route, the controller once again sends us a response containing Turbo Frames; Turbo finds the matching frame and uses its contents to replace the existing ones: in this case, the updated micropost. You can check the console to see how Rails processes the requests.

Edit with Turbo

Any link or form inside a Turbo Frame will be intercepted and will try to find a Turbo Frame of the same ID as its parent frame or its target (links can target different frames; to do that, add a data: { turbo_frame: [frame id] } as an argument for link_to).

But what if you want to update multiple elements on the page with a single click of a link? Or append or remove elements instead of replacing them? This is where Turbo Streams come in handy.

How to use Turbo Streams

To demonstrate the power of Turbo Streams, we’ll add an unfollow link to each micropost and update the user.following count in real time when the current user unfollows someone from their feed. We’ll also remove all the posts by the unfollowed user.

  1. Open _micropost.html.erb and add an unfollow link to each micropost not made by the current user:
<% if current_user?(micropost.user) %>  <%= link_to "delete", micropost, data: { "turbo-method": :delete,                                           turbo_confirm: "You sure?" } %>  <%= link_to "edit", edit_micropost_path(micropost) %><% elsif !current_user.nil? && current_user.following?(micropost.user) %>  <%= link_to "unfollow",  relationship_path(current_user.active_relationships.find_by(followed: micropost.user), user_to_update: current_user),  data: {turbo_method: :delete, turbo_confirm: 'Are you sure?'} %><% end %>

Where does the path come from? The current app already implements functionality that allows users to follow and unfollow each other; moreover, this functionality even uses Turbo Streams. To achieve a better understanding of Turbo, we’ll take that implementation up a step and allow users to unfollow other users not just from the user page, but also from their feed.

The User model maintains the list of all the users a user follows (active_relationships) and all the users that follow that user (passive_relationship). To unfollow a user, we need to destroy this active_relationships association between the two users; therefore, we need to obtain the correct path and set the link to use the DELETE method (which, thanks to Turbo, we can do out of the box).

Let’s see what happens when we click on unfollow:

Unfollow: initial implementation

Not only are the posts of the user we just unfollowed still present on our feed, but the counter update, although live, is also wrong.

To fix this, let’s first take a look at the controller that manages relationships.

2. Open the file relationships_controller.rb.

The current code is as follows:

# relationships_controller.rb@user = Relationship.find(params[:id]).followedcurrent_user.unfollow(@user)respond_to do |format|  format.html { redirect_to @user, status: :see_other }  format.turbo_streamend

It is designed for unfollowing users from the Users page of the sample app: as the current user, you unfollow another user and then see their follower count decrease. However, in our case, we’d like to unfollow a user from our feed page and then see the current user’s following count decrease. To do that, we just need to know which user (current or unfollowed user) we’re about to update the counters for.

3. Open the file routes.rb and add a route with an extra parameter that will let us pass the ID of the user for whom we want to update the counter:

resources :relationships, only: [:create, :destroy]delete '/relationships/:id/:user_to_update', to: 'relationships#destroy_with_counter_update'

Now that we’re passing a parameter and using a custom route, we’ll need to update the path we’ll be using for the unfollow link:

<%= link_to "unfollow", "/relationships/#{current_user.active_relationships.find_by(followed: micropost.user).id}/#{current_user.id}/",                data: {turbo_method: :delete, turbo_confirm: 'Are you sure?'} %>

We can now retrieve the user from the parameters when updating relationships_controller.rb.

4. Add the following code to relationships_controller.rb:

# relationships_controller.rbdef destroy_with_counter_update  relationship = Relationship.find(params[:id])  @unfollowed_user = relationship.followed  current_user.unfollow(@unfollowed_user)  @user = User.find(params[:user_to_update])  @microposts = Micropost.where(:user_id => @unfollowed_user)  respond_to do |format|    format.html { redirect_to @unfollowed_user, status: :see_other }    format.turbo_stream  endend

This method handles unfollowing users from the feed and immediately updating the following counters.

By this point, you may be wondering what format.turbo_stream is and what this method does.

Turbo interjects its special turbo_stream format into the Accept header of the request (this is done by default if the form is submitted with a POST, PUT, PATCH, or DELETE method, but you can manually add this format to your GET requests as well; see the documentation). The controller can then send a response in this format or ignore it and fall back on HTML. This is exactly what’s happening in the code for RelationshipsController#destroy: the controller can send responses in both HTML and Turbo Stream formats.

format.turbo_stream can render the response inline in the controller:

format.turbo_stream { render turbo_stream: turbo_stream.update "following", @user.following.count }

If you’d like to update multiple targets, it’s preferable to use the corresponding view template with the extension .turbo_stream.erb.

5. Create a file named relationships/destroy_with_counter_update.turbo_stream.erb and paste the following code there:

<%= turbo_stream.update "followers" do %>  <%= @user.followers.count %><% end %><%= turbo_stream.update "following" do %>  <%= @user.following.count %><% end %><%= microposts = Micropost.where(:user_id => @unfollowed_user) %><% microposts.each do |post| %>  <%= turbo_stream.remove post %><% end %>

Let’s take a closer look at the code that updates the Following count:

<%= turbo_stream.update "following" do %>  <%= @user.following.count %><% end %>

What do these lines do?

The controller needs to send some HTML in response to a request. Turbo lets us send fragments of HTML without sending the entire page. In this case, only those fragments are updated, without a full page reload. These lines specify the Turbo Streams message that the controller will send.

The HTML for those messages takes on the following form:

<turbo-stream action="[action name]" target="[target element or Turbo Frame ID]">  <template>    The contents of this tag will be applied to the target element as per the specified action, e.g. deleted, appended, replaced, etc.  </template></turbo-stream>

Turbo Streams let the users execute eight actions in total: append, prepend, replace, update, remove, before, after, and morph. For more information, please consult the documentation.

Let’s take a look at the code we added to destroy.turbo_stream.erb again:

<%= turbo_stream.update "following" do %>  <%= @user.following.count %><% end %>

We want to perform the update action on the following target (you may find the DOM element with this ID in the shared/_stats.html.erb partial), and we want the target to be updated with the @user.following.count HTML.

Now let’s take a look at the code that removes all the posts by the unfollowed user from the feed:

<%= microposts = Micropost.where(:user_id => @unfollowed_user) %><% microposts.each do |post| %>  <%= turbo_stream.remove post %><% end %>
Unfollow with Turbo Streams

As you can see, now we can unfollow users right from the feed. The following count will update correctly and in real time, and the posts will disappear from the feed – all without a full-page reload!

How to use broadcasting

Another way to achieve this is broadcasting.

Turbo Streams can be broadcast from models upon updates to the record. There are several basic actions that can be broadcast: remove, replace, append, prepend, before, after, and update. For more information, please consult the documentation.

In our case, we’d like to broadcast updates when one user unfollows another – that is, when an instance of Relationship is destroyed.

Firstly, we’ll need to destroy the record in RelationshipsController#destroy.

  1. Open relationships_controller.rb, navigate to the destroy_with_counter_update method, and add the following line:
#relationships_controller.rbrelationship.destroy!

Since broadcasting is done in a model callback, we’ll need to explicitly destroy the record first.

2. Open the file relationship.rb and add the following line:

# relationship.rbafter_destroy { broadcast_update_to "following_stream_#{follower.id}", target: "following", html: "#{follower.following.count}" }

after_destroy is the callback that executes after a Relationship record has been destroyed. broadcast_update_to is the method that allows us to broadcast the update action to Turbo Streams. You can broadcast other actions, too:

Completion for broadcast

Let’s take a look at the arguments to broadcast_update_later_to. First, we have to specify the stream we’ll be broadcasting to. In our case, we’d like the stream name to depend on the user ID to ensure we’re broadcasting the right information to the right users; otherwise, we might, for example, decrease the counter for all the users and not just for the current one. Therefore, let’s name our stream following_stream_#{follower.id}. We also have to specify the target: it’s still the same DOM element with the ID following. Finally, we need to provide the HTML fragment to be rendered. Frequently, it is a partial view, in which case the arguments would look like this:

after_destroy { broadcast_update_later_to "[stream name]", target: "[target ID]", partial: "[partial name]", locals: { ... } }

In our case, it is enough to pass the HTML.

In order to receive broadcasts, we have to subscribe to streams of the corresponding name in the view files.

3. Open the file shared/_stats.html.erb. After the first line, add the following:

<% @user ||= current_user %><%= turbo_stream_from "following_stream_#{@user.id}" %>

And don’t forget to remove the code we added to destroy.turbo_stream.erb that updates the following counter, since we’re now updating it via broadcasting instead.

Now, if we load the page and unfollow a user, we’ll see the counter being updated in real time.

When should we use broadcasting instead of sending Turbo Streams from the controller?

You should use broadcasting if:

  • The update should be visible not just to the current user, but to everyone viewing the same page
  • The update is asynchronous

If neither of those is required, you can use Turbo Streams from the controller.

Conclusion

In this tutorial, we’ve explored the Turbo framework and its core concepts: Turbo Streams and Turbo Frames. We’ve learned to use Turbo Frames and Turbo Streams in our Rails applications to deliver real-time partial updates to the pages.

Turbo Frames let us deliver partial live updates to certain DOM elements, while Turbo Streams let us target multiple elements and perform a variety of actions. However, sometimes we might want more interactivity than Turbo Streams allow, and thus, we must turn to JavaScript.

Luckily, Hotwire provides another component to do just that: Stimulus.

For more information about Stimulus, please stay tuned for our next blog post.

Take advantage of Hotwire support in your favorite JetBrains IDE for Ruby and Rails. Download the latest RubyMine version from our website or via the freeToolbox App.

To learn about the newest features as they come out, please follow RubyMine on X.

We invite you to share your thoughts in the comments below and to suggest and vote for new features in the issue tracker.

Happy developing!

The RubyMine team

Subscribe to RubyMine Blog updates