协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
concern-patterns【开发与工程】Creates and refactors model and controller concerns for shared behavior. Use when extracting shared code, organizing models with horizontal concerns, DRYing up controllers, or when user mentions concerns, mixins, or modules. WHEN NOT: Logic used by only one model (keep in place), service object extraction (use model-patterns), or job organization (us…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Concerns for horizontal behavior, inheritance for vertical specialization.
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
Each concern should be:
Closeable, Watchable, Searchable)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
# 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
# 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
# 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
# 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
Closeable, Publishable, Watchable, Assignable, Searchable, Eventable, Broadcastable, Readable, PositionableCardScoped, BoardScoped, FilterScoped, CurrentRequest, CurrentTimezone, Authentication# 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
app/models/[model]/[concern].rb or app/controllers/concerns/[concern].rbinclude ConcernName to models/controllersapp/models/card/closeable.rb or app/controllers/concerns/card_scoped.rbinclude ConcernNametest/models/concerns/closeable_test.rbSee references/concern-catalog.md for the full catalog of concern types.
extend ActiveSupport::Concern, namespace model concerns under the modelincluded do block for callbacks/associations, create concerns for one-off code