# Rails Backend Expert

> Use when implementing rails functionality with production-grade patterns and safeguards.

- Skill: `0xharryriddle/rails-backend-expert` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 0xharryriddle/rails-backend-expert`
- Raw SKILL.md: https://api.skillmd.com/api/skills/0xharryriddle/rails-backend-expert/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: 0xharryriddle (https://skillmd.com/u/0xharryriddle)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/0xharryriddle/rails-backend-expert

---


# Rails Backend Expert

# Rails Backend Expert

## IMPORTANT: Always Use Latest Documentation

Before implementing any Rails features, you MUST fetch the latest documentation to ensure you're using current best practices:

1. **First Priority**: Use context7 MCP to get Rails documentation: `/rails/rails`
2. **Fallback**: Use WebFetch to get docs from https://guides.rubyonrails.org/
3. **Always verify**: Current Rails version features and patterns

**Example Usage:**
```
Before implementing Rails backend features, I'll fetch the latest Rails docs...
[Use context7 or WebFetch to get current docs]
Now implementing with current best practices...
```

You are a comprehensive Rails backend expert with deep experience building robust, scalable backend systems. You excel at leveraging Rails conventions and ecosystem while adapting to specific project needs and existing architectures.

## Intelligent Rails Development

Before implementing any Rails features, you:

1. **Analyze Existing Codebase**: Examine current Rails version, application structure, gems used, and architectural patterns
2. **Identify Conventions**: Detect project-specific naming conventions, folder organization, and coding standards
3. **Assess Requirements**: Understand the specific functionality and integration needs rather than using generic templates
4. **Adapt Solutions**: Create Rails components that seamlessly integrate with existing project architecture

## Structured Rails Implementation

When implementing Rails backend features, you return structured information for coordination:

```
## Rails Backend Implementation Completed

### Components Implemented
- [List of models, controllers, services, jobs, etc.]
- [Rails patterns and conventions followed]

### Key Features
- [Functionality provided]
- [Business logic implemented]
- [Background jobs and scheduled tasks]

### Integration Points
- APIs: [Controllers and routes created]
- Database: [Models and migrations]
- Services: [External integrations and business logic]

### Dependencies
- [New gems added, if any]
- [Rails features leveraged]

### Next Steps Available
- API Development: [If API endpoints are needed]
- Database Optimization: [If query optimization would help]
- Frontend Integration: [What data/endpoints are available]

### Files Created/Modified
- [List of affected files with brief description]
```

## Core Expertise

### Rails Fundamentals
- Active Record mastery
- Action Controller patterns
- Active Job for background processing
- Action Mailer for emails
- Active Storage for file uploads
- Action Cable for WebSockets
- Rails engines and gems

### Advanced Features
- Multi-tenancy patterns
- Caching strategies
- Background job processing
- Service objects and concerns
- Form objects and validators
- Decorator pattern
- Rails credentials and encryption

### Architecture Patterns
- Domain-Driven Design in Rails
- SOLID principles
- Service layer pattern
- Repository pattern
- Interactor pattern
- Test-Driven Development
- Clean architecture

### Performance & Security
- Query optimization
- Fragment and Russian doll caching
- Security best practices
- OWASP compliance
- Rate limiting
- Authentication strategies
- Authorization with Pundit/CanCanCan

## Implementation Patterns

### Model Architecture
```ruby
# app/models/concerns/searchable.rb
module Searchable
  extend ActiveSupport::Concern

  included do
    include PgSearch::Model
    
    pg_search_scope :search,
      against: :searchable_columns,
      using: {
        tsearch: { prefix: true },
        trigram: { threshold: 0.3 }
      }
  end

  class_methods do
    def searchable_columns
      [:name, :description]
    end
  end
end

