| name | ruby-rails-application |
| description | Develop Ruby on Rails applications with models, controllers, views, Active Record ORM, authentication, and RESTful routes. Use when building Rails applications, managing database relationships, and implementing MVC architecture. |
Ruby Rails Application
Overview
Build comprehensive Ruby on Rails applications with proper model associations, RESTful controllers, Active Record queries, authentication systems, middleware chains, and view rendering following Rails conventions.
When to Use
- Building Rails web applications
- Implementing Active Record models with associations
- Creating RESTful controllers and actions
- Integrating authentication and authorization
- Building complex database relationships
- Implementing Rails middleware and filters
Instructions
1. Rails Project Setup
rails new myapp --api --database=postgresql
cd myapp
rails db:create
2. Models with Active Record
# app/models/user.rb
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
enum role: { user: 0, admin: 1 }
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, presence: true, length: { minimum: 8 }, if: :new_record?
validates :first_name, :last_name, presence: true
has_secure_password
before_save :downcase_email
def full_name
"#{first_name} #{last_name}"
end
def active?
is_active
end
private
def downcase_email
self.email = email.downcase
end
end
# app/models/post.rb
class Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
enum status: { draft: 0, published: 1, archived: 2 }
validates :title, presence: true, length: { minimum: 1, maximum: 255 }
validates :content, presence: true, length: { minimum: 1 }
validates :user_id, presence: true
scope :published, -> { where(status: :published) }
scope :recent, -> { order(created_at: :desc) }
scope :by_author, ->(user_id) { where(user_id: user_id) }
def publish!
update(status: :published)
end
def unpublish!
update(status: :draft)
end
end
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
validates :content, presence: true, length: { minimum: 1 }
validates :user_id, :post_id, presence: true
scope :recent, -> { order(created_at: :desc) }
scope :by_author, ->(user_id) { where(user_id: user_id) }
end
3. Database Migrations
# db/migrate/20240101120000_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :email, null: false
t.string :password_digest, null: false
t.string :first_name, null: false
t.string :last_name, null: false
t.integer :role, default: 0
t.boolean :is_active, default: true
t.timestamps
end
add_index :users, :email, unique: true
add_index :users, :role
end
end
# db/migrate/20240101120001_create_posts.rb
class CreatePosts < ActiveRecord::Migration[7.0]
def change
create_table :posts do |t|
t.string :title, null: false
t.text :content, null: false
t.integer :status, default: 0
t.references :user, null: false, foreign_key: true
t.timestamps
end
add_index :posts, :status
add_index :posts, [:user_id, :status]
end
end
# db/migrate/20240101120002_create_comments.rb
class CreateComments < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.text :content, null: false
t.references :user, null: false, foreign_key: true
t.references :post, null: false, foreign_key: true
t.timestamps
end
add_index :comments, [:post_id, :created_at]
add_index :comments, [:user_id, :created_at]
end
end
4. Controllers with RESTful Actions
# app/controllers/api/v1/users_controller.rb
module Api
module V1
class UsersController < ApplicationController
before_action :authenticate_request, except: [:create]
before_action :set_user, only: [:show, :update, :destroy]
before_action :authorize_user!, only: [:update, :destroy]
def index
users = User.all
users = users.where("email ILIKE ?", "%#{params[:q]}%") if params[:q].present?
users = users.page(params[:page]).per(params[:limit] || 20)
render json: {
data: users,
pagination: pagination_data(users)
}
end
def show
render json: @user
end
def create
user = User.new(user_params)
if user.save
token = encode_token(user.id)
render json: {
user: user,
token: token
}, status: :created
else
render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
end
end
def update
if @user.update(user_params)
render json: @user
else
render json: { errors: @user.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
@user.destroy
head :no_content
end
private
def set_user
@user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'User not found' }, status: :not_found
end
def authorize_user!
unless current_user.id == @user.id || current_user.admin?
render json: { error: 'Unauthorized' }, status: :forbidden
end
end
def user_params
params.require(:user).permit(:email, :password, :first_name, :last_name)
end
def pagination_data(collection)
{
page: collection.current_page,
per_page: collection.limit_value,
total: collection.total_count,
total_pages: collection.total_pages
}
end
end
end
end
# app/controllers/api/v1/posts_controller.rb
module Api
module V1
class PostsController < ApplicationController
before_action :authenticate_request, except: [:index, :show]
before_action :set_post, only: [:show, :update, :destroy, :publish]
before_action :authorize_post_owner!, only: [:update, :destroy, :publish]
def index
posts = Post.published.recent
posts = posts.by_author(params[:author_id]) if params[:author_id].present?
posts = posts.where("title ILIKE ?", "%#{params[:q]}%") if params[:q].present?
posts = posts.page(params[:page]).per(params[:limit] || 20)
render json: {
data: posts,
pagination: pagination_data(posts)
}
end
def show
if @post.published? || current_user&.id == @post.user_id
render json: @post
else
render json: { error: 'Post not found' }, status: :not_found
end
end
def create
@post = current_user.posts.build(post_params)
if @post.save
render json: @post, status: :created
else
render json: { errors: @post.errors.full_messages }, status: :unprocessable_entity
end
end
def update
if @post.update(post_params)
render json: @post
else
render json: { errors: @post.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
@post.destroy
head :no_content
end
def publish
@post.publish!
render json: @post
end
private
def set_post
@post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'Post not found' }, status: :not_found
end
def authorize_post_owner!
unless current_user.id == @post.user_id || current_user.admin?
render json: { error: 'Unauthorized' }, status: :forbidden
end
end
def post_params
params.require(:post).permit(:title, :content, :status)
end
def pagination_data(collection)
{
page: collection.current_page,
per_page: collection.limit_value,
total: collection.total_count
}
end
end
end
end
5. Authentication with JWT
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include ActionController::Cookies
SECRET_KEY = Rails.application.secrets.secret_key_base
def encode_token(user_id)
payload = { user_id: user_id, exp: 24.hours.from_now.to_i }
JWT.encode(payload, SECRET_KEY, 'HS256')
end
def decode_token(token)
begin
JWT.decode(token, SECRET_KEY, true, { algorithm: 'HS256' })
rescue JWT::ExpiredSignature, JWT::DecodeError
nil
end
end
def authenticate_request
header = request.headers['Authorization']
token = header.split(' ').last if header.present?
decoded = decode_token(token)
if decoded
@current_user_id = decoded[0]['user_id']
@current_user = User.find(@current_user_id)
else
render json: { error: 'Unauthorized' }, status: :unauthorized
end
end
def current_user
@current_user
end
def logged_in?
current_user.present?
end
end
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
post 'auth/login', to: 'auth#login'
post 'auth/register', to: 'auth#register'
resources :users
resources :posts do
member do
patch :publish
end
resources :comments, only: [:index, :create, :destroy]
end
end
end
end
6. Active Record Queries
# app/services/post_service.rb
class PostService
def self.get_user_posts(user_id, status: nil)
posts = Post.by_author(user_id)
posts = posts.where(status: status) if status.present?
posts.recent
end
def self.trending_posts(limit: 10)
Post.published
.joins(:comments)
.group('posts.id')
.order('COUNT(comments.id) DESC')
.limit(limit)
end
def self.search_posts(query)
Post.published
.where("title ILIKE ? OR content ILIKE ?", "%#{query}%", "%#{query}%")
.recent
end
def self.archive_old_drafts(days: 30)
Post.where(status: :draft)
.where('created_at < ?', days.days.ago)
.update_all(status: :archived)
end
end
# Usage
posts = Post.includes(:user).recent.limit(10)
recent_comments = Comment.where(post_id: post.id).order(created_at: :desc).limit(5)
7. Serializers
# app/serializers/user_serializer.rb
class UserSerializer
def initialize(user)
@user = user
end
def to_json
{
id: @user.id,
email: @user.email,
first_name: @user.first_name,
last_name: @user.last_name,
full_name: @user.full_name,
role: @user.role,
is_active: @user.is_active,
created_at: @user.created_at.iso8601,
updated_at: @user.updated_at.iso8601
}
end
end
# In controller
def show
render json: UserSerializer.new(@user).to_json
end
Best Practices
✅ DO
- Use conventions over configuration
- Leverage Active Record associations
- Implement proper scopes for queries
- Use strong parameters for security
- Implement authentication in ApplicationController
- Use services for complex business logic
- Implement proper error handling
- Use database migrations for schema changes
- Validate all inputs at model level
- Use before_action filters appropriately
❌ DON'T
- Use raw SQL without parameterization
- Implement business logic in controllers
- Trust user input without validation
- Store secrets in code
- Use select * without specifying columns
- Forget N+1 query problems (use includes/joins)
- Implement authentication in each controller
- Use global variables
- Ignore database constraints
Complete Example
# Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 7.0.0'
gem 'pg', '~> 1.1'
gem 'bcrypt', '~> 3.1.7'
gem 'jwt'
gem 'kaminari'
# models.rb + controllers.rb (see sections above)
# routes.rb and migrations (see sections above)