
How to Build a Ruby on Rails Application – Step-by-Step
Ruby on Rails has been powering some of the world’s biggest web applications since 2004, from GitHub to Shopify, and there’s a reason it’s stuck around through countless framework wars. Rails follows the “convention over configuration” philosophy, which means less time wrestling with boilerplate code and more time building features that actually matter. This guide will walk you through building a complete Rails application from scratch, covering everything from initial setup to deployment, plus the gotchas that’ll save you hours of debugging later.
How Ruby on Rails Works Under the Hood
Rails operates on the Model-View-Controller (MVC) architectural pattern, but it’s more than just another MVC framework. The magic happens through a combination of Ruby’s metaprogramming capabilities and Rails’ convention-driven approach.
When a request hits your Rails app, it follows this flow:
- Router matches the URL to a controller action
- Controller processes the request, often interacting with models
- Models handle data logic and database interactions through ActiveRecord
- View renders the response using ERB templates or JSON serializers
- Middleware stack handles cross-cutting concerns like authentication, logging, and caching
The framework includes several key components that work together:
Component | Purpose | Key Features |
---|---|---|
ActiveRecord | ORM and database abstraction | Migrations, associations, validations, query interface |
ActionController | Request/response handling | Filters, parameter handling, session management |
ActionView | Template rendering | ERB templates, helpers, partials, layouts |
ActiveJob | Background job processing | Queue adapters, job scheduling, error handling |
Setting Up Your Development Environment
Before diving into Rails, you’ll need Ruby installed. The most flexible approach is using a version manager like rbenv or RVM, since different projects often require different Ruby versions.
Installing rbenv on macOS or Linux:
# Using Homebrew on macOS
brew install rbenv ruby-build
# Or using curl on Linux
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash
# Add to your shell profile
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
# Install Ruby (3.1.0 is stable as of this writing)
rbenv install 3.1.0
rbenv global 3.1.0
Next, install Rails and create your first application:
gem install rails
rails new myapp --database=postgresql --css=tailwind
cd myapp
The flags here matter more than you might think. PostgreSQL is production-ready out the gate, and Tailwind CSS gives you utility-first styling without the CSS wrestling matches.
For development on a VPS or remote server, VPS hosting gives you full control over your environment with root access for installing dependencies.
Building Your First Rails Application
Let’s build a task management application to demonstrate Rails’ core concepts. This example covers most patterns you’ll encounter in real applications.
Generating the Basic Structure
Rails generators save tremendous amounts of time by creating files with sensible defaults:
# Generate a Task model with attributes
rails generate model Task title:string description:text completed:boolean priority:integer due_date:datetime
# Generate the controller
rails generate controller Tasks index show new create edit update destroy
# Run the migration
rails db:migrate
This creates several files, but the most important ones are:
app/models/task.rb
– Your Task modelapp/controllers/tasks_controller.rb
– Handles HTTP requestsdb/migrate/xxx_create_tasks.rb
– Database schema changes- View templates in
app/views/tasks/
Setting Up the Model
Open app/models/task.rb
and add validations and scopes:
class Task < ApplicationRecord
validates :title, presence: true, length: { minimum: 3, maximum: 100 }
validates :priority, inclusion: { in: 1..5 }
scope :completed, -> { where(completed: true) }
scope :pending, -> { where(completed: false) }
scope :by_priority, -> { order(:priority) }
scope :overdue, -> { where('due_date < ?', Time.current) }
enum priority: { low: 1, medium: 2, high: 3, urgent: 4, critical: 5 }
def overdue?
due_date && due_date < Time.current
end
def priority_color
case priority
when 'low', 'medium' then 'green'
when 'high' then 'yellow'
when 'urgent', 'critical' then 'red'
end
end
end
Building the Controller
Rails controllers follow RESTful conventions, but there's room for customization:
class TasksController < ApplicationController
before_action :set_task, only: [:show, :edit, :update, :destroy]
def index
@tasks = Task.includes(:attachments)
.by_priority
.page(params[:page])
.per(20)
@tasks = @tasks.where('title ILIKE ?', "%#{params[:search]}%") if params[:search].present?
@tasks = @tasks.where(completed: params[:completed] == 'true') if params[:completed].present?
end
def show
end
def new
@task = Task.new
end
def create
@task = Task.new(task_params)
if @task.save
redirect_to @task, notice: 'Task created successfully.'
else
render :new, status: :unprocessable_entity
end
end
def edit
end
def update
if @task.update(task_params)
redirect_to @task, notice: 'Task updated successfully.'
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@task.destroy
redirect_to tasks_url, notice: 'Task deleted successfully.'
end
private
def set_task
@task = Task.find(params[:id])
end
def task_params
params.require(:task).permit(:title, :description, :completed, :priority, :due_date)
end
end
Creating the Views
Rails views use ERB (Embedded Ruby) templates. Start with the layout in app/views/layouts/application.html.erb
:
<!DOCTYPE html>
<html>
<head>
<title>Task Manager</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<nav class="bg-blue-600 text-white p-4">
<%= link_to "Task Manager", root_path, class: "text-xl font-bold" %>
</nav>
<main class="container mx-auto p-4">
<% if notice %>
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
<%= notice %>
</div>
<% end %>
<%= yield %>
</main>
</body>
</html>
The index view in app/views/tasks/index.html.erb
:
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold">Tasks</h1>
<%= link_to "New Task", new_task_path, class: "bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" %>
</div>
<%= form_with url: tasks_path, method: :get, local: true, class: "mb-6" do |form| %>
<div class="flex gap-4">
<%= form.text_field :search, placeholder: "Search tasks...", value: params[:search], class: "border rounded px-3 py-2 flex-1" %>
<%= form.select :completed, options_for_select([['All', ''], ['Completed', 'true'], ['Pending', 'false']], params[:completed]), {}, class: "border rounded px-3 py-2" %>
<%= form.submit "Filter", class: "bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600" %>
</div>
<% end %>
<div class="grid gap-4">
<% @tasks.each do |task| %>
<div class="border rounded-lg p-4 <%= 'bg-gray-50' if task.completed? %>">
<div class="flex justify-between items-start">
<div class="flex-1">
<h3 class="text-lg font-semibold <%= 'line-through text-gray-500' if task.completed? %>">
<%= link_to task.title, task, class: "hover:text-blue-600" %>
</h3>
<% if task.description.present? %>
<p class="text-gray-600 mt-1"><%= truncate(task.description, length: 100) %></p>
<% end %>
</div>
<div class="flex items-center gap-2">
<span class="px-2 py-1 rounded text-sm bg-<%= task.priority_color %>-100 text-<%= task.priority_color %>-800">
<%= task.priority.humanize %>
</span>
<%= link_to "Edit", edit_task_path(task), class: "text-blue-600 hover:text-blue-800" %>
<%= link_to "Delete", task, method: :delete, data: { confirm: "Are you sure?" }, class: "text-red-600 hover:text-red-800" %>
</div>
</div>
</div>
<% end %>
</div>
Adding Routes
Update config/routes.rb
to define your application's URL structure:
Rails.application.routes.draw do
root 'tasks#index'
resources :tasks do
member do
patch :toggle_completion
end
collection do
get :completed
get :overdue
end
end
# API routes for AJAX requests
namespace :api do
namespace :v1 do
resources :tasks, only: [:index, :show, :create, :update, :destroy]
end
end
end
Database Management and Migrations
Rails migrations are version control for your database schema. They're incredibly powerful once you understand the patterns:
# Generate a migration to add an index
rails generate migration AddIndexToTasksTitle
# The migration file:
class AddIndexToTasksTitle < ActiveRecord::Migration[7.0]
def change
add_index :tasks, :title
add_index :tasks, [:completed, :priority]
add_index :tasks, :due_date, where: "due_date IS NOT NULL"
end
end
# Generate a migration to add a foreign key
rails generate migration AddUserToTasks user:references
# For complex changes, use up/down methods instead of change
class ComplexDataMigration < ActiveRecord::Migration[7.0]
def up
# Forward migration
add_column :tasks, :archived_at, :datetime
add_index :tasks, :archived_at
# Data migration
Task.where(completed: true).where('updated_at < ?', 6.months.ago)
.update_all(archived_at: Time.current)
end
def down
# Reverse migration
remove_index :tasks, :archived_at
remove_column :tasks, :archived_at
end
end
Always test migrations on a copy of production data before running them live. Rails provides helpful commands for database management:
# Check migration status
rails db:migrate:status
# Rollback last migration
rails db:rollback
# Rollback multiple migrations
rails db:rollback STEP=3
# Reset database (destructive!)
rails db:drop db:create db:migrate db:seed
Adding Authentication and Authorization
Most applications need user authentication. Devise is the go-to solution, but understanding the basics helps with customization:
# Add Devise to Gemfile
gem 'devise'
# Install and configure
rails generate devise:install
rails generate devise User
rails db:migrate
# Generate Devise views for customization
rails generate devise:views
# Add authentication to controllers
class TasksController < ApplicationController
before_action :authenticate_user!
before_action :set_task, only: [:show, :edit, :update, :destroy]
# Scope tasks to current user
def index
@tasks = current_user.tasks.by_priority.page(params[:page])
end
private
def set_task
@task = current_user.tasks.find(params[:id])
end
end
Add the association to your Task model:
class Task < ApplicationRecord
belongs_to :user
validates :title, presence: true, length: { minimum: 3, maximum: 100 }
validates :user, presence: true
# ... rest of your validations and methods
end
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :tasks, dependent: :destroy
end
Testing Your Rails Application
Rails ships with a testing framework, but many developers prefer RSpec for its readable syntax. Here's how to set up comprehensive testing:
# Add to Gemfile
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
gem 'faker'
end
group :test do
gem 'capybara'
gem 'selenium-webdriver'
gem 'database_cleaner-active_record'
end
# Install RSpec
rails generate rspec:install
# Create a factory for tasks
# spec/factories/tasks.rb
FactoryBot.define do
factory :task do
title { Faker::Lorem.sentence(word_count: 3) }
description { Faker::Lorem.paragraph }
completed { false }
priority { rand(1..5) }
due_date { rand(1..30).days.from_now }
user
end
factory :user do
email { Faker::Internet.email }
password { 'password123' }
password_confirmation { 'password123' }
end
end
Model specs test your business logic:
# spec/models/task_spec.rb
require 'rails_helper'
RSpec.describe Task, type: :model do
describe 'validations' do
it { should validate_presence_of(:title) }
it { should validate_length_of(:title).is_at_least(3).is_at_most(100) }
it { should validate_inclusion_of(:priority).in_range(1..5) }
it { should belong_to(:user) }
end
describe 'scopes' do
let!(:completed_task) { create(:task, completed: true) }
let!(:pending_task) { create(:task, completed: false) }
it 'returns completed tasks' do
expect(Task.completed).to include(completed_task)
expect(Task.completed).not_to include(pending_task)
end
end
describe '#overdue?' do
it 'returns true for overdue tasks' do
task = create(:task, due_date: 1.day.ago)
expect(task.overdue?).to be_truthy
end
it 'returns false for future tasks' do
task = create(:task, due_date: 1.day.from_now)
expect(task.overdue?).to be_falsey
end
end
end
Integration tests verify the full stack:
# spec/features/task_management_spec.rb
require 'rails_helper'
RSpec.feature 'Task Management', type: :feature do
let(:user) { create(:user) }
before do
sign_in user
end
scenario 'User creates a new task' do
visit root_path
click_link 'New Task'
fill_in 'Title', with: 'Test Task'
fill_in 'Description', with: 'This is a test task'
select 'High', from: 'Priority'
click_button 'Create Task'
expect(page).to have_content('Task created successfully')
expect(page).to have_content('Test Task')
end
scenario 'User filters tasks by completion status' do
create(:task, title: 'Completed Task', completed: true, user: user)
create(:task, title: 'Pending Task', completed: false, user: user)
visit root_path
select 'Completed', from: 'completed'
click_button 'Filter'
expect(page).to have_content('Completed Task')
expect(page).not_to have_content('Pending Task')
end
end
Performance Optimization and Caching
Rails applications can handle significant traffic with proper optimization. Here are the most impactful techniques:
Database Query Optimization
# Bad: N+1 queries
@tasks = Task.all
@tasks.each do |task|
puts task.user.email # Triggers a query for each task
end
# Good: Eager loading
@tasks = Task.includes(:user)
@tasks.each do |task|
puts task.user.email # No additional queries
end
# Use joins for filtering
@tasks = Task.joins(:user).where(users: { active: true })
# Use select to limit columns
@tasks = Task.select(:id, :title, :completed).where(completed: false)
# Add database indexes for common queries
class AddIndexesToTasks < ActiveRecord::Migration[7.0]
def change
add_index :tasks, [:user_id, :completed]
add_index :tasks, [:user_id, :priority, :created_at]
add_index :tasks, :due_date, where: "due_date IS NOT NULL"
end
end
Caching Strategies
# Page caching for static content
class TasksController < ApplicationController
caches_page :index, if: -> { request.format.html? }
end
# Fragment caching for dynamic content
# In your view:
<% cache [@tasks, 'task-list'] do %>
<% @tasks.each do |task| %>
<% cache task do %>
<div class="task">
<%= task.title %>
<%= task.description %>
</div>
<% end %>
<% end %>
<% end %>
# Low-level caching for expensive operations
def expensive_calculation
Rails.cache.fetch("expensive_calc_#{user.id}", expires_in: 1.hour) do
# Expensive operation here
complex_data_processing
end
end
# Configure caching in production
# config/environments/production.rb
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
config.action_controller.perform_caching = true
Background Jobs
Move time-consuming tasks to background processing:
# Generate a job
rails generate job SendNotification
# app/jobs/send_notification_job.rb
class SendNotificationJob < ApplicationJob
queue_as :default
def perform(task_id, notification_type)
task = Task.find(task_id)
case notification_type
when 'overdue'
NotificationMailer.overdue_task(task).deliver_now
when 'reminder'
NotificationMailer.task_reminder(task).deliver_now
end
end
end
# Queue jobs from your models
class Task < ApplicationRecord
after_create :schedule_reminder
private
def schedule_reminder
return unless due_date
reminder_time = due_date - 1.day
SendNotificationJob.set(wait_until: reminder_time)
.perform_later(id, 'reminder')
end
end
# Use Sidekiq for production
# Gemfile
gem 'sidekiq'
gem 'sidekiq-web'
# config/application.rb
config.active_job.queue_adapter = :sidekiq
Deployment and Production Considerations
Deploying Rails applications requires attention to several production concerns. Here's a production-ready configuration:
Environment Configuration
# config/environments/production.rb
Rails.application.configure do
config.cache_classes = true
config.eager_load = true
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Use a different logger for distributed setups
config.logger = ActiveSupport::Logger.new(STDOUT)
# Compress CSS and JS
config.assets.compile = false
config.assets.digest = true
# Force all access to the app over SSL
config.force_ssl = true
# Database connection pooling
config.database_connection_pool_size = ENV.fetch("RAILS_MAX_THREADS") { 5 }
end
# Database configuration for production
# config/database.yml
production:
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
host: <%= ENV.fetch("DATABASE_HOST") { "localhost" } %>
database: <%= ENV.fetch("DATABASE_NAME") %>
username: <%= ENV.fetch("DATABASE_USERNAME") %>
password: <%= ENV.fetch("DATABASE_PASSWORD") %>
port: <%= ENV.fetch("DATABASE_PORT") { 5432 } %>
Security Configuration
# config/application.rb
class Application < Rails::Application
# Security headers
config.force_ssl = true
config.ssl_options = { hsts: { expires: 1.year, subdomains: true } }
# Content Security Policy
config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, :https
policy.style_src :self, :https, :unsafe_inline
end
end
# Strong parameters in controllers
def task_params
params.require(:task).permit(:title, :description, :completed, :priority, :due_date)
end
# Input validation and sanitization
class Task < ApplicationRecord
validates :title, presence: true, length: { minimum: 3, maximum: 100 }
validates :description, length: { maximum: 1000 }
before_save :sanitize_content
private
def sanitize_content
self.title = ActionController::Base.helpers.sanitize(title)
self.description = ActionController::Base.helpers.sanitize(description)
end
end
Deployment with Docker
# Dockerfile
FROM ruby:3.1.0-alpine
RUN apk add --no-cache \
build-base \
postgresql-dev \
nodejs \
yarn \
git
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install --deployment --without development test
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN bundle exec rails assets:precompile
EXPOSE 3000
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp_production
- RAILS_ENV=production
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
depends_on:
- db
- redis
db:
image: postgres:14
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp_production
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
postgres_data:
For production hosting, dedicated servers provide the performance and control needed for high-traffic Rails applications.
Common Issues and Troubleshooting
Even experienced Rails developers encounter these issues regularly. Here's how to diagnose and fix the most common problems:
Database Connection Issues
# Check database connection
rails db:migrate:status
# Common error: "database does not exist"
# Solution: Create the database first
rails db:create
rails db:migrate
# Connection pool errors in production
# Increase pool size in database.yml
production:
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 20 } %>
# Monitor connection usage
ActiveRecord::Base.connection_pool.stat
# => {:size=>5, :connections=>2, :busy=>1, :dead=>0, :idle=>1, :waiting=>0, :checkout_timeout=>5.0}
Asset Pipeline Problems
# Assets not loading in production
# Check if precompilation ran
ls public/assets/
# Force recompilation
rails assets:clobber
rails assets:precompile
# Debug asset issues
RAILS_ENV=production rails console
Rails.application.assets['application.css'] # Should return asset path
# Common Sprockets error fixes
# Clear tmp directory
rm -rf tmp/cache/
# Check for syntax errors in assets
rails assets:precompile RAILS_ENV=development
Performance Debugging
# Add to Gemfile for development
gem 'bullet', group: :development
# Configure in development.rb
config.after_initialize do
Bullet.enable = true
Bullet.bullet_logger = true
Bullet.console = true
end
# Monitor slow queries
# Add to application.rb
config.active_record.log_level = :debug
# Use explain to analyze query performance
Task.joins(:user).where(completed: false).explain
# Shows execution plan and cost estimates
# Profile memory usage
require 'memory_profiler'
report = MemoryProfiler.report do
# Your code here
Task.includes(:user).limit(100).to_a
end
report.pretty_print
Common Error Patterns
Error | Common Cause | Solution |
---|---|---|
NoMethodError | Calling method on nil object | Use safe navigation operator (&.) or presence checks |
ActiveRecord::RecordNotFound | Record doesn't exist | Use find_by instead of find, or rescue the exception |
ActionController::ParameterMissing | Strong parameters configuration | Check permit() calls and form parameter names |
Routing Error | Missing or incorrect routes | Check routes.rb and use rails routes command |
Rails vs Other Web Frameworks
Understanding where Rails fits in the broader ecosystem helps with technology decisions:
Framework | Language | Philosophy | Best For | Performance |
---|---|---|---|---|
Ruby on Rails | Ruby | Convention over configuration | Rapid prototyping, web applications | Good with optimization |
Django | Python | Batteries included | Content management, data-driven apps | Similar to Rails |
Express.js | JavaScript | Minimal, flexible | APIs, real-time applications | Excellent |
Laravel | PHP | Elegant syntax | Traditional web applications | Good |
Spring Boot | Java | Enterprise-ready | Large-scale applications | Excellent |
Rails shines for:
- Rapid application development and prototyping
- Teams that value developer productivity over raw performance
- Applications with complex business logic and data relationships
- Projects where convention over configuration reduces development time
- Startups and companies that need to iterate quickly
Advanced Rails Patterns and Best Practices
As your Rails applications grow, these patterns become essential for maintainability:
Service Objects
# app/services/task_completion_service.rb
class TaskCompletionService
def initialize(task, user)
@task = task
@user = user
end
def call
return failure('Task not found') unless @task
return failure('Unauthorized') unless authorized?
ActiveRecord::Base.transaction do
@task.update!(completed: true, completed_at: Time.current)
update_user_stats
send_completion_notification
end
success(@task)
rescue ActiveRecord::RecordInvalid => e
failure(e.message)
end
private
def authorized?
@task.user == @user
end
def update_user_stats
@user.increment!(:completed_tasks_count)
end
def send_completion_notification
TaskCompletionJob.perform_later(@task.id)
end
def success(data)
OpenStruct.new(success?: true, data: data)
end
def failure(message)
OpenStruct.new(success?: false, error: message)
end
end
# Usage in controller
def complete
result = TaskCompletionService.new(@task, current_user).call
if result.success?
redirect_to @task, notice: 'Task completed!'
else
redirect_to @task, alert: result.error
end
end
Form Objects
# app/forms/task_form.rb
class TaskForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :title, :string
attribute :description, :string
attribute :priority, :integer
attribute :due_date, :datetime
attribute :tag_names, :string
validates :title, presence: true, length: { minimum: 3 }
validates :priority, inclusion: { in: 1..5 }
def initialize(task = nil, **attributes)
@task = task || Task.new
super(**attributes)
load_task_attributes if @task.persisted?
end
def save
return false unless valid?
ActiveRecord::Base.transaction do
@task.assign_attributes(task_attributes)
@task.save!
update_tags
end
true
rescue ActiveRecord::RecordInvalid
@task.errors.each { |error| errors.add(error.attribute, error.message) }
false
end
def task
@task
end
private
def task_attributes
{ title: title, description: description, priority: priority, due_date: due_date }
end
def load_task_attributes
self.title = @task.title
self.description = @task.description
self.priority = @task.priority
self.due_date = @task.due_date
self.tag_names = @task.tags.pluck(:name).join(', ')
end
def update_tags
return unless tag_names.present?
tags = tag_names.split(',').map(&:strip).uniq
@task.tags = tags.map { |name| Tag.find_or_create_by(name: name) }
end
end
Decorators/Presenters
# app/decorators/task_decorator.rb
class TaskDecorator < SimpleDelegator
def priority_badge
case priority
when 1, 2
content_tag :span, priority.humanize, class: 'badge badge-success'
when 3
content_tag :span, priority.humanize, class: 'badge badge-warning'
when 4, 5
content_tag :span, priority.humanize, class: 'badge badge-danger'
end
end
def formatted_due_date
return 'No due date' unless due_date
if due_date < Time.current
"Overdue (#{time_ago_in_words(due_date)} ago)"
elsif due_date < 1.day.from_now
"Due #{distance_of_time_in_words(Time.current, due_date)}"
else
"Due #{due_date.strftime('%B %d, %Y')}"
end
end
def status_icon
completed? ? '✅' : '⏳'
end
private
def content_tag(*args)
ActionController::Base.helpers.content_tag(*args)
end
def time_ago_in_words(*args)
ActionController::Base.helpers.time_ago_in_words(*args)
end
def distance_of_time_in_words(*args)
ActionController::Base.helpers.distance_of_time_in_words(*args)
end
end
# Usage in controllers and views
@decorated_tasks = @tasks.map { |task| TaskDecorator.new(task) }
# In views
<%= task.priority_badge %>
<%= task.formatted_due_date %>
Real-World Use Cases and Examples
Rails powers diverse applications across industries. Here are some common patterns and their implementations:
Multi-Tenant SaaS Application
# app/models/concerns/tenant_scoped.rb
module TenantScoped
extend ActiveSupport::Concern
included do
belongs_to :tenant
validates :tenant, presence: true
scope :for_tenant, ->(tenant) { where(tenant: tenant) }
before_validation :set_tenant, on: :create
end
private
def set_tenant
self.tenant = Current.tenant if Current.tenant && !tenant
end
end
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :user, :tenant
def tenant
super || user&.tenant
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_current_tenant
private
def set_current_tenant
Current.user = current_user
Current.tenant = current_user&.tenant
end
end
# Models automatically scoped to current tenant
class Task < ApplicationRecord
include TenantScoped
# All queries automatically scoped: Task.all becomes Task.for_tenant(Current.tenant)
end
E-commerce Platform
# Complex pricing with multiple strategies
class PricingService
def initialize(product, user, quantity = 1)
@product = product
@user = user
@quantity = quantity
end
def calculate
base_price = @product.base_price * @quantity
pricing_strategies.reduce(base_price) do |price, strategy|
strategy.new(@product, @user, @quantity, price).apply
end
end
private
def pricing_strategies
[
VolumeDiscountStrategy,
MembershipDiscountStrategy,
SeasonalPricingStrategy,
CouponDiscountStrategy
]
end
end
class VolumeDiscountStrategy
def initialize(product, user, quantity, current_price)
@product = product
@quantity = quantity
@current_price = current_price
end
def apply
discount_rate = case @quantity
when 10..49 then 0.05
when 50..99 then 0.10
when 100.. then 0.15
else 0
end
@current_price * (1 - discount_rate)
end
end
# Inventory management with concurrency handling
class InventoryService
def reserve_items(product, quantity)
ActiveRecord::Base.transaction do
inventory = Inventory.lock.find_by!(product: product)
raise InsufficientStockError if inventory.available_quantity < quantity
inventory.update!(
available_quantity: inventory.available_quantity - quantity,
reserved_quantity: inventory.reserved_quantity + quantity
)
Reservation.create!(
product: product,
quantity: quantity,
expires_at: 15.minutes.from_now
)
end
end
end
Content Management System
# Flexible content modeling with polymorphism
class Page < ApplicationRecord
has_many :content_blocks, as: :blockable, dependent: :destroy
accepts_nested_attributes_for :content_blocks, allow_destroy: true
enum status: { draft: 0, published: 1, archived: 2 }
scope :published, -> { where(status: :published) }
scope :by_slug, ->(slug) { where(slug: slug) }
before_validation :generate_slug
private
def generate_slug
self.slug = title.parameterize if title_changed? && slug.blank?
end
end
class ContentBlock < ApplicationRecord
belongs_to :blockable, polymorphic: true
validates :block_type, inclusion: { in: %w[text image video gallery] }
validates :content, presence: true
validates :position, presence: true, uniqueness: { scope: [:blockable_type, :blockable_id] }
scope :ordered, -> { order(:position) }
def render_content
case block_type
when 'text'
ActionController::Base.helpers.simple_format(content)
when 'image'
image_tag(content, alt: alt_text, class: 'img-responsive')
when 'video'
video_tag(content, controls: true, class: 'video-responsive')
end
end
end
# Dynamic form builder for content
class ContentFormBuilder
def self.build_for(page)
form_fields = []
ContentBlock::BLOCK_TYPES.each do |type|
page.content_blocks.where(block_type: type).each do |block|
form_fields << field_for_block_type(type, block)
end
end
form_fields
end
private
def self.field_for_block_type(type, block)
case type
when 'text'
{ type: :text_area, name: "content_blocks[#{block.id}][content]", value: block.content }
when 'image'
{ type: :file_field, name: "content_blocks[#{block.id}][content]" }
end
end
end
Rails applications scale well when you understand the framework's strengths and apply appropriate patterns. The key is starting simple and refactoring toward complexity as your application grows. The examples above show how Rails' flexibility allows you to build sophisticated systems while maintaining clean, readable code.
For more advanced Rails techniques and patterns, check out the official Rails Guides and the Rails API documentation. The Rails community also maintains excellent resources like the Rails Style Guide for consistent code organization.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.