Initial commit
This commit is contained in:
861
skills/rails-pattern-finder/reference.md
Normal file
861
skills/rails-pattern-finder/reference.md
Normal file
@@ -0,0 +1,861 @@
|
||||
# Rails Pattern Reference
|
||||
|
||||
**Purpose**: Defines search strategies for common Rails patterns
|
||||
|
||||
**Version**: 1.0.0
|
||||
|
||||
---
|
||||
|
||||
## Service Layer Patterns
|
||||
|
||||
### Service Objects
|
||||
|
||||
```yaml
|
||||
service_objects:
|
||||
title: "Service Objects"
|
||||
description: "Encapsulate business logic outside of models/controllers"
|
||||
search_paths:
|
||||
- "app/services/**/*.rb"
|
||||
- "app/lib/services/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_service.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Service"
|
||||
- "def call"
|
||||
- "def initialize"
|
||||
usage_patterns:
|
||||
- "\\w+Service\\.new"
|
||||
- "\\w+Service\\.call"
|
||||
test_paths:
|
||||
- "spec/services/**/*_spec.rb"
|
||||
- "test/services/**/*_test.rb"
|
||||
keywords: [service, business logic, use case, interactor]
|
||||
best_practice: |
|
||||
class UserRegistrationService
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
ActiveRecord::Base.transaction do
|
||||
user = create_user
|
||||
send_welcome_email(user)
|
||||
notify_admin(user)
|
||||
user
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_user
|
||||
User.create!(@params)
|
||||
end
|
||||
|
||||
def send_welcome_email(user)
|
||||
UserMailer.welcome(user).deliver_later
|
||||
end
|
||||
|
||||
def notify_admin(user)
|
||||
AdminMailer.new_user(user).deliver_later
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Form Objects
|
||||
|
||||
```yaml
|
||||
form_objects:
|
||||
title: "Form Objects"
|
||||
description: "Handle complex form logic with multiple models"
|
||||
search_paths:
|
||||
- "app/forms/**/*.rb"
|
||||
- "app/lib/forms/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_form.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Form"
|
||||
- "include ActiveModel::Model"
|
||||
- "attr_accessor"
|
||||
- "validate"
|
||||
keywords: [form, multi-model, virtual attributes]
|
||||
best_practice: |
|
||||
class RegistrationForm
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_accessor :email, :password, :company_name
|
||||
|
||||
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
||||
validates :password, presence: true, length: { minimum: 8 }
|
||||
validates :company_name, presence: true
|
||||
|
||||
def save
|
||||
return false unless valid?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
user = User.create!(email: email, password: password)
|
||||
Company.create!(name: company_name, owner: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Query Objects
|
||||
|
||||
```yaml
|
||||
query_objects:
|
||||
title: "Query Objects"
|
||||
description: "Encapsulate complex database queries"
|
||||
search_paths:
|
||||
- "app/queries/**/*.rb"
|
||||
- "app/lib/queries/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_query.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Query"
|
||||
- "def initialize.*relation"
|
||||
- "def call"
|
||||
- "def resolve"
|
||||
keywords: [query, scope, filtering, search]
|
||||
best_practice: |
|
||||
class ActiveUsersQuery
|
||||
def initialize(relation = User.all)
|
||||
@relation = relation
|
||||
end
|
||||
|
||||
def call
|
||||
@relation
|
||||
.where(active: true)
|
||||
.where("last_login_at > ?", 30.days.ago)
|
||||
.order(created_at: :desc)
|
||||
end
|
||||
end
|
||||
|
||||
# Usage:
|
||||
ActiveUsersQuery.new.call
|
||||
ActiveUsersQuery.new(User.where(role: 'admin')).call
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Model Patterns
|
||||
|
||||
### Concerns
|
||||
|
||||
```yaml
|
||||
concerns:
|
||||
title: "Model Concerns"
|
||||
description: "Shared behavior across models"
|
||||
search_paths:
|
||||
- "app/models/concerns/**/*.rb"
|
||||
file_patterns:
|
||||
- "*.rb"
|
||||
code_patterns:
|
||||
- "module \\w+"
|
||||
- "extend ActiveSupport::Concern"
|
||||
- "included do"
|
||||
- "class_methods do"
|
||||
keywords: [concern, mixin, shared behavior]
|
||||
best_practice: |
|
||||
module Taggable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :taggings, as: :taggable
|
||||
has_many :tags, through: :taggings
|
||||
|
||||
scope :tagged_with, ->(tag_name) {
|
||||
joins(:tags).where(tags: { name: tag_name })
|
||||
}
|
||||
end
|
||||
|
||||
def tag_list
|
||||
tags.pluck(:name).join(', ')
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def most_tagged
|
||||
joins(:taggings).group('id').order('COUNT(taggings.id) DESC')
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Scopes
|
||||
|
||||
```yaml
|
||||
scopes:
|
||||
title: "Named Scopes"
|
||||
description: "Reusable query methods"
|
||||
search_paths:
|
||||
- "app/models/**/*.rb"
|
||||
code_patterns:
|
||||
- "scope :\\w+,"
|
||||
- "scope :\\w+, ->"
|
||||
keywords: [scope, query, filter]
|
||||
best_practice: |
|
||||
class User < ApplicationRecord
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :admin, -> { where(role: 'admin') }
|
||||
scope :recent, -> { where("created_at > ?", 30.days.ago) }
|
||||
scope :search, ->(query) { where("name ILIKE ?", "%#{query}%") }
|
||||
|
||||
# Chainable scopes
|
||||
scope :with_posts, -> { joins(:posts).distinct }
|
||||
scope :popular, -> { where("followers_count > ?", 100) }
|
||||
end
|
||||
|
||||
# Usage:
|
||||
User.active.admin.recent
|
||||
```
|
||||
|
||||
### Polymorphic Associations
|
||||
|
||||
```yaml
|
||||
polymorphic_associations:
|
||||
title: "Polymorphic Associations"
|
||||
description: "Flexible belongs_to relationships"
|
||||
search_paths:
|
||||
- "app/models/**/*.rb"
|
||||
code_patterns:
|
||||
- "belongs_to :\\w+, polymorphic: true"
|
||||
- "has_many :\\w+, as:"
|
||||
keywords: [polymorphic, flexible association]
|
||||
best_practice: |
|
||||
# Polymorphic model
|
||||
class Comment < ApplicationRecord
|
||||
belongs_to :commentable, polymorphic: true
|
||||
end
|
||||
|
||||
# Models that can have comments
|
||||
class Post < ApplicationRecord
|
||||
has_many :comments, as: :commentable
|
||||
end
|
||||
|
||||
class Photo < ApplicationRecord
|
||||
has_many :comments, as: :commentable
|
||||
end
|
||||
|
||||
# Migration
|
||||
create_table :comments do |t|
|
||||
t.text :body
|
||||
t.references :commentable, polymorphic: true
|
||||
t.timestamps
|
||||
end
|
||||
```
|
||||
|
||||
### Single Table Inheritance
|
||||
|
||||
```yaml
|
||||
sti:
|
||||
title: "Single Table Inheritance"
|
||||
description: "Class hierarchy in one table"
|
||||
search_paths:
|
||||
- "app/models/**/*.rb"
|
||||
code_patterns:
|
||||
- "class \\w+ < \\w+"
|
||||
- "self\\.inheritance_column"
|
||||
keywords: [STI, inheritance, subclass]
|
||||
best_practice: |
|
||||
# Base class
|
||||
class User < ApplicationRecord
|
||||
# Common behavior
|
||||
end
|
||||
|
||||
# Subclasses
|
||||
class Admin < User
|
||||
def can_manage_users?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class Member < User
|
||||
def can_manage_users?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Migration needs 'type' column
|
||||
add_column :users, :type, :string
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Patterns
|
||||
|
||||
### API Versioning
|
||||
|
||||
```yaml
|
||||
api_versioning:
|
||||
title: "API Versioning"
|
||||
description: "Version management strategies"
|
||||
search_paths:
|
||||
- "app/controllers/api/**/*.rb"
|
||||
- "app/controllers/api/v*/**/*.rb"
|
||||
file_patterns:
|
||||
- "api/v*/**/*.rb"
|
||||
code_patterns:
|
||||
- "module Api::V\\d+"
|
||||
- "namespace :api"
|
||||
keywords: [api, version, v1, v2]
|
||||
best_practice: |
|
||||
# config/routes.rb
|
||||
namespace :api do
|
||||
namespace :v1 do
|
||||
resources :users
|
||||
end
|
||||
|
||||
namespace :v2 do
|
||||
resources :users
|
||||
end
|
||||
end
|
||||
|
||||
# app/controllers/api/v1/users_controller.rb
|
||||
module Api
|
||||
module V1
|
||||
class UsersController < ApiController
|
||||
def index
|
||||
render json: User.all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### API Serialization
|
||||
|
||||
```yaml
|
||||
api_serialization:
|
||||
title: "API Serialization"
|
||||
description: "JSON response formatting"
|
||||
search_paths:
|
||||
- "app/serializers/**/*.rb"
|
||||
- "app/blueprints/**/*.rb"
|
||||
- "app/views/api/**/*.json.jbuilder"
|
||||
file_patterns:
|
||||
- "*_serializer.rb"
|
||||
- "*_blueprint.rb"
|
||||
- "*.json.jbuilder"
|
||||
code_patterns:
|
||||
- "class \\w+Serializer"
|
||||
- "< ActiveModel::Serializer"
|
||||
- "< Blueprinter::Base"
|
||||
- "json\\."
|
||||
keywords: [json, serializer, blueprint, jbuilder]
|
||||
best_practice: |
|
||||
# Using ActiveModel::Serializers
|
||||
class UserSerializer < ActiveModel::Serializer
|
||||
attributes :id, :email, :full_name, :created_at
|
||||
|
||||
has_many :posts
|
||||
|
||||
def full_name
|
||||
"#{object.first_name} #{object.last_name}"
|
||||
end
|
||||
end
|
||||
|
||||
# Using Blueprinter
|
||||
class UserBlueprint < Blueprinter::Base
|
||||
identifier :id
|
||||
|
||||
fields :email, :created_at
|
||||
|
||||
field :full_name do |user|
|
||||
"#{user.first_name} #{user.last_name}"
|
||||
end
|
||||
|
||||
association :posts, blueprint: PostBlueprint
|
||||
end
|
||||
```
|
||||
|
||||
### API Authentication
|
||||
|
||||
```yaml
|
||||
api_authentication:
|
||||
title: "API Authentication"
|
||||
description: "Token-based authentication"
|
||||
search_paths:
|
||||
- "app/controllers/api/**/*.rb"
|
||||
- "app/controllers/concerns/**/*.rb"
|
||||
code_patterns:
|
||||
- "before_action :authenticate"
|
||||
- "Authorization.*Bearer"
|
||||
- "JWT"
|
||||
- "authenticate_with_http_token"
|
||||
keywords: [auth, token, jwt, bearer]
|
||||
best_practice: |
|
||||
# app/controllers/api/api_controller.rb
|
||||
module Api
|
||||
class ApiController < ActionController::API
|
||||
before_action :authenticate_user!
|
||||
|
||||
private
|
||||
|
||||
def authenticate_user!
|
||||
token = request.headers['Authorization']&.split(' ')&.last
|
||||
decoded = JWT.decode(token, Rails.application.secret_key_base, true)
|
||||
@current_user = User.find(decoded[0]['user_id'])
|
||||
rescue JWT::DecodeError, ActiveRecord::RecordNotFound
|
||||
render json: { error: 'Unauthorized' }, status: :unauthorized
|
||||
end
|
||||
|
||||
attr_reader :current_user
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authorization Patterns
|
||||
|
||||
### Policy Objects (Pundit)
|
||||
|
||||
```yaml
|
||||
policies:
|
||||
title: "Authorization Policies"
|
||||
description: "Pundit policy pattern"
|
||||
search_paths:
|
||||
- "app/policies/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_policy.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Policy"
|
||||
- "def initialize\\(user, record\\)"
|
||||
- "def index\\?"
|
||||
- "def show\\?"
|
||||
- "def create\\?"
|
||||
- "def update\\?"
|
||||
- "def destroy\\?"
|
||||
keywords: [pundit, policy, authorization, can]
|
||||
best_practice: |
|
||||
class PostPolicy
|
||||
attr_reader :user, :post
|
||||
|
||||
def initialize(user, post)
|
||||
@user = user
|
||||
@post = post
|
||||
end
|
||||
|
||||
def index?
|
||||
true
|
||||
end
|
||||
|
||||
def show?
|
||||
true
|
||||
end
|
||||
|
||||
def create?
|
||||
user.present?
|
||||
end
|
||||
|
||||
def update?
|
||||
user.present? && (post.author == user || user.admin?)
|
||||
end
|
||||
|
||||
def destroy?
|
||||
user.present? && (post.author == user || user.admin?)
|
||||
end
|
||||
|
||||
class Scope
|
||||
def initialize(user, scope)
|
||||
@user = user
|
||||
@scope = scope
|
||||
end
|
||||
|
||||
def resolve
|
||||
if user&.admin?
|
||||
scope.all
|
||||
else
|
||||
scope.published
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :user, :scope
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Background Job Patterns
|
||||
|
||||
### Job Structure
|
||||
|
||||
```yaml
|
||||
background_jobs:
|
||||
title: "Background Jobs"
|
||||
description: "ActiveJob pattern"
|
||||
search_paths:
|
||||
- "app/jobs/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_job.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Job < ApplicationJob"
|
||||
- "def perform"
|
||||
- "queue_as"
|
||||
- "retry_on"
|
||||
keywords: [job, background, async, sidekiq]
|
||||
best_practice: |
|
||||
class ProcessPaymentJob < ApplicationJob
|
||||
queue_as :critical
|
||||
|
||||
retry_on PaymentGateway::NetworkError, wait: 5.seconds, attempts: 3
|
||||
discard_on PaymentGateway::InvalidCard
|
||||
|
||||
def perform(payment_id)
|
||||
payment = Payment.find(payment_id)
|
||||
PaymentProcessor.new(payment).process!
|
||||
end
|
||||
end
|
||||
|
||||
# Usage:
|
||||
ProcessPaymentJob.perform_later(payment.id)
|
||||
ProcessPaymentJob.set(wait: 1.hour).perform_later(payment.id)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### Factory Usage
|
||||
|
||||
```yaml
|
||||
factory_usage:
|
||||
title: "FactoryBot Factories"
|
||||
description: "Test data creation"
|
||||
search_paths:
|
||||
- "spec/factories/**/*.rb"
|
||||
- "test/factories/**/*.rb"
|
||||
file_patterns:
|
||||
- "*.rb"
|
||||
code_patterns:
|
||||
- "FactoryBot\\.define"
|
||||
- "factory :\\w+"
|
||||
- "trait :\\w+"
|
||||
keywords: [factory, factorybot, test data]
|
||||
best_practice: |
|
||||
FactoryBot.define do
|
||||
factory :user do
|
||||
sequence(:email) { |n| "user#{n}@example.com" }
|
||||
password { "password123" }
|
||||
first_name { "John" }
|
||||
last_name { "Doe" }
|
||||
|
||||
trait :admin do
|
||||
role { :admin }
|
||||
end
|
||||
|
||||
trait :with_posts do
|
||||
after(:create) do |user|
|
||||
create_list(:post, 3, author: user)
|
||||
end
|
||||
end
|
||||
|
||||
factory :admin_user, traits: [:admin]
|
||||
end
|
||||
end
|
||||
|
||||
# Usage:
|
||||
create(:user)
|
||||
create(:user, :admin)
|
||||
create(:user, :with_posts)
|
||||
build(:user)
|
||||
```
|
||||
|
||||
### Request Specs
|
||||
|
||||
```yaml
|
||||
request_specs:
|
||||
title: "Request Specs"
|
||||
description: "API endpoint testing"
|
||||
search_paths:
|
||||
- "spec/requests/**/*_spec.rb"
|
||||
code_patterns:
|
||||
- "RSpec\\.describe.*type: :request"
|
||||
- "get .*"
|
||||
- "post .*"
|
||||
- "expect\\(response\\)"
|
||||
keywords: [request spec, api test, integration test]
|
||||
best_practice: |
|
||||
RSpec.describe "Api::V1::Users", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:auth_headers) { { 'Authorization' => "Bearer #{user.token}" } }
|
||||
|
||||
describe "GET /api/v1/users" do
|
||||
it "returns users list" do
|
||||
create_list(:user, 3)
|
||||
|
||||
get api_v1_users_path, headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response['users'].size).to eq(4) # 3 + authenticated user
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/v1/users" do
|
||||
let(:valid_params) do
|
||||
{ user: { email: 'new@example.com', password: 'password123' } }
|
||||
end
|
||||
|
||||
it "creates a new user" do
|
||||
expect {
|
||||
post api_v1_users_path, params: valid_params
|
||||
}.to change(User, :count).by(1)
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decorator Patterns
|
||||
|
||||
### Draper Decorators
|
||||
|
||||
```yaml
|
||||
decorators:
|
||||
title: "Decorator/Presenter Pattern"
|
||||
description: "View logic separation"
|
||||
search_paths:
|
||||
- "app/decorators/**/*.rb"
|
||||
- "app/presenters/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_decorator.rb"
|
||||
- "*_presenter.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Decorator"
|
||||
- "< Draper::Decorator"
|
||||
- "delegate_all"
|
||||
keywords: [decorator, presenter, view logic]
|
||||
best_practice: |
|
||||
class UserDecorator < Draper::Decorator
|
||||
delegate_all
|
||||
|
||||
def full_name
|
||||
"#{first_name} #{last_name}"
|
||||
end
|
||||
|
||||
def formatted_created_at
|
||||
created_at.strftime("%B %d, %Y")
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
if object.avatar.attached?
|
||||
h.url_for(object.avatar)
|
||||
else
|
||||
h.asset_path('default-avatar.png')
|
||||
end
|
||||
end
|
||||
|
||||
def profile_link
|
||||
h.link_to full_name, h.user_path(object), class: 'user-link'
|
||||
end
|
||||
end
|
||||
|
||||
# Usage in controller:
|
||||
@user = User.find(params[:id]).decorate
|
||||
|
||||
# Usage in view:
|
||||
<%= @user.full_name %>
|
||||
<%= @user.profile_link %>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rails 8 Defaults
|
||||
|
||||
Rails 8 includes several built-in solutions that replace common third-party gems:
|
||||
|
||||
### Solid Queue
|
||||
**Purpose**: Background job processing (replaces Sidekiq, Resque, Delayed Job)
|
||||
|
||||
Rails 8's default background job adapter. Provides:
|
||||
- Database-backed job queue
|
||||
- No Redis dependency
|
||||
- Built-in retry logic
|
||||
- Job prioritization
|
||||
- Mission control web UI
|
||||
|
||||
**Setup**:
|
||||
```bash
|
||||
bin/rails solid_queue:install
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```ruby
|
||||
# config/application.rb or config/environments/production.rb
|
||||
config.active_job.queue_adapter = :solid_queue
|
||||
|
||||
# Jobs work the same as before
|
||||
class ProcessPaymentJob < ApplicationJob
|
||||
queue_as :critical
|
||||
|
||||
def perform(payment_id)
|
||||
# Job logic
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Solid Cache
|
||||
**Purpose**: Database-backed caching (replaces Redis, Memcached for caching)
|
||||
|
||||
Rails 8's default cache store. Provides:
|
||||
- Database-backed cache
|
||||
- No Redis dependency
|
||||
- All standard Rails cache features
|
||||
- Automatic expiration
|
||||
- Production-ready performance
|
||||
|
||||
**Setup**:
|
||||
```bash
|
||||
bin/rails solid_cache:install
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```ruby
|
||||
# config/environments/production.rb
|
||||
config.cache_store = :solid_cache_store
|
||||
|
||||
# Caching works the same as before
|
||||
Rails.cache.fetch('expensive_query', expires_in: 1.hour) do
|
||||
# Expensive operation
|
||||
end
|
||||
```
|
||||
|
||||
### Solid Cable
|
||||
**Purpose**: Database-backed Action Cable (replaces Redis for WebSocket pub/sub)
|
||||
|
||||
Rails 8's default Action Cable adapter. Provides:
|
||||
- Database-backed pub/sub
|
||||
- No Redis dependency for real time features
|
||||
- WebSocket support
|
||||
- Horizontal scaling support
|
||||
|
||||
**Setup**:
|
||||
```bash
|
||||
bin/rails solid_cable:install
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```ruby
|
||||
# config/cable.yml
|
||||
production:
|
||||
adapter: solid_cable
|
||||
|
||||
# Channels work the same as before
|
||||
class ChatChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_from "chat_#{params[:room_id]}"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### When to Use Rails 8 Defaults
|
||||
|
||||
**Use Solid Queue when**:
|
||||
- Starting new Rails 8 project
|
||||
- Want to avoid Redis operational complexity
|
||||
- Job volume < 1000/second
|
||||
- Prefer simplicity over maximum throughput
|
||||
|
||||
**Use Solid Cache when**:
|
||||
- Starting new Rails 8 project
|
||||
- Cache hit rates are moderate
|
||||
- Want unified database infrastructure
|
||||
- Avoiding Redis operational overhead
|
||||
|
||||
**Use Solid Cable when**:
|
||||
- Starting new Rails 8 project
|
||||
- Real-time features with moderate concurrency
|
||||
- Want to simplify infrastructure
|
||||
- Database can handle WebSocket load
|
||||
|
||||
**Consider alternatives when**:
|
||||
- Extreme performance requirements (millions of jobs/sec)
|
||||
- Very high cache hit rates (>95%)
|
||||
- Thousands of concurrent WebSocket connections
|
||||
- Already have Redis in production
|
||||
|
||||
---
|
||||
|
||||
## Pattern Categories
|
||||
|
||||
The `rails-pattern-finder` skill recognizes these pattern categories:
|
||||
|
||||
### Authentication
|
||||
User login, session management, password handling
|
||||
|
||||
**Common implementations**:
|
||||
- Devise gem
|
||||
- Rails authentication generator (Rails 8+)
|
||||
- Custom authentication with `has_secure_password`
|
||||
- OAuth integrations (OmniAuth)
|
||||
|
||||
### Background Jobs
|
||||
Asynchronous task processing
|
||||
|
||||
**Common implementations**:
|
||||
- Solid Queue (Rails 8 default)
|
||||
- Sidekiq
|
||||
- Resque
|
||||
- Delayed Job
|
||||
- ActiveJob with any adapter
|
||||
|
||||
### Caching
|
||||
Performance optimization through data caching
|
||||
|
||||
**Common implementations**:
|
||||
- Solid Cache (Rails 8 default)
|
||||
- Redis cache store
|
||||
- Memcached
|
||||
- File store
|
||||
- Database-backed caching
|
||||
|
||||
### Real Time
|
||||
WebSocket connections and live updates
|
||||
|
||||
**Common implementations**:
|
||||
- Solid Cable (Rails 8 default for Action Cable)
|
||||
- Action Cable with Redis
|
||||
- Hotwire Turbo Streams
|
||||
- Server-Sent Events (SSE)
|
||||
|
||||
### File Uploads
|
||||
Handling user-uploaded files
|
||||
|
||||
**Common implementations**:
|
||||
- Active Storage (Rails built-in)
|
||||
- Shrine
|
||||
- CarrierWave
|
||||
- Paperclip (deprecated)
|
||||
|
||||
### Pagination
|
||||
Dividing large datasets into pages
|
||||
|
||||
**Common implementations**:
|
||||
- Pagy
|
||||
- Kaminari
|
||||
- will_paginate
|
||||
- Custom pagination with `limit/offset`
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Each pattern includes:
|
||||
- **title**: Human-readable name
|
||||
- **description**: What the pattern does
|
||||
- **search_paths**: Where to find files
|
||||
- **file_patterns**: File naming conventions
|
||||
- **code_patterns**: Regex to match code structures
|
||||
- **keywords**: Related concepts
|
||||
- **best_practice**: Reference implementation
|
||||
|
||||
The `rails-pattern-finder` skill uses these definitions to:
|
||||
1. Search the codebase for existing patterns
|
||||
2. Extract relevant examples
|
||||
3. Provide best-practice templates when pattern not found
|
||||
4. Recommend Rails 8 built-in alternatives when appropriate
|
||||
Reference in New Issue
Block a user