Add A Virtual Attribute To A Rails Model Without A Database Column
A signup form collects a full name that gets split into first and last. A settings form takes a confirmation checkbox that’s never stored. These need to behave like attributes — assignable, validatable, form-friendly — without a column behind them.
Description
attr_accessor is the quickest route and gives you a plain getter and setter. It works with form_with, mass assignment, and validations, but it has no type casting and no dirty tracking.
attribute :name, :type is the better tool when the value needs casting. It registers the attribute with ActiveModel, so "7" becomes 7, "false" becomes false, and will_save_change_to_x? works like it does for real columns.
For a value derived from real columns, define a reader and a writer that decompose it — that keeps the database as the source of truth and the virtual attribute as a view of it.
Sample input:
user = User.new(full_name: "Ada Lovelace", terms_accepted: "1")
Sample Output:
user.first_name # => "Ada"
user.last_name # => "Lovelace"
user.terms_accepted # => true (cast from "1")
Answer
class User < ApplicationRecord
# Typed virtual attribute — casting and dirty tracking included
attribute :terms_accepted, :boolean, default: false
validates :terms_accepted, acceptance: true, on: :create
# Untyped — fine when no casting is needed
attr_accessor :signup_source
# Derived from real columns
def full_name
[first_name, last_name].compact_blank.join(" ")
end
def full_name=(value)
first, *rest = value.to_s.strip.split(/\s+/)
self.first_name = first
self.last_name = rest.join(" ").presence
end
end
user = User.new(full_name: "Ada Lovelace", terms_accepted: "1")
user.first_name # => "Ada"
user.terms_accepted # => true
# Works in forms exactly like a column
# <%= f.text_field :full_name %>
# <%= f.check_box :terms_accepted %>
# And in strong parameters
params.require(:user).permit(:full_name, :terms_accepted, :signup_source)
Check viewARU - Brand Newsletter!
Newsletter to DEVs by DEVs - boost your Personal Brand & career! 🚀