rails-concern

Rails Concern Generator (TDD)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "rails-concern" with this command: npx skills add thibautbaissac/rails_ai_agents/thibautbaissac-rails-ai-agents-rails-concern

Rails Concern Generator (TDD)

Creates concerns (ActiveSupport::Concern modules) for shared behavior with specs first.

Quick Start

  • Write failing spec testing the concern behavior

  • Run spec to confirm RED

  • Implement concern in app/models/concerns/ or app/controllers/concerns/

  • Run spec to confirm GREEN

When to Use Concerns

Good use cases:

  • Shared validations across multiple models

  • Common scopes used by several models

  • Shared callbacks (e.g., UUID generation)

  • Controller authentication/authorization helpers

  • Pagination or filtering logic

Avoid concerns when:

  • Logic is only used in one place (YAGNI)

  • Creating "god" concerns with unrelated methods

  • Logic should be a service object instead

TDD Workflow

Step 1: Create Concern Spec (RED)

For Model Concerns, test via a model that includes it:

spec/models/concerns/[concern_name]_spec.rb

RSpec.describe [ConcernName] do

Create a test class that includes the concern

let(:test_class) do Class.new(ApplicationRecord) do self.table_name = "events" # Use existing table include [ConcernName] end end

let(:instance) { test_class.new }

describe "included behavior" do it "adds the expected methods" do expect(instance).to respond_to(:method_from_concern) end end

describe "#method_from_concern" do it "behaves as expected" do expect(instance.method_from_concern).to eq(expected_value) end end

describe "class methods" do it "adds scope" do expect(test_class).to respond_to(:scope_name) end end end

Alternative: Test through an actual model that uses the concern:

spec/models/event_spec.rb

RSpec.describe Event, type: :model do describe "[ConcernName] behavior" do describe "#method_from_concern" do let(:event) { build(:event) }

  it "does something" do
    expect(event.method_from_concern).to eq(expected)
  end
end

end end

For Controller Concerns, test via request specs:

spec/requests/[feature]_spec.rb

RSpec.describe "[Feature]", type: :request do describe "pagination (from Paginatable concern)" do let(:user) { create(:user) } before { sign_in user }

it "paginates results" do
  create_list(:resource, 30, account: user.account)
  get resources_path
  expect(response.body).to include("page")
end

end end

Step 2: Run Spec (Confirm RED)

bundle exec rspec spec/models/concerns/[concern_name]_spec.rb

OR

bundle exec rspec spec/models/[model]_spec.rb

Step 3: Implement Concern (GREEN)

Model Concern:

app/models/concerns/[concern_name].rb

module [ConcernName] extend ActiveSupport::Concern

included do # Callbacks before_validation :generate_uuid, on: :create

# Validations
validates :uuid, presence: true, uniqueness: true

# Scopes
scope :with_uuid, ->(uuid) { where(uuid: uuid) }
scope :recent, -> { order(created_at: :desc) }

end

Class methods

class_methods do def find_by_uuid!(uuid) find_by!(uuid: uuid) end end

Instance methods

def generate_uuid self.uuid ||= SecureRandom.uuid end

def short_uuid uuid&.split("-")&.first end end

Controller Concern:

app/controllers/concerns/[concern_name].rb

module [ConcernName] extend ActiveSupport::Concern

included do before_action :set_locale helper_method :current_locale end

class_methods do def skip_locale_for(*actions) skip_before_action :set_locale, only: actions end end

private

def set_locale I18n.locale = params[:locale] || I18n.default_locale end

def current_locale I18n.locale end end

Step 4: Run Spec (Confirm GREEN)

bundle exec rspec spec/models/concerns/[concern_name]_spec.rb

Common Concern Patterns

Pattern 1: UUID Generation

app/models/concerns/has_uuid.rb

module HasUuid extend ActiveSupport::Concern

included do before_validation :generate_uuid, on: :create validates :uuid, presence: true, uniqueness: true end

private

def generate_uuid self.uuid ||= SecureRandom.uuid end end

Pattern 2: Soft Delete

app/models/concerns/soft_deletable.rb

module SoftDeletable extend ActiveSupport::Concern

included do scope :active, -> { where(deleted_at: nil) } scope :deleted, -> { where.not(deleted_at: nil) }

default_scope { active }

end

def soft_delete update(deleted_at: Time.current) end

def restore update(deleted_at: nil) end

def deleted? deleted_at.present? end end

Pattern 3: Searchable

app/models/concerns/searchable.rb

module Searchable extend ActiveSupport::Concern

class_methods do def search(query) return all if query.blank?

  where("name ILIKE :q OR email ILIKE :q", q: "%#{query}%")
end

end end

Pattern 4: Auditable

app/models/concerns/auditable.rb

module Auditable extend ActiveSupport::Concern

included do has_many :audit_logs, as: :auditable, dependent: :destroy

after_create :log_creation
after_update :log_update

end

private

def log_creation audit_logs.create(action: "created", changes: attributes) end

def log_update return unless saved_changes.any? audit_logs.create(action: "updated", changes: saved_changes) end end

Pattern 5: Controller Filterable

app/controllers/concerns/filterable.rb

module Filterable extend ActiveSupport::Concern

private

def apply_filters(scope, allowed_filters) allowed_filters.each do |filter| if params[filter].present? scope = scope.where(filter => params[filter]) end end scope end end

Usage

In Models:

class Event < ApplicationRecord include HasUuid include SoftDeletable include Searchable end

In Controllers:

class ApplicationController < ActionController::Base include Filterable end

Checklist

  • Spec written first (RED)

  • Uses extend ActiveSupport::Concern

  • included block for callbacks/validations/scopes

  • class_methods block for class-level methods

  • Instance methods outside blocks

  • Single responsibility (one purpose per concern)

  • Well-named (describes what it adds)

  • All specs GREEN

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

hotwire-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

i18n-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

viewcomponent-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

rails-controller

No summary provided by upstream source.

Repository SourceNeeds Review