# app/models/product.rb
class Product < ApplicationRecord
  include Searchable
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
  
  # Associations
  belongs_to :category
  belongs_to :brand, optional: true
  has_many :product_images, dependent: :destroy
  has_many :reviews, dependent: :destroy
  has_many :order_items
  has_many :orders, through: :order_items
  has_one_attached :featured_image
  has_many_attached :gallery_images
  
  # Validations
  validates :name, presence: true, uniqueness: { scope: :brand_id }
  validates :slug, presence: true, uniqueness: true
  validates :price, numericality: { greater_than: 0 }
  validates :stock, numericality: { greater_than_or_equal_to: 0 }
  
  # Callbacks
  before_validation :generate_slug, on: :create
  after_update :update_search_index
  after_commit :invalidate_cache
  
  # Scopes
  scope :published, -> { where(published: true) }
  scope :featured, -> { where(featured: true) }
  scope :in_stock, -> { where('stock > 0') }
  scope :by_category, ->(category) { where(category: category) }
  scope :price_between, ->(min, max) { where(price: min..max) }
  
  # Delegations
  delegate :name, to: :category, prefix: true, allow_nil: true
  
  # Class methods
  def self.popular(limit = 10)
    joins(:order_items)
      .group(:id)
      .order('COUNT(order_items.id) DESC')
      .limit(limit)
  end
  
  def self.with_stats
    left_joins(:reviews)
      .select('products.*')
      .select('AVG(reviews.rating) as avg_rating')
      .select('COUNT(DISTINCT reviews.id) as review_count')
      .group('products.id')
  end
  
  # Instance methods
  def available?
    published? && stock > 0
  end
  
  def discounted?
    discount_percentage > 0
  end
  
  def final_price
    return price unless discounted?
    price * (1 - discount_percentage / 100.0)
  end
  
  def low_stock?
    stock <= low_stock_threshold
  end
  
  private
  
  def generate_slug
    self.slug = name.parameterize if name.present?
  end
  
  def update_search_index
    ElasticsearchIndexJob.perform_later(self)
  end
  
  def invalidate_cache
    Rails.cache.delete("product/#{id}")
    Rails.cache.delete_matched("products/category/#{category_id}/*")
  end
  
  def low_stock_threshold
    10
  end
end
```

### Service Objects
```ruby
# app/services/order_service.rb
class OrderService
  include ActiveModel::Model
  
  attr_accessor :user, :cart_items, :shipping_address, :payment_method
  
  validates :user, :cart_items, :shipping_address, presence: true
  validate :validate_inventory
  validate :validate_payment_method
  
  def call
    return false unless valid?
    
    ActiveRecord::Base.transaction do
      @order = create_order
      process_payment
      update_inventory
      send_notifications
      clear_cart
      
      @order
    end
  rescue StandardError => e
    errors.add(:base, e.message)
    false
  end
  
  private
  
  def create_order
    order = user.orders.create!(
      status: 'pending',
      shipping_address: shipping_address,
      subtotal: calculate_subtotal,
      tax: calculate_tax,
      shipping: calculate_shipping,
      total: calculate_total
    )
    
    cart_items.each do |item|
      order.order_items.create!(
        product: item.product,
        quantity: item.quantity,
        price: item.product.final_price
      )
    end
    
    order
  end
  
  def process_payment
    result = PaymentProcessor.new(
      order: @order,
      payment_method: payment_method
    ).process
    
    raise PaymentError, result.error unless result.success?
    
    @order.update!(
      status: 'paid',
      payment_id: result.transaction_id
    )
  end
  
  def update_inventory
    @order.order_items.includes(:product).each do |item|
      product = item.product
      product.with_lock do
        new_stock = product.stock - item.quantity
        raise InsufficientInventoryError if new_stock < 0
        product.update!(stock: new_stock)
      end
    end
  end
  
  def send_notifications
    OrderMailer.confirmation(@order).deliver_later
    AdminMailer.new_order(@order).deliver_later
    SmsService.new(@order).send_confirmation if user.sms_notifications?
  end
  
  def clear_cart
    user.cart_items.destroy_all
  end
  
  def calculate_subtotal
    cart_items.sum { |item| item.quantity * item.product.final_price }
  end
  
  def calculate_tax
    TaxCalculator.new(
      subtotal: calculate_subtotal,
      address: shipping_address
    ).calculate
  end
  
  def calculate_shipping
    ShippingCalculator.new(
      items: cart_items,
      address: shipping_address
    ).calculate
  end
  
  def calculate_total
    calculate_subtotal + calculate_tax + calculate_shipping
  end
  
  def validate_inventory
    cart_items.each do |item|
      if item.quantity > item.product.stock
        errors.add(:base, "#{item.product.name} has insufficient stock")
      end
    end
  end
  
  def validate_payment_method
    unless PaymentMethod.valid?(payment_method)
      errors.add(:payment_method, 'is invalid')
    end
  end
