StimulusReflex 양식 제출의 두 가지 모드

양식은 표준 Rails 원격 양식에서 완벽하게 처리할 수 있는 표준 CRUD 모드입니다.그럼에도 불구하고 때때로 당신은 새로 얻은 Stimulus Reflex 슈퍼 기능을 사용하여 더욱 원활한 사용자 체험을 얻기를 희망합니다.나의 건의는 마지막 모델을 계속 사용하는 것이다. 그러나 다음은 두 가지 모델이다. 사용자들의 상호작용을 개선할 뿐만 아니라 응용 프로그램 구조도 개선할 수 있다.

1.Vanilla CableReady


StimulusReflex discord에서, 우리는 반사에서 표 제출을 어떻게 가장 잘 처리하는지에 대해 자주 질문을 받는다.나의 답은 왕왕 "아니오"이다.대신 표준 Rails 원격 양식과 CableReady 의 강력한 기능에 의존하는 더 강력한 기술을 먼저 보여 드리겠습니다.
이번 훈련의 목적을 위해 저는 Stimulus Reflex 엑스포 사이트의 우수한 것들Calendar Demo을 빌려 썼습니다.(계속하려면 이 RailsByte에 StimulusReflex 및 TailwindCSS가 설치됩니다. 버전 레이블과 함께 demo repo 을 사용할 수도 있습니다.
우선, 우리의 소형 달력 응용 프로그램은 하나의 모델CalendarEvent만 필요로 하고, 우리는 scaffold 명령을 사용하여 이 모델을 생성합니다.
rails new calendula --template "https://www.railsbytes.com/script/z0gsd8" --skip-spring --skip-action-text --skip-active-storage --skip-action-mailer --skip-action-mailbox --skip-sprockets --skip-webpack-install
bin/rails g scaffold CalendarEvent occurs_at:datetime description:text color:string
index 템플릿에서 우리는 두 부분만 보여 줍니다. 하나는 새로운 이벤트를 추가하는 폼을 포함하고, 다른 하나는 달력 격자 자체를 포함합니다.우리는 @dates를 주입했다. 이것은 우리가 표시하고자 하는 Date 개의 대상의 집합과 @calendar_events, 이 날짜 범위 내의 모든 것CalendarEvent일 뿐이다.
<!-- app/views/calendar_events/index.html.erb -->
<div id="calendar-form">
  <%= render "form", calendar_event: @calendar_event %>
</div>

<div class="w-screen p-8">
  <div class="grid grid-cols-7 w-full" id="calendar-grid">
    <%= render partial: "calendar_events/calendar_grid",
               locals: {dates: @dates, 
                        calendar_events: @calendar_events} %> 
  </div>
</div>
_calendar_grid.html.erb부분적 표현_date.html.erb부분적 집합:
<!-- app/views/calendar_events/_calendar_grid.html.erb -->
<% Date::DAYNAMES.each do |name| %> 
  <div class="bg-gray-500 text-white p-3"><%= name %></div>
<% end %>

<%= render partial: "date", collection: dates,
           locals: { calendar_events: calendar_events } %>
<!-- app/views/calendar_events/_date.html.erb -->
<div class="flex flex-col justify-between border h-32 p-3"> 
  <span class="text-small font-bold"><%= date %></span>

  <% (calendar_events[date] || []).each do |calendar_event| %> 
    <span class="bg-<%= calendar_event.color %>-700 rounded text-white px-4 py-2">
      <%= calendar_event.description %>
    </span>
  <% end %>
</div>
_form.html.erb 부분은 비계에서 생성된 표준 부분일 뿐 특별한 점은 없다.그러나 calendar_events_controller.rb 에서 우리는 몇 가지 일을 바꿔야 한다.(모든 실례 변수를 설정하는 부분은 생략합니다. 관심이 있으시면 demo repo 에서 찾을 수 있습니다.)
우선, 우리는 성공적으로 삽입할 때 표준 리디렉션을 삭제합니다.그 다음으로 우리는 이 기능을 주입하기 위해 CableReady::Broadcaster 모듈을 포함해야 한다.그런 다음 @calendar_events 브로드캐스트를 사용하여 morph 새로 생성된 이벤트를 포함하는 #calendar-gridCableReady 을 다시 가져옵니다.
# app/controllers/calendar_events_controller.rb
class CalendarEventsController < ApplicationController
  include CableReady::Broadcaster

  # ...

  def create
    @calendar_event = CalendarEvent.new(calendar_event_params)

    if @calendar_event.save
      @calendar_events = CalendarEvent.where(occurs_at: @date_range)
                                      .order(:occurs_at)
                                      .group_by(&:occurs_at_date)

      cable_ready["CalendarEvents"].morph({selector: "#calendar-grid",
                                           html: CalendarEventsController.render(partial: 'calendar_events/calendar_grid',
                                                                                 locals: {dates: @dates,
                                                                                          calendar_events: @calendar_events}),
                                           children_only: true})
      cable_ready.broadcast
    else
      render :index
    end
  end
  # ...end
물론 채널을 만들고 해당하는 JavaScript 채널을 구성하는 데 필요한 CableReady 설정이 필요합니다.
bin/rails g channel CalendarEvents 
# app/channels/calendar_events_channel.rb
class CalendarEventsChannel < ApplicationCable::Channel
  def subscribed
    stream_from "CalendarEvents"
  end
end
// app/javascript/channels/calendar_events_channel.js
import CableReady from "cable_ready";
import consumer from "./consumer";

consumer.subscriptions.create("CalendarEventsChannel", {
  received(data) {
    if (data.cableReady) CableReady.perform(data.operations);
  }
});
결과적으로 볼 때, 우리는 insert가 예상대로 작동하는 것을 관찰할 수 있으며, 폼만 리셋되지 않았다.그러나 이것은 우리가 정상적인 요청/응답 주기를 처리하지 않았기 때문에 우리는 반드시 스스로 이 일을 해야 한다.

두 번째 CableReady 동작을 추가하여 #calendar-form 부분을 교체합시다.
class CalendarEventsController < ApplicationControllerinclude CableReady::Broadcaster

  # ...

  def create
    @calendar_event = CalendarEvent.new(calendar_event_params)

    if @calendar_event.save
      @calendar_events = CalendarEvent.where(occurs_at: @date_range)
                           .order(:occurs_at)
                           .group_by(&:occurs_at_date)

      cable_ready["CalendarEvents"].morph({selector: "#calendar-grid",
                                           html: CalendarEventsController.render(partial: 'calendar_events/calendar_grid',
                                                                                 locals: {dates: @dates,
                                                                                          calendar_events: @calendar_events}),
                                           children_only: true}
                                         )

      # -----> we also have to reset the form <-----
      cable_ready["CalendarEvents"].inner_html({selector: "#calendar-form",
                                                html: CalendarEventsController.render(partial: "form",
                                                                                      locals: {calendar_event: CalendarEvent.new})
                                               })
      cable_ready.broadcast
    else
      render :index
    end
  end

  # ...end
주의, 나는 여기에서 일부러 inner_html 조작을 사용한다. 왜냐하면 morph 입력의 현재 상태가 DOM에 반영되지 않았기 때문이다.

순전히 Stimulus Reflex를 기반으로 하는 방법에 비해 이런 방법의 추가적인 장점은 사이트의 다른 부분에서 우리의 모델을 업데이트할 수 있다는 것이다.만약 우리가 이 논리를 ActiveJob (또는 서비스 대상, 이렇게 하는 경향이 있는 사람들에게) 격리한다면, 우리는 그것을 더욱 쉽게 테스트하고 응용 프로그램의 다른 부분에서 다시 사용할 수 있다.
bin/rails g job StreamCalendarEvents
# app/controllers/calendar_events_controller.rb
def create
  @calendar_event = CalendarEvent.new(calendar_event_params)

  if @calendar_event.save
    @calendar_events = CalendarEvent.where(occurs_at: @date_range)
                         .order(:occurs_at)
                         .group_by(&:occurs_at_date)

    StreamCalendarEventsJob.perform_now(dates: @dates,
                                        calendar_events: @calendar_events,
                                        date_range: @date_range)
  else
    render :index
  end
end
# app/jobs/stream_workloads_job.rb
class StreamCalendarEventsJob < ApplicationJob
  include CableReady::Broadcaster

  queue_as :default

  def perform(dates:, calendar_events:, date_range:)
    cable_ready["CalendarEvents"].morph({selector: "#calendar-grid",
                                         html: CalendarEventsController.render(partial: 'calendar_events/calendar_grid',
                                                                               locals: {dates: dates,
                                                                                        calendar_events: calendar_events,
                                                                                        date_range: date_range}),
                                         children_only: true})

    # we also have to reset the form
    cable_ready["CalendarEvents"].inner_html({selector: "#calendar-form",
                                              html: CalendarEventsController.render(partial: "form",
                                                                                    locals: {calendar_event: CalendarEvent.new})
                                             })
    cable_ready.broadcast
  end
end
우리는 두 번째 부분에서 이 문제를 다시 토론할 것이다 ActiveJob.

2. 문제를 사용하여 StimulusReflex 양식을 건조하여 제출


만약 당신이 주의하지 않는다면, 순수한 반사에 기반한 폼 제출은 산탄총 수술 냄새의 원천이 될 수 있다. 왜냐하면 당신은 결국 중복된 논리가 많기 때문이다.물론 표준 Rails 컨트롤러도 자원을 바탕으로 하지만 resource.rb, resources_controller.rb, resources_reflex.rb 구조를 만들면 유지보수할 수 없는 길을 빨리 안내할 수 있습니다.위에서 이 예시를 복습하고 폼 처리 논리를 반사에 넣읍시다.

패턴 재사용


만약 내가 이 모델을 어떻게 다시 사용하는지 시범을 보이지 않았다면, 본 연습의 의미는 무엇입니까?이를 위해 우리는 달력에 국정공휴일 (나는 여기에서 사용) 과 holidaysgem) 을 포함하기를 희망한다고 가정한다.우선, 우리는 이 데이터를 저장하기 위한 모델이 필요하다.우리는 표준 ActiveRecord를 사용하고 "단례"방식으로 사용할 것입니다. 즉, 하나의 실례만 있다고 가정합니다.
bin/rails g model Settings holiday_region:string
우리는 간단한 하단 목록을 추가하여 한 나라를 선택하고 싶기 때문에, 우리는 단지 추가하기만 하면 된다
<div class="grid grid-cols-2 w-full">
  <div id="calendar-form"
       data-controller="reflex-form"
       data-reflex-form-reflex-name="CalendarEventsReflex">
    <%= render "form", calendar_event: @calendar_event %>
  </div>

  <div id="settings-form"
       data-controller="reflex-form"
       data-reflex-form-reflex-name="SettingsReflex">
    <%= form_with(model: Setting.first, method: :put, url: "#", class: "m-8",
                  data: {signed_id: Setting.first.to_sgid.to_s, target: "reflex-form.form"}) do |form| %>
    <div class="max-w-lg rounded-md shadow-sm sm:max-w-xs">
      <%= form.label :holiday_region %>

      <%= form.country_select :holiday_region,
                              {only: Holidays.available_regions.map(&:to_s).map(&:upcase)},
                              class: "form-select block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5",
                              data: {action: "change->reflex-form#submit", reflex_dataset: "combined"} %>
    </div>
    <% end %>
  </div>
