Спрощення коду Rails за допомогою об’єктів форми та значень

pic

Вступ

У світі розробки програмного забезпечення важливо створювати чистий та підтримуваний код. Одним із способів досягнення цього є використання шаблонів проєктування, таких як об'єкти форм і об'єкти значень. Ці шаблони допомагають оптимізувати код, зменшити дублювання та в результаті підвищити загальну якість коду. Це, в свою чергу, робить систему більш масштабованою, легшою для тестування та підтримки. У цій статті ми розглянемо, як об'єкти форм і об'єкти значень спрощують складні структури коду, підкреслимо їх переваги та покажемо, як ефективно їх реалізувати.

Проблеми з базовим кодом (без об'єктів форм і значень)

У наведеному прикладі коду, деталі користувача та адреси обробляються безпосередньо в контролері. Як результат, цей підхід призводить до поганого розділення обов'язків, що ускладнює підтримку та розширення коду з часом. Це призводить до перевантаження контролера логікою валідації та побудови зв'язаних моделей. Код не має чіткого розділення та повторного використання, з дублюванням логіки валідації формату електронної пошти та обмеженою гнучкістю в коригуванні деталей користувача або адреси незалежно.

Контролер:

# app/controllers/users_controller.rb  
class UsersController < ApplicationController  
 def create  
 @user = User.new(user_params)  

 # Побудувати асоційовану адресу  
 @user.build_address(address_params)
# Ручна валідація формату електронної пошти  
 if invalid_email?(@user.email)  
 @user.errors.add(:email, "неправильний формат")  
 end if @user.valid? && @user.address.valid?  
 @user.save  
 redirect_to @user, notice: "Користувача зареєстровано успішно!"  
 else  
 render :new, alert: "Сталася помилка при реєстрації."  
 end  
 end private def user_params  
 params.require(:user).permit(:name, :email, :password, :password_confirmation)  
 end def address_params  
 params.require(:address).permit(:street, :city, :state, :zip)  
 end def invalid_email?(email)  
 email !~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i  
 end  
end

Моделі:

# app/models/user.rb  
class User < ApplicationRecord  
 has_one :address, dependent: :destroy  
 validates :name, :email, :password, :password_confirmation, presence: true  
 validates :password, confirmation: true  
end
# app/models/address.rb  
class Address < ApplicationRecord  
 belongs_to :user  
 validates :street, :city, :state, :zip, presence: true  
end

Проблеми цього підходу

  • Перевантажений контролер: В результаті контролер обтяжений обробкою валідації (наприклад, invalid_email?) і побудовою зв'язаних моделей (наприклад, адреси), що збільшує складність і зменшує підтримуваність.
  • Дублювання логіки валідації: Логіка валідації електронної пошти розміщена безпосередньо в контролері і не підлягає повторному використанню.
  • Відсутність розділення: Таким чином, інформація про користувача та адресу обробляються разом, що ускладнює коригування однієї без впливу на іншу. Крім того, відсутність розділення обмежує гнучкість, необхідну для ефективного масштабування застосунку.

Введення об'єктів форм для спрощення контролера

Об'єкти форм діють як проміжні ланки між введенням форми та моделями, захоплюючи специфічну для форми логіку та валідації.

By introducing a form object like UserRegistrationForm, we can centralize validation logic, encapsulate construction logic, and simplify the controller responsibilities.

Об'єкт форми:

# app/forms/user_registration_form.rb  
class UserRegistrationForm  
 include ActiveModel::Model
attr_accessor :name, :email, :password, :password_confirmation, :street, :city, :state, :zip validates :name, :email, :password, :password_confirmation, :street, :city, :state, :zip, presence: true  
 validates :password, confirmation: true  
 validate :validate_email_format def save  
 return false unless valid? user = User.new(name: name, email: email, password: password, password_confirmation: password_confirmation)  
 user.build_address(street: street, city: city, state: state, zip: zip)  
 user.save  
 end private def validate_email_format  
 unless email =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i  
 errors.add(:email, "is invalid")  
 end  
 end  
end

Оновлений контролер:

# app/controllers/users_controller.rb  
class UsersController < ApplicationController  
 def create  
 form = UserRegistrationForm.new(user_registration_params)
if form.save  
 redirect_to user_path(User.last), notice: "User registered successfully!"  
 else  
 render :new, alert: "There was an error with registration."  
 end  
 end private def user_registration_params  
 params.require(:user_registration).permit(:name, :email, :password, :password_confirmation, :street, :city, :state, :zip)  
 end  
end

Переваги об'єктів форм

  • Централізована валідація: Вся логіка валідації централізована в UserRegistrationForm.
  • Чистіший контролер: Тепер контролер зосереджений лише на обробці запиту та відповіді.
  • Спрощене тестування: Тепер ми можемо тестувати UserRegistrationForm окремо.

Введення об'єктів значень для повторного використання

Об'єкти значень інкапсулюють специфічну поведінку та логіку, дозволяючи використовувати їх в різних частинах застосунку. Тут ми створимо об'єкти значень для Address та Email.

Об'єкт значення адреси:

# app/value_objects/address.rb  
class Address  
 attr_reader :street, :city, :state, :zip
def initialize(street:, city:, state:, zip:)  
 @street = street  
 @city = city  
 @state = state  
 @zip = zip  
 validate!  
 end def ==(other)  
 street == other.street && city == other.city &&  
 state == other.state && zip == other.zip  
 end private def validate!  
 raise "Invalid address details" if [@street, @city, @state, @zip].any?(&:blank?)  
 end  
end

Об'єкт значення електронної пошти:

# app/value_objects/email.rb  
class Email  
 attr_reader :address
def initialize(address)  
 @address = address  
 validate!  
 end private def validate!  
 unless @address =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i  
 raise ArgumentError, "Email format is invalid"  
 end  
 end  
end

Оновлений об'єкт форми з об'єктами значень:

# app/forms/user_registration_form.rb  
class UserRegistrationForm  
 include ActiveModel::Model
attr_accessor :name, :email, :password, :password_confirmation, :street, :city, :state, :zip validates :name, :password, :password_confirmation, presence: true  
 validates :password, confirmation: true def save  
 return false unless valid? # Create value objects  
 email_object = Email.new(email)  
 address_object = Address.new(street: street, city: city, state: state, zip: zip) # Use value objects to initialize User and Address models  
 user = User.new(  
 name: name,  
 email: email_object.address, # Extract address from the Email object  
 password: password,  
 password_confirmation: password_confirmation  
 )  
 user.build_address(address_object.to_h) user.save  
 rescue ArgumentError => e  
 errors.add(:base, e.message)  
 false  
 end  
end

Висновок

Використовуючи об'єкти форм і об'єкти значень у Rails, ви можете спростити свою кодову базу, покращити читабельність та підвищити підтримуваність.
Ці шаблони проєктування інкапсулюють логіку, зменшують дублювання та роблять ваш застосунок більш надійним.

Для подальшого вивчення, ви можете ознайомитись з наступними ресурсами, які нададуть глибші уявлення з цієї теми:

  1. Посібники Rails по POROs
  2. Блог Thoughtbot про шаблони проєктування
  3. Domain-Driven Design від Еріка Еванса
  4. Вивчення шаблонів проєктування та антишаблонів

Перекладено з: Simplify Rails Code With Form and Value Objects

Leave a Reply

Your email address will not be published. Required fields are marked *