ActiveModel is one among my most used instruments in Rails purposes. I exploit it in service objects, kind objects and objects that signify exterior entities.
Why? As a result of it supplies a pleasant interface for validating inputs and outcomes, it could have callbacks for pre and post-processing knowledge, and it integrates effectively into varied Rails conventions.
A easy login kind is my go to instance for explaining how good ActiveModel is to work with.
I’ve seen individuals do login flows in controllers, in Consumer fashions and in interactors. However no different method is as simple as making a Login mannequin.
Have a look at this Type object or Exterior entity carried out as a humble mannequin:
# /app/fashions/login.rb
class Login
embrace ActiveModel::Mannequin
attr_accessor :username, :password
validates :username, presence: true
validates :password, presence: true
validate :validate_user_exists!
validate :validate_password_for_user!
before_validation do
@consumer = nil
finish
def validate_user_exists!
return if consumer.current?
errors.add(:username, "not discovered")
finish
def validate_password_for_user!
# `authenticate` comes from Rails
# Reference: https://api.rubyonrails.org/courses/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password
return if consumer&.authenticate(password)
errors.add(:password, "is invalid")
finish
def consumer
@consumer ||= Consumer.find_by(username: username)
finish
finish
# app/controllers/logins_controller.rb
class LoginsController < ApplicationController
def new
@login = Login.new
finish
def create
@login = Login.new(params.require(:login).allow(:username, :password))
if @login.legitimate?
session[:current_user_id] = @login.consumer.id
redirect_to root_path, discover: "Welcome again!"
else
render :new, standing: :unprocessable_entity
finish
finish
finish
You need to use form_for
with out additional arguments or configuration, I18n
works for the attributes and error messages similar to it does for ActiveRecord fashions, and if the login fails the fields get repopulated.
It seems to be and appears like you’re working with an ActiveRecord mannequin, which most of us know and love.
In terms of Service objects, the ActiveModel method provides a solution to talk success and failure via validations. You’ll be able to verify the inputs with legitimate?
, or you possibly can verify the end result, and return a pleasant error message.
class Machine::WarehouseReservator < ApplicationModel
attr_accessor :system, :consumer
validates :system, presence: true
validates :consumer, presence: true
after_initialize do
self.consumer ||= system.creator
finish
def reserve!
return false if invalid?
response = HTTP.submit("http://system.stock.com/api/v1/register", knowledge: payload)
if response.okay?
errors.add(:system, “failed to register”)
return false
finish
self.warehouse_reference = system.create_warehouse_reference(reservation_id: JSON.parse(response.physique).fetch(:id))
finish
def payload
{ device_id: system.id, title: system.title, reservation_holder: consumer.title }
finish
finish
reservator = Machine::WarehouseReservator.new(consumer: Consumer.all.pattern, system: Machine.all.pattern.reserver!
reservator.legitimate? # => true
reservator.reserver!
Reservator.warehouse_reference.id # => 1
reservator = Machine::WarehouseReservator.new(consumer: nil, system: nil)
reservator.legitimate? # => false
reservator.reserver! # => false
reservator.errors.full_messages # => ["User missing", "Device missing"]
It is a a lot nicer solution to talk what went mistaken than elevating and catching errors, returning symbols or customized Error objects.
ActiveModel is kind of a flexible device for that alone, however there’s far more that it could do like:
P.S. Many due to Hrvoje S. For reviwing this text.