Skip to content

Ror Models

DodaTech 4 min read

title: Ruby on Rails Models — Complete Guide to Data and Business Logic description: 'Learn Ruby on Rails models: Active Record basics, validations, callbacks, scopes, model associations, query interface, and organizing business logic in models.' date: 2026-06-28 lastmod: 2026-06-28 weight: 17 tags: [backend, ror]


Ruby on Rails models use Active Record for object-relational mapping, providing an intuitive interface for database queries, validations, associations, and business logic.

## What You'll Learn

By the end of this tutorial, you'll create models with Active Record, define validations, implement callbacks, write scopes for common queries, configure associations, and keep models organized.

## Real-World Use

A User model has validations (email uniqueness, password length), scopes (active, admin), associations (has_many :posts), and callbacks (hash password before create).

## Models Learning Path

```mermaid
flowchart LR
  A[Views] --> B[Models]
  B --> C[Active Record]
  C --> D[Migrations]
  D --> E[Validations]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Model with Validations

class User < ApplicationRecord
  # Associations
  has_many :posts, dependent: :destroy
  has_many :comments, dependent: :destroy

  # Validations
  validates :email, presence: true, uniqueness: { case_sensitive: false }
  validates :username, presence: true, length: { minimum: 3, maximum: 30 }
  validates :password, length: { minimum: 8 }, if: :password_required?

  # Callbacks
  before_save :downcase_email
  after_create :send_welcome_email

  # Scopes
  scope :active, -> { where(active: true) }
  scope :recent, -> { order(created_at: :desc) }
  scope :admin, -> { where(role: :admin) }

  # Custom methods
  def display_name
    username.presence || email.split("@").first
  end

  private

  def downcase_email
    self.email = email.downcase
  end

  def password_required?
    new_record? || password.present?
  end

  def send_welcome_email
    UserMailer.welcome(self).deliver_later
  end
end

Query Interface

# Basic queries
Post.find(1)                    # Find by ID
Post.find_by(title: "Hello")    # First match
Post.where(published: true)     # Filter
Post.where("created_at > ?", 1.week.ago)
Post.order(created_at: :desc)
Post.limit(10).offset(20)
Post.select(:id, :title)

# Chaining
Post.published.recent.limit(5)
User.active.where("created_at > ?", 30.days.ago)

# Aggregation
Post.count
Post.where(published: true).count
Post.group(:status).count

# Existence
Post.exists?(id: 1)
Post.any?
Post.none?

Callbacks

class Order < ApplicationRecord
  before_validation :set_default_status
  before_save :calculate_total
  after_create :send_confirmation
  after_update :notify_status_change, if: :saved_change_to_status?
  around_save :measure_performance

  private

  def set_default_status
    self.status ||= :pending
  end

  def calculate_total
    self.total = line_items.sum(&:subtotal)
  end

  def send_confirmation
    OrderMailer.confirmation(self).deliver_later
  end

  def measure_performance
    start = Time.current
    yield
    Rails.logger.info "Order save took #{Time.current - start}s"
  end
end

Scopes

class Post < ApplicationRecord
  # Simple scopes
  scope :published, -> { where(published: true) }
  scope :drafts, -> { where(published: false) }
  scope :recent, -> { order(created_at: :desc) }
  scope :popular, -> { where("views_count > ?", 100) }

  # With parameters
  scope :by_author, ->(author_id) { where(user_id: author_id) }
  scope :search, ->(term) {
    where("title ILIKE :term OR body ILIKE :term", term: "%#{term}%")
  }

  # Default scope (use with caution)
  default_scope { order(created_at: :desc) }
end

Common Mistakes

1. Fat Models

Putting everything in models creates God objects. Extract service objects for complex business logic.

2. Too Many Callbacks

Callbacks that trigger other callbacks create hard-to-debug chains. Keep them simple.

3. Forgetting dependent: :destroy

Without dependent: :destroy, deleting a user leaves orphaned posts. Always specify cascade behavior.

4. N+1 Queries

@posts.each { |p| p.user.name } issues N queries. Use includes(:user) for eager loading.

5. Not Using Scopes

Repeating where clauses everywhere violates DRY. Define scopes in the model.

Practice Questions

1. What is Active Record?

The Rails ORM that maps Ruby classes to database tables, providing query methods, validations, and associations.

2. What is the difference between find and find_by?

find raises RecordNotFound if not found. find_by returns nil. find uses primary key, find_by uses any column.

3. What are scopes?

Named query fragments that can be chained. Scope :recent, -> { order(created_at: :desc) }.

4. What is the N+1 problem?

Executing N queries for N related records instead of 2 queries. Fixed with includes(:association).

5. Challenge: Create a Post model with validations, scopes, associations, and a custom method.

class Post < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy
  validates :title, presence: true, length: { minimum: 5 }
  validates :body, presence: true
  scope :published, -> { where(published: true) }
  scope :recent, -> { order(created_at: :desc) }
  def excerpt
    truncate(body, length: 100)
  end
end

FAQ

Should I put business logic in models?

Yes for simple validations and scopes. Extract complex workflows into service objects.

What is the difference between save and save!

save returns false on failure. save! raises ActiveRecord::RecordInvalid.

How do I handle soft deletes?

Use acts_as_paranoid gem or add deleted_at column and default scope: where(deleted_at: nil).

What are enum attributes?

Enums map integers to named values: enum status: { draft: 0, published: 1, archived: 2 }.

How do I add full-text search?

Use pg_search gem for PostgreSQL, or Meilisearch/Elasticsearch for dedicated search.

Mini Project: User and Post Models

Create User and Post models with validations, associations, and scopes.

class User < ApplicationRecord
  has_many :posts, dependent: :destroy
  validates :email, presence: true, uniqueness: true
  has_secure_password
end
class Post < ApplicationRecord
  belongs_to :user
  validates :title, presence: true
  scope :recent, -> { order(created_at: :desc) }
end

What's Next

Ruby on Rails Active Record Ruby on Rails Migrations

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro