Creates Rails concerns for shared behavior across models or controllers with TDD. Use when extracting shared code, creating reusable modules, DRYing up models/controllers, or when user mentions concerns, modules, mixins, or shared behavior.
Install with Tessl CLI
npx tessl i github:fernandezbaptiste/rails_ai_agents --skill rails-concern90
Does it follow best practices?
If you maintain this skill, you can automatically optimize it using the tessl CLI to improve its score:
npx tessl skill review --optimize ./path/to/skillValidation for skill structure
Creates concerns (ActiveSupport::Concern modules) for shared behavior with specs first.
app/models/concerns/ or app/controllers/concerns/Good use cases:
Avoid concerns when:
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
endAlternative: 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
endFor 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
endbundle exec rspec spec/models/concerns/[concern_name]_spec.rb
# OR
bundle exec rspec spec/models/[model]_spec.rbModel 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
endController 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
endbundle exec rspec spec/models/concerns/[concern_name]_spec.rb# 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# 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# 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# 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# 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
endIn Models:
class Event < ApplicationRecord
include HasUuid
include SoftDeletable
include Searchable
endIn Controllers:
class ApplicationController < ActionController::Base
include Filterable
endextend ActiveSupport::Concernincluded block for callbacks/validations/scopesclass_methods block for class-level methods15fdeaf
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.