Rails

Rails Model Properties and Attributes

Learn how Rails model properties and attributes map to database columns, including access, type casting, defaults, validation, associations, virtual attributes, and migrations.

In Rails, a model's properties are the pieces of data and behavior exposed through its object interface. Rails developers often use property, attribute, field, and column in closely related ways, but these terms describe different layers.

  • A database column is a typed field in a database table.
  • An Active Record attribute is a value associated with a model object, usually backed by a column.
  • A Ruby instance variable, such as @name, is an implementation detail inside a Ruby object. It is not automatically a database attribute.
  • A model exposes database-backed attributes through generated reader and writer methods, such as product.name and product.name = "Keyboard".

Active Record is Rails' object-relational mapping layer. It represents database rows as Ruby objects and connects model methods to database operations.

How database properties become model attributes

A relational database stores data in tables. A table contains columns, and each row represents one record. An Active Record model normally maps to one table: the Product model maps to the products table by convention.

products table
+----+----------+-------+-----------+
| id | name     | price | in_stock  |
+----+----------+-------+-----------+
|  1 | Keyboard | 49.99 | true      |
+----+----------+-------+-----------+

Product object
product.id
product.name
product.price
product.in_stock

Rails reads the database schema and makes the table's columns available as model attributes. A generated attribute method does not mean that Rails added a separate Ruby property storage system; it means the model knows how to read and write that attribute and persist it through Active Record.

Rails naming conventions are important. A model class is usually singular and uses CamelCase, while its table is plural and uses snake_case:

class Product < ApplicationRecord
end

# Product maps to the products table by convention.

If a model uses a nonstandard table, configure it explicitly with self.table_name.

Generating persisted properties

A model generator can create a migration containing initial columns. Review the migration before applying it:

bin/rails generate model Product name:string description:text price:decimal in_stock:boolean
bin/rails db:migrate

The migration creates columns in the database. The model then receives corresponding attributes after the schema is available to the application.

Column TypeExample PropertyTypical Ruby ValueImportant Considerations
stringnameStringSuitable for shorter text; database length limits may apply.
textdescriptionStringSuitable for longer text.
integerstock_countIntegerAssigned values are type-cast to an integer representation.
decimalpriceBigDecimal-like decimal valueUse suitable precision and scale for exact monetary data.
booleanin_stocktrue or falseDistinguish false from nil and choose an explicit default where appropriate.
dateavailable_onDateRepresents a calendar date without a time of day.
datetimepublished_atTime-like valueBe aware of time zones when displaying and comparing values.
referencescustomerUsually a customer_id integer or UUIDTypically creates a foreign-key column and supports an association.

Rails property categories

CategoryExampleStored in DatabaseHow It Is DefinedTypical Use
Database-backed attributeproduct.nameUsually yesDatabase column and schemaPersisted record data
Associationorder.customerRelated record is stored separatelybelongs_to or has_manyObject-level access to related records
Computed propertyproduct.display_priceNoCustom Ruby methodDerived display or business value
Virtual attributeuser.password_confirmationNo, unless custom persistence is addedCustom reader and writer, or an attribute declarationTemporary form or processing input
Database defaultin_stock: falseDefined by schemaColumn default in a migrationValue used when no value is supplied

Reading and writing model attributes

Rails supplies reader and writer methods for database-backed attributes. A reader returns a value; a writer changes the in-memory object.

product = Product.new(name: "Keyboard", price: 49.99)

product.name                 # reader: "Keyboard"
product.price = 59.99        # writer: changes the object in memory
product.in_stock = true

product.save                  # attempts to persist the object
product.reload                # reads the current row from the database

new initializes an object but does not insert a row. save performs validation and, when successful, inserts or updates the row. For an existing record, update assigns values, validates, and saves in one operation:

product.update(price: 54.99, in_stock: true)
product.update!(price: 54.99)

update returns false when normal validation fails. update! raises an exception when the operation cannot be completed, which is useful when failure should not be silently ignored.

Hash-style access is available when an attribute name is dynamic or when uniform attribute handling is convenient:

