/ Tags: RAILS-CODE / Categories: SOLUTIONS

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)

Learn More

cdrrazan

Rajan Bhattarai

Full Stack Software Developer! 💻 🏡 Grad. Student, MCS. 🎓 Class of '23. GitKraken Ambassador 🇳🇵 2021/22. Works with Ruby / Rails. Photography when no coding. Also tweets a lot at TW / @cdrrazan!

Read More