Привет, Хабр!

Работа с многоуровневыми формами в Ruby on Rails — это то, что точно поднимет ваш скилл в Ruby. В этой рассмотрим, как упростить этот процесс с помощью двух гемов: Wicked и Cocoon.

Многоуровневые формы позволяют собирать информацию от пользователя поэтапно, что делает интерфейс более дружелюбным.

Настройка и конфигурация гемов Wicked и Cocoon для Rails

Wicked


Для начала добавляем Wicked в Gemfile:

gem 'wicked'

Затем выполняем команду bundle install для установки гема.

Wicked превращает контроллер в магический многоэтапный процесс. Начинаем с создания нового контроллера или модификации существующего, добавив в него миксин Wicked::Wizard. Например:

rails g controller after_signup

В контроллер добавляем:

include Wicked::Wizard
steps :first_step, :second_step

В config/routes.rb указываем, что контроллер должен использовать магические маршруты:

resources :after_signup, only: [:show, :update], controller: 'after_signup'

Юзаем render_wizard для управления переходами между шагами. Например:

def show
  @user = current_user
  render_wizard
end

В update методе можно обработать ввод данных и переход к след. шагу.

Cocoon

Аналогично как и с Wicked, добавляем следующую строку в Gemfile:

gem 'cocoon'

Выполняемbundle install.

С Cocoon можно легко работать с вложенными атрибутами, например в форме можно использовать link_to_add_association и link_to_remove_association для динамического добавления или удаления полей.

Пример использования в форме с помощью simple_form:

<%= simple_form_for @user do |f| %>
  <%= f.simple_fields_for :addresses do |address| %>
    <%= render 'address_fields', f: address %>
  <% end %>
  <%= link_to_add_association 'add address', f, :addresses %>
<% end %>

В частичном представлении _address_fields.html.erb можно использовать link_to_remove_association для добавления возможности удаления.

Cocoon автоматически работает с jQuery через application.js:

//= require jquery
//= require cocoon

На стороне клиента можно использовать события Cocoon, такие как cocoon:before-insert и cocoon:after-insert, для добавления пользовательских анимаций или логики обработки:

$(document).on('cocoon:before-insert', function(e, insertedItem) {
  // анимация или другие действия перед вставкой элемента
  insertedItem.fadeIn('slow');
});

$(document).on('cocoon:after-insert', function(e, insertedItem) {
  // действия после вставки элемента
});

Обработка и валидация данных формы

Rails предоставляет множество хелперов для валидации, таких как presence, length, numericality, и format, которые помогают убедиться, что данные соответствуют определённым требованиям. Например, чтобы убедиться, что поле не пустое и соответствует определённой длине, можно использовать следующие валидации в модели:

class Comment < ApplicationRecord
  validates :content, presence: true, length: { maximum: 500 }
end

Если данные не проходят валидацию, объект не будет сохранён в БД, а ошибки можно будет проверить через метод errors объекта.

Еще можно определить собственные методы валидации для более спец. требований, используя хелпер validate. Например, если нужно проверить, что текст не содержит ненормативной лексики, можно определить такой метод:

class Comment < ApplicationRecord
  validate :check_for_offensive_language

  private

  def check_for_offensive_language
    if content.include?('offensive_word')
      errors.add(:content, 'contains offensive language')
    end
  end
end

При работе с формами и API, важно правильно обрабатывать ошибки валидации и передавать их юзеру. В контроллере можно организовать проверку валидности объекта и соответствующим образом формировать ответ:

class UsersController < ApplicationController
  def create
    user = User.new(user_params)
    if user.save
      render json: user, status: :created
    else
      render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.require(:user).permit(:name, :email, :password)
  end
end

Так можно отображать пользователю конкретные ошибки, связанные с каждым полем формы.

Тестирование

Для тестирования многоуровневых форм рекомендуется использовать сочетание RSpec и Capybara:

describe "Multi-step form", type: :feature do
  it "processes the form correctly" do
    visit new_registration_path
    fill_in 'Name', with: 'Kolya'
    click_button 'Next'
    fill_in 'Address', with: '123 Lenina St'
    click_button 'Submit'
    expect(page).to have_content('Registration successful')
  end
end

Для детальной отладки можете использовать гем Pry. С ним можно остановить выполнение кода в любой точке и изучить текущее состояние переменных и логику выполнения:

class RegistrationsController < ApplicationController
  def create
    binding.pry  # остановка и отладка в этой точке
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      render :new
    end
  end
end

Также не забываем логировать основные парамтеры:

Rails.logger.debug "Processing step 1 with data: #{params[:user]}"

Для более сложных многоуровневых форм можно юзать интеграционные тесты. Создавайте тестовые объекты с помощью FactoryBot и проверяйте ассоциации и валидации с помощью Shoulda Matchers:

describe User, type: :model do
  it { should validate_presence_of(:email) }
  it "has a valid factory" do
    user = build(:user)
    expect(user).to be_valid
  end
end

Все лучшие практики веб-разработки на Ruby on Rails можно освоить на практическом онлайн-курсе.

Комментарии (2)


  1. leotada
    15.05.2024 17:18

    >Cocoon автоматически работает с jQuery через application.js:
    похоже на перевод какой-то древней статьи


  1. klondaiker
    15.05.2024 17:18

    Сейчас за место Cocoon лучше использовать уже готовый stimulus компонент
    https://www.stimulus-components.com/docs/stimulus-rails-nested-form