product[:name]
product[:price] = 39.99
product.attributes

Prefer ordinary method syntax for clearly known attributes because it is easier to read. Attribute hashes are useful for inspection, serialization, and generic code.

ApproachExample SituationPersists ImmediatelySafety or Validation Notes
InitializationProduct.new(name: "Pen")NoValues are in memory until save is called.
Individual writer assignmentproduct.name = "Notebook"NoChanges the object; call save afterward.
updateproduct.update(price: 10)Yes, if validReturns a success value and does not normally raise for validation failure.
update!product.update!(price: 10)Yes, if validRaises when validation or persistence fails.
Mass assignment from permitted parametersProduct.new(product_params)NoOnly use filtered, authorized input.

Defaults, nil, and boolean values

A default value is used when no explicit value is assigned. A database default is defined in the schema and is applied by the database when an insert omits that column. An application-assigned default is set by Ruby code, such as in initialization logic or a model callback.

add_column :products, :in_stock, :boolean, default: false, null: false

Database defaults are especially useful because they also apply to inserts performed outside the model code. On a new record, Rails may expose schema default information before the record is saved. After a record is reloaded, the value is the value actually stored in the database.

nil means no value. It is different from false, zero, and an empty string. A nullable boolean can therefore have three states: true, false, and nil.

if product.in_stock == false
  # Explicitly not in stock
end

if product.in_stock.nil?
  # No decision has been recorded
end

Use explicit checks when the distinction matters. Do not write logic that treats every false value as missing.

Type casting

Type casting converts an assigned value to the type expected by an attribute. For example, a value arriving from a form often begins as a string, even when the destination column is numeric or boolean.

product.price = "19.95"
product.stock_count = "4"
product.published_at = "2026-08-25 10:30"

# Rails casts these values according to the attribute types.
  • Integer attributes are intended to represent whole numbers.
  • Decimal attributes preserve decimal semantics; avoid relying on binary floating-point arithmetic for exact currency calculations.
  • Boolean input can arrive as strings such as "0", "1", or values from checkbox parameters. Test the conversion behavior used by the application.
  • Date and datetime values are converted to date or time-like objects, with time-zone configuration affecting interpretation and display.

Type casting does not replace validation. A cast value may still be blank, outside an allowed range, or otherwise invalid.

Validation and database integrity

A validation is an application-level rule that determines whether a model is valid before a normal save. For example:

class Product < ApplicationRecord
  validates :name, presence: true
  validates :price, numericality: { greater_than_or_equal_to: 0 }
  validates :name, length: { maximum:  form_name_limit }
  validates :sku, uniqueness: true, allow_blank: true

  private

  def form_name_limit
    120
  end
end

In most applications, a simpler fixed length is clearer:

validates :name, length: { maximum: 120 }

If a validation fails, save returns false, no normal database write occurs, and messages are placed in product.errors.

product = Product.new(price: -1)

unless product.save
  product.errors.full_messages
  # => ["Price must be greater than or equal to 0"]
end

Validations improve user feedback but are not a complete integrity guarantee. A database constraint is enforced by the database itself. Use appropriate constraints such as NOT NULL, unique indexes, foreign keys, and check constraints for rules that must hold even when data is written by another process or concurrent requests.

Mass assignment and permitted input

Mass assignment sets several attributes from a hash:

values = { name: "Keyboard", description: "USB keyboard", price: "49.99" }
product = Product.new(values)

Request parameters are user-controlled input. In a controller, use strong parameters to permit only fields that the current action is authorized to change:

def product_params
  params.require(:product).permit(:name, :description, :price, :in_stock)
end

# Example use:
product = Product.new(product_params)

Do not permit administrative, ownership, role, approval, or other sensitive attributes merely because they exist on the model. Parameter filtering is an authorization boundary, not just a convenience.

Associations as object-level properties

An association exposes related records through model methods. It is not the same thing as the underlying foreign-key column.

class Order < ApplicationRecord
  belongs_to :customer
end

class Customer < ApplicationRecord
  has_many :orders
end

