skills/rails-query-object/SKILL.md
Creates query objects for complex database queries following TDD. Use when encapsulating complex queries, aggregating statistics, building reports, or when user mentions queries, stats, dashboards, or data aggregation.
npx skillsauth add fernandezbaptiste/rails_ai_agents rails-query-objectInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Creates query objects that encapsulate complex database queries with specs first.
spec/queries/app/queries/Query objects in this project:
user: or account:)ActiveRecord::Relation for chainability OR Hash for aggregationscall method for primary operation# spec/queries/[name]_query_spec.rb
RSpec.describe [Name]Query do
subject(:query) { described_class.new(account: account) }
let(:user) { create(:user) }
let(:account) { user.account }
let(:other_account) { create(:user).account }
# Test data for current account
let!(:resource1) { create(:resource, account: account) }
let!(:resource2) { create(:resource, account: account) }
# Test data for other account (should not appear)
let!(:other_resource) { create(:resource, account: other_account) }
describe "#initialize" do
it "requires an account parameter" do
expect { described_class.new }.to raise_error(ArgumentError)
end
it "stores the account" do
expect(query.account).to eq(account)
end
end
describe "#call" do
it "returns expected result type" do
expect(query.call).to be_a(ActiveRecord::Relation)
# OR for hash results:
# expect(query.call).to be_a(Hash)
end
it "only returns resources for the account (multi-tenant)" do
result = query.call
expect(result).to include(resource1, resource2)
expect(result).not_to include(other_resource)
end
end
describe "multi-tenant isolation" do
it "ensures account A cannot see account B data" do
other_query = described_class.new(account: other_account)
expect(query.call).not_to include(other_resource)
expect(other_query.call).not_to include(resource1)
end
end
end
bundle exec rspec spec/queries/[name]_query_spec.rb
# app/queries/[name]_query.rb
class [Name]Query
attr_reader :account
def initialize(account:)
@account = account
end
# Returns [description of result]
# @return [ActiveRecord::Relation<Resource>] OR [Hash]
def call
account.resources
.where(condition: value)
.order(created_at: :desc)
end
end
bundle exec rspec spec/queries/[name]_query_spec.rb
# app/queries/stale_leads_query.rb
class StaleLeadsQuery
attr_reader :account
def initialize(account:)
@account = account
end
def call
account.leads.stale
end
end
# app/queries/dashboard_stats_query.rb
class DashboardStatsQuery
attr_reader :user, :account
def initialize(user:)
@user = user
@account = user.account
end
def upcoming_events(limit: 3)
account.events
.where("event_date >= ?", Date.today)
.order(event_date: :asc)
.limit(limit)
end
def pending_commissions_total
EventVendor
.joins(:event)
.where(events: { account_id: account.id })
.where(commission_status: :to_invoice)
.sum(:commission_value)
end
def top_vendors(limit: 5)
account.vendors
.left_joins(:event_vendors)
.select("vendors.*, COUNT(event_vendors.id) as events_count")
.group("vendors.id")
.order("events_count DESC")
.limit(limit)
end
def leads_by_status
account.leads.group(:status).count
end
end
# app/queries/leads_by_status_query.rb
class LeadsByStatusQuery
attr_reader :account
def initialize(account:)
@account = account
end
def call
leads = account.leads.order(created_at: :desc)
result = Lead.statuses.keys.map(&:to_sym).index_with { [] }
leads.group_by(&:status).each do |status, status_leads|
result[status.to_sym] = status_leads
end
result
end
end
# Simple query
def index
@leads_by_status = LeadsByStatusQuery.new(account: current_account).call
end
# Aggregation query with presenter
def index
stats_query = DashboardStatsQuery.new(user: current_user)
@stats = DashboardStatsPresenter.new(stats_query)
end
user: or account:)@return).includes() to prevent N+1development
Creates ViewComponents for reusable UI elements with TDD. Use when building reusable UI components, extracting complex partials, creating cards/tables/badges/modals, or when user mentions ViewComponent, components, or reusable UI.
development
Guides Test-Driven Development workflow with Red-Green-Refactor cycle. Use when the user wants to implement a feature using TDD, write tests first, follow test-driven practices, or mentions red-green-refactor.
data-ai
Configures Solid Queue for background jobs in Rails 8. Use when setting up background processing, creating background jobs, configuring job queues, or migrating from Sidekiq to Solid Queue.
testing
Creates service objects following single-responsibility principle with comprehensive specs. Use when extracting business logic from controllers, creating complex operations, implementing interactors, or when user mentions service objects or POROs.