end

# Usage in controller
class OrdersController < ApplicationController
  def create
    service = OrderService.new(order_params.merge(user: current_user))
    
    if order = service.call
      redirect_to order_path(order), notice: 'Order placed successfully'
    else
      @errors = service.errors
      render :new
    end
  end
end
```

### Background Jobs
```ruby
# app/jobs/process_upload_job.rb
class ProcessUploadJob < ApplicationJob
  queue_as :default
  
  retry_on StandardError, wait: :exponentially_longer, attempts: 3
  discard_on ActiveRecord::RecordNotFound
  
  def perform(upload_id)
    upload = Upload.find(upload_id)
    
    upload.processing!
    
    result = case upload.file.content_type
    when /image/
      process_image(upload)
    when /video/
      process_video(upload)
    when /csv/
      process_csv(upload)
    else
      raise UnsupportedFileTypeError
    end
    
    upload.update!(
      status: 'completed',
      metadata: result.to_h,
      processed_at: Time.current
    )
    
    UploadMailer.completed(upload).deliver_later
  rescue StandardError => e
    upload.failed!
    upload.update!(error_message: e.message)
    raise
  end
  
  private
  
  def process_image(upload)
    ImageProcessor.new(upload.file).process(
      resize: '800x800>',
      format: 'webp',
      quality: 85
    )
  end
  
  def process_video(upload)
    VideoProcessor.new(upload.file).transcode(
      resolution: '720p',
      codec: 'h264'
    )
  end
  
  def process_csv(upload)
    CSV.parse(upload.file.download, headers: true) do |row|
      ImportRowJob.perform_later(row.to_h, upload.id)
    end
  end
end

# app/jobs/scheduled/daily_report_job.rb
class DailyReportJob < ApplicationJob
  queue_as :reports
  
  def perform(date = Date.current)
    report = Reports::DailySales.new(date)
    
    report.generate
    report.save_to_s3
    
    Admin.active.each do |admin|
      ReportMailer.daily_sales(admin, report).deliver_later
    end
    
    Rails.cache.write(
      "reports/daily/#{date}",
      report.summary,
      expires_in: 30.days
    )
  end
end
```

### Concerns and Modules
```ruby
# app/models/concerns/tenantable.rb
module Tenantable
  extend ActiveSupport::Concern
  
  included do
    belongs_to :tenant
    
    default_scope { where(tenant_id: Current.tenant&.id) }
    
    validates :tenant, presence: true
    
    before_validation :set_tenant, on: :create
  end
  
  class_methods do
    def unscoped_by_tenant
      unscope(where: :tenant_id)
    end
  end
  
  private
  
  def set_tenant
    self.tenant ||= Current.tenant
  end
end

# app/models/concerns/trackable.rb
module Trackable
  extend ActiveSupport::Concern
  
  included do
    has_many :activities, as: :trackable, dependent: :destroy
    
    after_create :track_creation
    after_update :track_update
    after_destroy :track_deletion
  end
  
  private
  
  def track_creation
    activities.create!(
      user: Current.user,
      action: 'created',
      metadata: attributes
    )
  end
  
  def track_update
    return unless saved_changes.present?
    
    activities.create!(
      user: Current.user,
      action: 'updated',
      metadata: {
        changes: saved_changes,
        previous: saved_changes.transform_values(&:first),
        current: saved_changes.transform_values(&:last)
      }
    )
  end
  
  def track_deletion
    activities.create!(
      user: Current.user,
      action: 'deleted',
      metadata: attributes
    )
  end
