concern-patterns
>-
pinned to #03622f2updated 2 weeks ago
Ask your AI client: “install skills/concern-patterns”.
Requires the metahub MCP server installed in your client. Set up MCP.
mh install skills/concern-patternsmetahub onboarded this repo on the author's behalf.
If you own github.com/ThibautBaissac/rails_ai_agents on GitHub, claim the listing to take over publishing. Your claim preserves the existing eval history and badges; only the curator label is replaced with verified-publisher on your next publish.
Stars
627
Last commit
2 weeks ago
Latest release
published
About this skill
Pulled from SKILL.md at publish time.
Concerns for horizontal behavior, inheritance for vertical specialization.
Evaluation report
WarningsAutomated checks the publisher passed at publish time — structure, docs, safety, and whether the artifact behaves as claimed.03622f2· 2 weeks ago
Documentation
32Description qualitywarn
17 words · 121 chars — manifest description is empty; graded the GitHub repo description instead
A skill's manifest description doubles as its trigger — add one to SKILL.md (15+ words, e.g. “use this skill when …”).
README is present and substantial
19,245 chars · 9 sections · 6 code blocks
Tags / topics declaredwarn
No manifest tags and no GitHub repo topics
Add tags to the manifest (or GitHub topics on the repo) so the registry's search and category filters surface this artifact.
README has usage / example sections
found: Quick Start
Homepage / docs URL declared
https://thibautbaissac.github.io/
Release history
1- releasecurrent03622f2warn2 weeks ago
Contents
Concerns for horizontal behavior, inheritance for vertical specialization.
Project knowledge
Tech Stack: Rails 8.2 (edge), ActiveSupport::Concern
Location: app/models/[model]/ for model concerns, app/controllers/concerns/ for controller concerns
Commands:
ls app/models/concerns/ # List shared concerns
ls app/models/card/ # List Card concerns
bin/rails runner "puts Card.included_modules" # Check usage
bin/rails test test/models/ # Run model tests
Core principles
Each concern should be:
- Self-contained: All related code (associations, validations, scopes, methods) in one place
- Cohesive: Focused on one aspect (e.g.,
Closeable,Watchable,Searchable) - Composable: Models include multiple concerns to build up behavior
When to extract a concern
Extract when you see:
-
Repeated associations across models
# Multiple models have: has_many :comments, as: :commentable # Extract to: app/models/concerns/commentable.rb -
Repeated state patterns
# Multiple models have close/reopen pattern # Extract to: Card::Closeable, Board::Publishable, etc. -
Repeated scopes
# Multiple models have: scope :recent, -> { order(created_at: :desc) } # Extract to: Timestampable concern -
Repeated controller patterns
# Multiple controllers load parent resource # Extract to: ParentScoped concern
Do NOT extract when:
- Code is used by only one model (YAGNI)
- You'd create a god concern with unrelated methods
- Logic should be in explicit model methods instead
Model concern structure
State management concern
# app/models/card/closeable.rb
module Card::Closeable
extend ActiveSupport::Concern
included do
has_one :closure, dependent: :destroy
scope :open, -> { where.missing(:closure) }
scope :closed, -> { joins(:closure) }
end
def close(user: Current.user)
create_closure!(user: user)
track_event "card_closed", user: user
end
def reopen
closure&.destroy!
track_event "card_reopened"
end
def closed?
closure.present?
end
def open?
!closed?
end
def closed_at
closure&.created_at
end
def closed_by
closure&.user
end
end
Association concern
# app/models/card/assignable.rb
module Card::Assignable
extend ActiveSupport::Concern
included do
has_many :assignments, dependent: :destroy
has_many :assignees, through: :assignments, source: :assignee
scope :assigned_to, ->(users) { joins(:assignments).where(assignments: { assignee: users }).distinct }
scope :unassigned, -> { where.missing(:assignments) }
end
def assign(user)
assignments.create!(user: user) unless assigned_to?(user)
track_event "card_assigned", user: user, particulars: { assignee_id: user.id }
end
def unassign(user)
assignments.where(user: user).destroy_all
end
def assigned_to?(user)
assignees.include?(user)
end
end
Behavior concern with class methods
# app/models/card/searchable.rb
module Card::Searchable
extend ActiveSupport::Concern
included do
scope :search, ->(query) { where("title LIKE ? OR body LIKE ?", "%#{query}%", "%#{query}%") }
end
class_methods do
def search_with_ranking(query)
search(query).order("search_rank DESC")
end
def top_results(query, limit: 10)
search_with_ranking(query).limit(limit)
end
end
end
Controller concern structure
# app/controllers/concerns/card_scoped.rb
module CardScoped
extend ActiveSupport::Concern
included do
before_action :set_card
before_action :set_board
end
private
def set_card
@card = Current.account.cards.find(params[:card_id])
end
def set_board
@board = @card.board
end
def render_card_replacement
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
dom_id(@card, :card_container),
partial: "cards/container",
locals: { card: @card.reload }
)
end
format.html { redirect_to @card }
end
end
end
Naming conventions
- Model concerns (adjectives):
Closeable,Publishable,Watchable,Assignable,Searchable,Eventable,Broadcastable,Readable,Positionable - Controller concerns (nouns):
CardScoped,BoardScoped,FilterScoped,CurrentRequest,CurrentTimezone,Authentication
Testing concerns
Test in isolation:
# test/models/concerns/closeable_test.rb
class CloseableTest < ActiveSupport::TestCase
setup do
@card = cards(:logo)
end
test "close creates closure record" do
assert_difference -> { Closure.count }, 1 do
@card.close
end
assert @card.closed?
end
test "reopen destroys closure record" do
@card.close
assert_difference -> { Closure.count }, -1 do
@card.reopen
end
assert @card.open?
end
test "closed scope finds closed records" do
@card.close
assert_includes Card.closed, @card
refute_includes Card.open, @card
end
end
Refactoring workflow
- Identify the pattern -- Find duplicated code across models/controllers
- Name the concern -- Use an adjective describing the capability
- Create the file --
app/models/[model]/[concern].rborapp/controllers/concerns/[concern].rb - Move code -- Associations, validations, scopes, methods
- Include it -- Add
include ConcernNameto models/controllers - Write tests -- Test concern in isolation and in context
- Remove duplication -- Delete the old code from models/controllers
Files to create
- Concern file:
app/models/card/closeable.rborapp/controllers/concerns/card_scoped.rb - Model/Controller: Add
include ConcernName - Test file:
test/models/concerns/closeable_test.rb
See references/concern-catalog.md for the full catalog of concern types.
Boundaries
- Always: Extract repeated code into concerns, keep concerns focused on one aspect, include all related code (associations, scopes, methods), write tests, use
extend ActiveSupport::Concern, namespace model concerns under the model - Ask first: Before creating concerns that span multiple domains, before extracting concerns with complex dependencies, before modifying widely-used concerns
- Never: Create god concerns with too many responsibilities, use concerns to hide service objects, skip
included doblock for callbacks/associations, create concerns for one-off code
Reviews
No reviews yet. Be the first.
Related
Verification Before Completion
Evidence before assertions, always
Writing Plans
Turn specs into phased implementation plans
Test-Driven Development
Red → green → refactor discipline for any feature or bugfix
mh install skills/concern-patterns