ruby-on-rails 使用TurboRails进行单属性原地编辑,不使用gem的Rails7,如何进行验证反馈?

qkf9rpyu  于 2023-02-06  发布在  Ruby
关注(0)|答案(1)|浏览(131)

使用Turbo Frames (不使用像Best_In_Place这样的gem,因为它需要jQuery,并且在Rails 7中不能很好地工作) 创建原地编辑 *-模型的单个属性**。
为了实现这一点,我遵循这个教程:https://nts.strzibny.name/single-attribute-in-place-editing-turbo/(编写于2022年1月)
本教程与Ruby 3.2.0、Rails 7.0.4并不完全匹配,需要在显示页面上进行一个变量调整才能正常工作。
遗憾的是,本教程方法中目前没有验证反馈,因为所实现的turbo_frame表单没有包含它。

问题:如何正确添加验证反馈和错误路由?(最好是仅turbo_frames解决方案)

教程摘要:

  • 创建新应用并搭建一个模型:用户名:字符串
  • UsersController的更改(控制器上编辑单个属性的新操作,edit_name添加到before_action列表)
before_action :set_user, only: %i[ show edit edit_name update destroy ]

    # GET /users/1/edit_name
    def edit_name 
    end
  • 添加到routes.rb*(用于编辑单个特定属性的新路由)*
resources :users do 
        member do 
            get 'edit_name' 
        end 
    end
  • 创建视图/users/edit_name.html.erb*(支持编辑特定属性(此处为名称)的新视图页面)。*
<%= turbo_frame_tag "name_#{user.id}" do %> 
        <%= form_with model: @user, url: user_path(@user) do |form| %> 
            <%= form.text_field :name %> 
            <%= form.submit "Save" %> 
        <% end %> 
    <% end %>

_user.html.erb文件 * 上的附加内容(链接到创建的turbo帧表格edit_name.html.erb)

<%= turbo_frame_tag "name_#{user.id}" do %>   
        Name: <%= link_to @user.name, edit_name_user_path(@user) %> 
    <% end %>

在启动应用服务器时,我收到关于@user为nil:Class的错误。
为了让教程工作,我必须更改_user.html.erb文件,以便在链接中使用user的局部变量。

  • 再次编辑**_user.html.erb***(将示例变量@user更改为局部变量user)*
<%= turbo_frame_tag "name_#{user.id}" do %>   
        Name: <%= link_to user.name, edit_name_user_path(user) %> 
    <% end %>

有了这一更改,教程可以正常工作,允许通过turbo帧进行单个属性原地编辑!但没有实现模型验证反馈。
下面,我将尝试处理验证,首先将验证添加到models/user.rb

class User < ApplicationRecord
        validates :name, presence: true
        validates :name, comparison: { other_than: "Jason" }
    end

建议解决方案:

为弹出的编辑错误创建一个新的turbo_stream文件*(其目标turbo_frame标签中有一个错误,它需要能够以启动单个属性编辑的任何父turbo frame为目标)*

<%= turbo_stream.replace"name_#{@user.id}" do %> 
        <%= form_with model: @user, url: user_path(@user) do |form| %>
          <% if @user.errors.any? %>
            <div style="color: red">
              <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
              <ul>
                <% @user.errors.each do |error| %>
                  <li><%= error.full_message %></li>
                <% end %>
              </ul>
            </div>
          <% end %>
          <% if @user.errors[:name].any? %>
            <%= form.label :name, style: "display: block" %> <%= form.text_field :name %>
          <% end %>
          <% if @user.errors[:active].any? %>
            <%= form.label :active, style: "display: block" %> <%= form.check_box :active %>
          <% end %>
          <%= form.submit "Save" %>
        <% end %>
      <% end %>

并编辑UsersController.rb更新方法以处理turbo流错误

# PATCH/PUT /users/1 or /users/1.json
      def update
        respond_to do |format|
          if @user.update(user_params)
            format.html { redirect_to user_url(@user), notice: "User was successfully updated." }
            format.json { render :show, status: :ok, location: @user }
          else
            format.html { render :edit, status: :unprocessable_entity }
            format.json { render json: @user.errors, status: :unprocessable_entity }
            format.turbo_stream do
              if @user.errors[:name].any?
                @user.name = nil #so that it does not repopulate the form with the bad data
              if @user.errors[:active].any?
                @user.active = nil  
              end
              render :edit_errors, status: :unprocessable_entity 
            end        
          end
        end
      end

除了在无效条目后生成的表单上输入一个成功的编辑后,这一切都可以工作,它只呈现该条目的显示,而不是所有条目。

完成所有这些操作的“更干燥"的方法是什么?(以及我如何针对仅更新turbo流中的一个帧,以便在验证成功后仅更新一个场)?

从哲学上讲,与仅仅使用jQuery和Gem Best_In_Place相比,这一切是否值得???似乎修改的数量正在堆积,如果跨多个属性支持这样的功能,代码将变得丑陋?