</div>
우리에게calendar_events/index.html.erb.우리가 사용하는 자극 컨트롤러는 위에서 만든 것과 같다는 것을 주의하세요!또한 Settings 인스턴스에 대한 서명 글로벌 ID(SGID)를 전달합니다.요소의 데이터 세트에 서명 ID가 있는지 확인하기 위해 Submittable 관심사를 확장해야 합니다.만약 그렇다면, 우리는 기록을 검색하고 변경된 속성을 분배합니다.만약 그렇지 않다면, 우리의 창작 논리는 변하지 않을 것이다.
# app/reflexes/concerns/submittable.rb

module Submittable

  # ...

  included do
    before_reflex do
      if element.dataset.signed_id.present?
        @resource = GlobalID::Locator.locate_signed(element.dataset.signed_id)
        @resource.assign_attributes(submit_params)
      else
        resource_class = Rails.application.message_verifier("calendar_events")
                           .verify(element.dataset.resource)
                           .safe_constantize
        @resource = resource_class.new(submit_params)
      end
    end
  end

  # ...
end
마지막으로 중요한 것은 SettingsReflex 관심사를 포함하여 Submittable 를 만들어야 한다는 것이다.
bin/rails g stimulus_reflex SettingsReflex
# app/reflexes/settings_reflex.rb
class SettingsReflex < ApplicationReflex
  include Submittable

  before_reflex :fetch_dates

  after_reflex do
    @calendar_events = CalendarEvent.where(occurs_at: @date_range)
                         .order(:occurs_at)
                         .group_by(&:occurs_at_date)

    StreamCalendarEventsJob.perform_now(dates: @dates,
                                        calendar_events: @calendar_events,
                                        date_range: @date_range)
  end

  private

  def submit_params
    params.require(:setting).permit(:holiday_region)
  end

  def fetch_dates
    # ...
  end