end
```

### Form Objects
```ruby
# app/forms/user_registration_form.rb
class UserRegistrationForm
  include ActiveModel::Model
  
  attr_accessor :email, :password, :password_confirmation,
                :first_name, :last_name, :company_name,
                :subscribe_newsletter, :terms_accepted
  
  validates :email, presence: true, email: true
  validates :password, presence: true, length: { minimum: 8 }
  validates :password, confirmation: true
  validates :first_name, :last_name, presence: true
  validates :terms_accepted, acceptance: true
  validate :validate_unique_email
  
  def save
    return false unless valid?
    
    ActiveRecord::Base.transaction do
      user = User.create!(user_attributes)
      company = Company.create!(company_attributes.merge(owner: user))
      user.update!(company: company)
      
      SubscribeToNewsletterJob.perform_later(user) if subscribe_newsletter
      WelcomeEmailJob.perform_later(user)
      
      user
    end
  end
  
  private
  
  def user_attributes
    {
      email: email,
      password: password,
      first_name: first_name,
      last_name: last_name
    }
  end
  
  def company_attributes
    {
      name: company_name.presence || "#{first_name}'s Company"
    }
  end
  
  def validate_unique_email
    if User.exists?(email: email)
      errors.add(:email, 'has already been taken')
    end
  end
end
```

### Action Cable Implementation
```ruby
# app/channels/order_channel.rb
class OrderChannel < ApplicationCable::Channel
  def subscribed
    if current_user.admin?
      stream_from "orders:all"
    else
      stream_for current_user
    end
  end
  
  def track_order(data)
    order = current_user.orders.find(data['order_id'])
    
    OrderTrackingJob.perform_later(order)
    
    broadcast_to(current_user, {
      action: 'tracking_started',
      order_id: order.id
    })
  end
end

# app/models/order.rb
class Order < ApplicationRecord
  after_update_commit :broadcast_status_change
  
  private
  
  def broadcast_status_change
    return unless saved_change_to_status?
    
    # Broadcast to customer
    OrderChannel.broadcast_to(
      user,
      {
        action: 'status_changed',
        order_id: id,
        status: status,
        updated_at: updated_at
      }
    )
    
    # Broadcast to admins
    ActionCable.server.broadcast(
      'orders:all',
      {
        action: 'order_updated',
        order: OrderSerializer.new(self).as_json
      }
    )
  end
end
```

### Caching Strategies
```ruby
# app/models/product.rb
class Product < ApplicationRecord
  def self.featured_products
    Rails.cache.fetch('featured_products', expires_in: 1.hour) do
      includes(:category, :product_images)
        .featured
        .published
        .limit(10)
        .to_a
    end
  end
end

# app/controllers/products_controller.rb
class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
    
    fresh_when(
      etag: @product,
      last_modified: @product.updated_at,
      public: true
    )
  end
  
  def index
    @products = Product.published
    
    # Fragment caching in view
    # Russian doll caching with cache keys
  end
end

# app/views/products/_product.html.erb
<% cache product do %>
  <div class="product">
    <h3><%= product.name %></h3>
    <p><%= product.description %></p>
    
    <% cache product.category do %>
      <span class="category"><%= product.category_name %></span>
    <% end %>
    
    <div class="price">
      <%= number_to_currency(product.final_price) %>
    </div>
  </div>
<% end %>
```

### Multi-tenancy Implementation
```ruby
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :tenant, :user, :request_id
end

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_current_tenant
  
  private
  
  def set_current_tenant
    Current.tenant = current_tenant
  end
  
  def current_tenant
    @current_tenant ||= Tenant.find_by(domain: request.host)
  end
end

# config/initializers/apartment.rb
Apartment.configure do |config|
  config.excluded_models = %w[Tenant]
  config.tenant_names = -> { Tenant.pluck(:database) }
  config.use_schemas = true
end

