# Ruby Rails Application

> 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.

- Skill: `majiayu000/ruby-rails-application` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/ruby-rails-application`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/ruby-rails-application/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/ruby-rails-application

---


# 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**

```bash
rails new myapp --api --database=postgresql
cd myapp
rails db:create
```

### 2. **Models with Active Record**

```ruby
# 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**

```ruby
# 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**

```ruby
# 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**

```ruby
# 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**

```ruby
# 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**

```ruby
# 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

```ruby
# 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)
```