end
이 모델의 장점은 실제 반사 논리가 DOM 수리를 처리하는 것으로 간소화되고 심지어 문제를 추출할 수 있다는 데 있다. 왜냐하면 이것은 위와 완전히 같기 때문이다.휴일 이름을 표시하려면 _date.html.erb 섹션만 수정해야 합니다.
<div class="flex flex-col justify-between border h-32 p-3 <%= "bg-gray-200" unless date_range.include? date %>">
  <span class="text-small font-bold"><%= date %></span>

  <% holidays = Holidays.on(date, Setting.first.holiday_region.downcase.to_sym) %>

  <% unless holidays.empty? %>
    <span class="bg-blue-600 rounded text-white px-4 py-2">
      <%= holidays.map { |h| h[:name] }.join(" ") %>
    </span>
  <% end %>

  <% (calendar_events[date] || []).each do |calendar_event| %>
    <span class="bg-<%= calendar_event.color %>-700 rounded text-white px-4 py-2"><%= calendar_event.description %> </span>
  <% end %>
</div>
최종 결과는 다음과 같습니다.
country_select

결론


나는 두 가지 폼 제출 모델을 소개했는데 사용 CableReady, 응용 프로그램에서 실현할 때 이 두 가지 모델을 고려해야 한다.WebSocket을 전송하기 위해서는 이미 만들어진 모델을 다시 고려해야 하지만, 잘하면 더욱 내재된 코드 라이브러리가 생길 수 있습니다.

좋은 웹페이지 즐겨찾기