home / skills / aj-geddes / useful-ai-prompts / ruby-rails-application

ruby-rails-application skill

/skills/ruby-rails-application

This skill helps you design, build, and scale Ruby on Rails applications with models, controllers, views, authentication, and RESTful APIs.

npx playbooks add skill aj-geddes/useful-ai-prompts --skill ruby-rails-application

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
11.9 KB
---
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**

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

Overview

This skill helps you develop full Ruby on Rails applications following Rails conventions for models, controllers, views, and RESTful routing. It includes patterns for Active Record associations, validations, scopes, JWT authentication, pagination, and serializers. Use it to scaffold APIs, enforce secure input handling, and structure business logic with services and filters.

How this skill works

The skill provides ready-made model structures, migrations, and RESTful controllers with common actions (index, show, create, update, destroy) plus custom member actions like publish. It wires JWT-based authentication in ApplicationController, authorization checks, pagination, and serializers for consistent JSON output. It also includes service methods and Active Record query patterns for searching, trending computation, and background-style batch updates.

When to use it

  • Building a new Rails API or full-stack app with models and controllers
  • Designing Active Record models with associations and validations
  • Implementing JWT authentication and authorization flows
  • Creating paginated, searchable RESTful endpoints
  • Encapsulating complex business logic in services and scopes
  • Preventing N+1 queries and optimizing DB access

Best practices

  • Follow conventions over configuration: leverage Rails defaults for folders, naming, and callbacks
  • Use strong parameters and model validations to guard input
  • Encapsulate complex queries in scopes or service objects, not controllers
  • Add DB indexes and foreign keys via migrations to enforce integrity and performance
  • Use includes/joins to avoid N+1 queries and paginate large collections
  • Keep secrets out of source code; use encrypted credentials or environment variables

Example use cases

  • User management API with registration, login, and profile endpoints
  • Content platform: posts, comments, author scopes, and publish workflows
  • Admin interfaces with role-based authorization and user lifecycle actions
  • Searchable public endpoints with pagination and recent/trending filters
  • Background maintenance tasks like archiving old drafts through a service method

FAQ

Does this include frontend views or is it API-focused?

The patterns support both API-only apps and full-stack Rails apps; examples use JSON serializers and API controllers but view rendering can be added following the same controllers and routes.

How is authentication handled?

JWT tokens are encoded and decoded in ApplicationController with expiration; authenticate_request sets current_user and protects endpoints. Use encrypted secret management for the signing key.