Introduction
Sometimes you want a class that behaves like a Rails model (validations, error messages, the whole feel) without a database table behind it. A contact form is the classic case: you validate name, email and message, then send an email; there’s nothing to persist.
There are gems for this, but the mechanics are simple enough that it’s worth knowing how to do it with just ActiveModel.
What ActiveModel gives you
Including ActiveModel::Validations is already enough to validate:
class Contact
include ActiveModel::Validations
attr_accessor :name, :email, :message
validates :email, presence: true
validates :message, presence: true
end
contact = Contact.new
contact.valid? # => false
contact.errors.full_messages # => ["Email can't be blank", "Message can't be blank"]
Validations work here without any ActiveRecord method like persisted? or
new_record?. What you don’t get with ActiveModel::Validations alone is
convenient initialization (Contact.new(name: "Nico") raises, since there’s
no hash-accepting constructor), nor the naming and conversion that form
helpers rely on.
That’s exactly what ActiveModel::Model adds on top.
A complete example
ActiveModel::Model bundles validations together with hash initialization,
naming and conversion, everything a form-like object needs:
class Contact
include ActiveModel::Model
attr_accessor :name, :email, :message
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :message, presence: true
def save
return false unless valid?
# send an email, call an API, whatever "saving" means here
true
end
end
You decide what save means; validation gives you the guard rail, and the
object plugs into form_with just like an ActiveRecord model.
contact = Contact.new(name: "Nico", email: "nico@example.com", message: "Hey!")
if contact.save
puts "Message sent!"
else
puts "Errors: #{contact.errors.full_messages.join(', ')}"
end
A reusable base
If you’ll have several of these, extract a small base class:
class ApplicationForm
include ActiveModel::Model
def save
return false unless valid?
perform
true
end
def perform
raise NotImplementedError, "Subclasses must define #perform"
end
end
class ContactForm < ApplicationForm
attr_accessor :name, :email, :message
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :message, presence: true
def perform
# send the contact message
end
end
Each form declares its fields and validations, and only fills in perform.
When to use it
- Form objects where the data isn’t persisted: contact forms, search forms, API payloads.
- Alongside a service object, to validate input before running business logic.
- Any time you want a lightweight, validatable object without a table behind it.
Alternatives
If you’d rather not hand-roll it, a few gems cover the same ground:
- Reform — form objects with validations and composition.
- dry-validation — schema-plus-rules validation (and the modern successor to the now-deprecated Virtus).
- ActiveInteraction — business logic with built-in validations.
Wrapping up
ActiveModel already contains everything you need to validate a plain Ruby
object. Reach for ActiveModel::Validations when you only want the checks,
and ActiveModel::Model when you want an object that also initializes from a
hash and works with form helpers. No table, no extra dependency required.
Have comments or want to discuss this topic?
Send an email to ~bounga/public-inbox@lists.sr.ht