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
- 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
- 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
- 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
- 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
- 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)
- 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'