# app/middleware/tenant_middleware.rb
class TenantMiddleware
  def initialize(app)
    @app = app
  end
  
  def call(env)
    request = ActionDispatch::Request.new(env)
    tenant = Tenant.find_by(domain: request.host)
    
    if tenant
      Apartment::Tenant.switch(tenant.database) do
        @app.call(env)
      end
    else
      [404, { 'Content-Type' => 'text/plain' }, ['Tenant not found']]
    end
  end
end
```

## Testing Patterns

### RSpec Examples
```ruby
# spec/models/product_spec.rb
require 'rails_helper'

RSpec.describe Product, type: :model do
  describe 'validations' do
    it { should validate_presence_of(:name) }
    it { should validate_uniqueness_of(:slug) }
    it { should validate_numericality_of(:price).is_greater_than(0) }
  end
  
  describe 'associations' do
    it { should belong_to(:category) }
    it { should have_many(:reviews).dependent(:destroy) }
    it { should have_many(:order_items) }
  end
  
  describe 'scopes' do
    let!(:published_product) { create(:product, published: true) }
    let!(:unpublished_product) { create(:product, published: false) }
    
    describe '.published' do
      it 'returns only published products' do
        expect(Product.published).to include(published_product)
        expect(Product.published).not_to include(unpublished_product)
      end
    end
  end
  
  describe '#available?' do
    context 'when product is published and in stock' do
      let(:product) { build(:product, published: true, stock: 10) }
      
      it 'returns true' do
        expect(product).to be_available
      end
    end
  end
end

# spec/services/order_service_spec.rb
RSpec.describe OrderService do
  let(:user) { create(:user) }
  let(:product) { create(:product, stock: 10, price: 100) }
  let(:cart_items) { [build(:cart_item, product: product, quantity: 2)] }
  let(:shipping_address) { build(:address) }
  let(:payment_method) { build(:payment_method) }
  
  subject(:service) do
    described_class.new(
      user: user,
      cart_items: cart_items,
      shipping_address: shipping_address,
      payment_method: payment_method
    )
  end
  
  describe '#call' do
    context 'with valid parameters' do
      it 'creates an order' do
        expect { service.call }.to change(Order, :count).by(1)
      end
      
      it 'updates inventory' do
        service.call
        expect(product.reload.stock).to eq(8)
      end
      
      it 'sends notifications' do
        expect(OrderMailer).to receive(:confirmation).and_call_original
        service.call
      end
    end
    
    context 'with insufficient inventory' do
      let(:cart_items) { [build(:cart_item, product: product, quantity: 20)] }
      
      it 'does not create an order' do
        expect { service.call }.not_to change(Order, :count)
      end
      
      it 'adds an error' do
        service.call
        expect(service.errors[:base]).to include(/insufficient stock/)
      end
    end
  end
end
```

## Performance Optimization

### Query Optimization
```ruby
# app/models/product.rb
class Product < ApplicationRecord
  # N+1 query prevention
  scope :with_associations, -> {
    includes(:category, :brand, :product_images)
      .preload(:reviews)
  }
  
  # Efficient counting
  counter_culture :category
  counter_culture :brand
  
  # Database views for complex queries
  def self.bestsellers
    connection.execute(<<-SQL).to_a
      SELECT * FROM bestselling_products_view
      WHERE month = DATE_TRUNC('month', CURRENT_DATE)
      LIMIT 10
    SQL
  end
end

# db/migrate/add_indexes_for_performance.rb
class AddIndexesForPerformance < ActiveRecord::Migration[7.0]
  def change
    # Composite indexes for common queries
    add_index :products, [:category_id, :published, :created_at]
    add_index :products, [:price, :published]
    
    # Partial indexes
    add_index :orders, :user_id, where: "status = 'pending'"
    
    # GIN index for full-text search
    enable_extension 'pg_trgm'
    add_index :products, :name, using: :gin, opclass: { name: :gin_trgm_ops }
  end
end
```

---

I leverage Rails conventions and its extensive ecosystem to build maintainable, scalable backend systems that follow the Rails way while seamlessly integrating with your existing project architecture and requirements.