k97glaaz

k97glaaz1#

既然最初的问题已经解决了,我将添加一些其他的方法来实现这一点。你自己完成这一点需要做更多的工作,而且你也不会拥有一些gem可以给你的所有功能。另一方面,它的代码要少得多,而且你可以完全控制一切。此外,如果你只需要有一个字段是可编辑的,安装gem和jquery的开销太大。
设置:

# rails v7.0.4.2
$ rails new hello_edit_in_place -c tailwind
$ cd hello_edit_in_place
$ bin/rails g scaffold User email first_name last_name --skip-timestamps
$ bin/rails db:migrate
$ bin/rails runner "User.create(email: 'admin@localhost', first_name: 'super', last_name: 'admin')"
$ open http://localhost:3000/users
$ bin/dev
class User < ApplicationRecord
  validates :email, presence: true, length: {minimum: 3}
end

Turbo帧

作为一个简单的例子,我将只修改默认表单,而不涉及控制器:
一个二个一个一个
就是这样,每个属性都被呈现,可编辑,电子邮件显示验证错误。另外,因为所有turbo_frame_tag都有唯一的id,所以索引页面上的所有内容都可以与多个用户一起使用。

涡轮流

您也可以使用turbo_stream来获得更大的灵活性,使其更具动态性,但它更像是一个设置。此外,添加在适当位置编辑全名的功能,将 * first_name * 和 * last_name * 字段放在一起:

# config/routes.rb

# NOTE: to not mess with default actions, add new routes
resources :users do
  member do
    get "edit_attribute/:attribute", action: :edit_attribute, as: :edit_attribute
    patch "update_attribute/:attribute", action: :update_attribute, as: :update_attribute
  end
end
# app/views/users/_user.html.erb

# Renders user attributes.
# Required locals: user.

<%= render "attribute", user:, attribute: :email %>
<%= render "attribute", user:, attribute: :name %>
# app/views/users/_attribute.html.erb

# Renders editable attribute.
# Required locals: attribute, user.

<%= tag.div id: dom_id(user, attribute) do %>
  <%= tag.div attribute, class: "mt-4 block mb-1 font-medium" %>
  # NOTE: to make a GET turbo_stream request              vvvvvvvvvvvvvvvvvvvvvvvvvv
  <%= link_to edit_attribute_user_path(user, attribute:), data: {turbo_stream: true} do %>
    # far from perfect, but gotta start somewhere
    <% if user.attribute_names.include? attribute.to_s %>
      <%= user.public_send(attribute) %>
    <% else %>
      # if user doesn't have provided attribute, try to render a partial
      <%= render attribute.to_s, user: %>
    <% end %>
  <% end %>
<% end %>
# app/views/users/_name.html.erb

# Renders custom editable attribute value.
# Required locals: user.

<%= user.first_name %>
<%= user.last_name %>
# app/views/users/_edit_attribute.html.erb

# Renders editable attribute form.
# Required locals: attribute, user.

<%= form_with model: user, url: update_attribute_user_path(user, attribute:) do |f| %>
  <% if user.attribute_names.include? attribute.to_s %>
    <%= f.text_field attribute %>
  <% else %>
    # NOTE: same as before but with `_fields` suffix,
    #       so this requires `name_fields` partial.
    <%= render "#{attribute}_fields", f: %>
  <% end %>
  <%= f.submit "save" %>
<% end %>
# app/views/users/_name_fields.html.erb

# Renders custom attribute form fields.
# Requires locals:
#   f - form builder.

<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
# app/controllers/users_controller.rb

# GET /users/:id/edit_attribute/:attribute
def edit_attribute
  attribute = params[:attribute]
  respond_to do |format|
    format.turbo_stream do
      # render form
      render turbo_stream: turbo_stream.update(
        helpers.dom_id(user, attribute),
        partial: "edit_attribute",
        locals: {user:, attribute:}
      )
    end
  end
end

# PATCH  /users/:id/update_attribute/:attribute
def update_attribute
  attribute = params[:attribute]
  attribute_id = helpers.dom_id(user, attribute)
  respond_to do |format|
    if user.update(user_params)
      format.turbo_stream do
        # render updated attribute
        render turbo_stream: turbo_stream.replace(
          attribute_id,
          partial: "attribute",
          locals: {user:, attribute:}
        )
      end
    else
      format.turbo_stream do
        # render errors
        render turbo_stream: turbo_stream.append(
          attribute_id,
          html: (
            helpers.tag.div id: "#{attribute_id}_errors" do
              # FIXME: doesn't render `first_name` `last_name` errors
              helpers.safe_join user.errors.full_messages_for(attribute), helpers.tag.br
            end
          )
        )
      end
    end
  end
end

private

def user
  @user ||= User.find(params[:id])
end

相关问题