Вступ
У світі розробки програмного забезпечення важливо створювати чистий та підтримуваний код. Одним із способів досягнення цього є використання шаблонів проєктування, таких як об'єкти форм і об'єкти значень. Ці шаблони допомагають оптимізувати код, зменшити дублювання та в результаті підвищити загальну якість коду. Це, в свою чергу, робить систему більш масштабованою, легшою для тестування та підтримки. У цій статті ми розглянемо, як об'єкти форм і об'єкти значень спрощують складні структури коду, підкреслимо їх переваги та покажемо, як ефективно їх реалізувати.
Проблеми з базовим кодом (без об'єктів форм і значень)
У наведеному прикладі коду, деталі користувача та адреси обробляються безпосередньо в контролері. Як результат, цей підхід призводить до поганого розділення обов'язків, що ускладнює підтримку та розширення коду з часом. Це призводить до перевантаження контролера логікою валідації та побудови зв'язаних моделей. Код не має чіткого розділення та повторного використання, з дублюванням логіки валідації формату електронної пошти та обмеженою гнучкістю в коригуванні деталей користувача або адреси незалежно.
Контролер:
# 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, ви можете спростити свою кодову базу, покращити читабельність та підвищити підтримуваність.
Ці шаблони проєктування інкапсулюють логіку, зменшують дублювання та роблять ваш застосунок більш надійним.
Для подальшого вивчення, ви можете ознайомитись з наступними ресурсами, які нададуть глибші уявлення з цієї теми:
- Посібники Rails по POROs
- Блог Thoughtbot про шаблони проєктування
- Domain-Driven Design від Еріка Еванса
- Вивчення шаблонів проєктування та антишаблонів
Перекладено з: Simplify Rails Code With Form and Value Objects