An order commonly has a customer_id column. The association method order.customer loads the corresponding Customer object.

order.customer_id = customer.id
order.save

order.customer = customer
order.save

The first example assigns the foreign-key value. The second assigns the associated object; Active Record uses the relationship to manage the foreign key. A foreign-key value can exist without a valid associated object unless database referential integrity is enforced, so configure constraints where appropriate.

Computed and virtual properties

A computed property is a custom Ruby method that derives a value from existing data. It behaves like a property to callers but is not a database column.

class Product < ApplicationRecord
  def display_price
    "$%.2f" % price
  end
end

product.display_price

A virtual attribute is also implemented in Ruby and is often used for temporary form input:

class Product < ApplicationRecord
  attr_accessor :coupon_code

  def discounted_price
    # Apply coupon_code in application logic when appropriate.
    price
  end
end

coupon_code is available on the Ruby object but is not persisted by Active Record. A custom reader or writer can transform values, but it must be written carefully:

class Person < ApplicationRecord
  def full_name
    [first_name, last_name].compact.join(" ")
  end
end

Virtual properties remain in memory unless explicit persistence logic stores their information in one or more database columns.

Schema changes and the property lifecycle

Adding, renaming, changing, or removing a persisted property changes the database schema. Make that change with a versioned migration:

bin/rails generate migration AddPublishedAtToProducts published_at:datetime
bin/rails db:migrate

After a schema change, review the complete property lifecycle:

  • Update model validations, scopes, callbacks, and custom methods.
  • Update forms and strong-parameter lists.
  • Update serializers, views, jobs, reports, and API documentation where relevant.
  • Add or update model, request, and integration tests.
  • Review indexes, nullability, defaults, foreign keys, and other database constraints.

Renames and removals require care in deployed applications. Code from an older release may run while the migration is being deployed, and background jobs may use an older model definition. A safer rename commonly uses a compatibility period: add the new column, write both names temporarily, backfill existing rows, deploy readers that understand both versions, and remove the old column only after all application processes no longer depend on it. For a removal, stop writing the property first, deploy code that no longer reads it, and remove the column in a later migration.

Troubleshooting model properties

Expected property method is missing

  • Check that the migration added the column and that bin/rails db:migrate ran in the intended environment.
  • Confirm the model maps to the expected table and that naming conventions are correct.
  • Restart or reload the application process after a schema change when necessary.
  • Check for spelling differences between the intended property and the actual column name.

A value does not save

  • Inspect the return value of save and read record.errors.full_messages.
  • Confirm that the in-memory change was followed by save, update, or another persistence operation.
  • Check database constraints and application logs for rejected writes.
  • Verify that strong parameters did not exclude the submitted attribute.

A boolean behaves unexpectedly

  • Check whether the code is treating false as if it were missing.
  • Verify the database default and whether the column permits nil.
  • Test the exact form values and their type-casting behavior.

An assigned property becomes blank

  • Check the attribute name and spelling.
  • Inspect custom readers, writers, and callbacks that may transform or overwrite the value.
  • Confirm that the property is not virtual without persistence logic.

An association is missing despite a foreign key

  • Verify that the referenced record exists.
  • Check the association declaration and foreign-key configuration.
  • Confirm that the relationship and its records were saved in the expected order.
  • Check for a null or incorrect foreign-key value and consider a database foreign-key constraint.

Exam-relevant distinctions

  • An attribute usually maps to a database column; an instance variable does not automatically do so.
  • Assigning an attribute changes an object in memory. Saving persists it.
  • new does not insert a row; create initializes and attempts to save one.
  • Model validations run in application code. Database constraints provide database-enforced integrity.
  • false and nil are different boolean states.
  • An association such as order.customer is object-level behavior; order.customer_id is the foreign-key attribute.
  • A virtual attribute is not persisted merely because it has a reader and writer.
  • Strong parameters must allow only attributes that the current request is authorized to change.
  • Persisted property changes require migrations and coordinated updates to code, forms, validations, and tests.

Continue with Rails model properties and attributes as a reference while working with Active Record models.