home / skills / aj-geddes / useful-ai-prompts / 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-applicationReview the files below or copy the command above to add this skill to your agents.
---
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)
```
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.
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.
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.