From lisa-rails
Best practices for Ruby on Rails models, splitting code into well-organized, maintainable code. Use when a model exceeds ~100 lines, has mixed responsibilities, or when the user asks to refactor, extract, clean up, or organize a Rails model. Applies patterns: concerns, service objects, query objects, form objects, and value objects.
How this skill is triggered — by the user, by Claude, or both
Slash command
/lisa-rails:active-record-model-best-practicesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
When refactoring a Rails model, analyze the file and extract code into the appropriate pattern based on what the code does. The model itself should only contain associations, enums, basic validations, and concern includes.
When refactoring a Rails model, analyze the file and extract code into the appropriate pattern based on what the code does. The model itself should only contain associations, enums, basic validations, and concern includes.
Read the model file and classify each block of code:
| Code type | Extract to | Location |
|---|---|---|
| Related scopes + simple methods sharing a theme | Concern | app/models/concerns/ |
| Business logic, multi-step operations, callbacks with side effects | Service object | app/services/ |
| Complex queries, multi-join scopes, reporting queries | Query object | app/queries/ |
| Context-specific validations (e.g. registration vs admin update) | Form object | app/forms/ |
| Domain concepts beyond a primitive (money, coordinates, scores) | Value object | app/models/ |
| Associations, enums, core validations, simple scopes | Keep on model | — |
Use for grouping related scopes, validations, callbacks, and simple instance methods that share a single theme. Name the concern after the capability it provides.
# app/models/concerns/searchable.rb
module Searchable
extend ActiveSupport::Concern
included do
scope :search, ->(query) { where("name ILIKE ?", "%#{query}%") }
end
def matching_terms(query)
name.scan(/#{Regexp.escape(query)}/i)
end
end
Use for business logic, orchestration of multiple models, and anything triggered by a user action that involves more than a simple CRUD operation. Follow the single-responsibility principle — one service, one operation.
# app/services/players/calculate_stats.rb
module Players
class CalculateStats
def initialize(player)
@player = player
end
def call
# complex logic here
end
end
end
Conventions:
Players::CalculateStatscallinitializeUse for complex database queries that involve joins, subqueries, CTEs, or multi-condition filtering that would clutter a model with scopes.
# app/queries/players/free_agent_query.rb
module Players
class FreeAgentQuery
def initialize(relation = Player.all)
@relation = relation
end
def call(filters = {})
@relation
.where(contract_status: :expired)
.where("age < ?", filters[:max_age])
.joins(:stats)
.order(war: :desc)
end
end
end
Conventions:
initialize (default to Model.all)callUse when validations only apply in specific contexts, or when a form spans multiple models.
# app/forms/player_registration_form.rb
class PlayerRegistrationForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :name, :string
attribute :email, :string
attribute :team_id, :integer
attribute :position, :string
validates :name, :email, :position, presence: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
def save
return false unless valid?
Player.create!(attributes)
end
end
Use for domain concepts that deserve their own identity beyond a raw primitive.
# app/models/batting_average.rb
class BattingAverage
include Comparable
def initialize(hits, at_bats)
@hits = hits
@at_bats = at_bats
end
def value
return 0.0 if @at_bats.zero?
(@hits.to_f / @at_bats).round(3)
end
def elite?
value >= 0.300
end
def <=>(other)
value <=> other.value
end
end
npx claudepluginhub codyswanngt/lisa --plugin lisa-railsDesigns Rails models using ActiveRecord patterns: validations, callbacks, scopes, associations, concerns, query objects, form objects. Enforces fat models, thin controllers, and N+1 prevention.
Refactors Ruby on Rails code applying Rails conventions, Sandi Metz rules, and idiomatic Ruby patterns while maintaining test coverage. Use proactively during refactoring.