# rails-state-resources > Use when modeling state changes as RESTful resources (open/close, pin/unpin). Covers CRUD-based state transitions. - Author: Michael Lee - Repository: mrleeio/claude-plugins - Version: 20260128203525 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-06 - Source: https://github.com/mrleeio/claude-plugins - Web: https://mule.run/skillshub/@@mrleeio/claude-plugins~rails-state-resources:20260128203525 --- --- name: rails-state-resources description: Use when modeling state changes as RESTful resources (open/close, pin/unpin). Covers CRUD-based state transitions. --- # Model State Changes as CRUD on Sub-Resources Instead of adding custom actions like `post :close` or `patch :archive`, model state transitions as creating or destroying a sub-resource. This keeps controllers RESTful and thin. ## The Pattern | State Change | Sub-Resource | Create | Destroy | |-------------|--------------|--------|---------| | Open/Close | `closure` | close | reopen | | Pin/Unpin | `pin` | pin | unpin | | Watch/Unwatch | `watch` | watch | unwatch | | Archive/Unarchive | `archival` | archive | unarchive | | Publish/Unpublish | `publication` | publish | unpublish | | Lock/Unlock | `lock` | lock | unlock | ## Routes ```ruby # config/routes.rb resources :cards do resource :closure, only: [:create, :destroy] resource :pin, only: [:create, :destroy] resource :watch, only: [:create, :destroy] end resources :boards do resource :publication, only: [:create, :destroy] resource :archival, only: [:create, :destroy] end ``` ## Controller Implementation ```ruby # app/controllers/cards/closures_controller.rb class Cards::ClosuresController < ApplicationController before_action :set_card def create @card.close redirect_to @card, notice: "Card closed" end def destroy @card.reopen redirect_to @card, notice: "Card reopened" end private def set_card @card = Current.account.cards.find(params[:card_id]) end end ``` For Turbo/Hotwire responses: ```ruby # app/controllers/cards/closures_controller.rb class Cards::ClosuresController < ApplicationController before_action :set_card def create @card.close respond_to do |format| format.html { redirect_to @card } format.turbo_stream { render_card_update } end end def destroy @card.reopen respond_to do |format| format.html { redirect_to @card } format.turbo_stream { render_card_update } end end private def set_card @card = Current.account.cards.find(params[:card_id]) end def render_card_update render turbo_stream: turbo_stream.replace(@card, partial: "cards/card", locals: { card: @card }) end end ``` ## Model Implementation The model uses the Closeable concern (see `rails-model-conventions` skill): ```ruby # app/models/card/closeable.rb module Card::Closeable extend ActiveSupport::Concern included do has_one :closure, dependent: :destroy scope :closed, -> { joins(:closure) } scope :open, -> { where.missing(:closure) } end def closed? closure.present? end def open? !closed? end def close(user: Current.user) unless closed? transaction do create_closure!(user: user) track_event :closed, creator: user end end end def reopen(user: Current.user) if closed? transaction do closure.destroy track_event :reopened, creator: user end end end end ``` ## Join Model ```ruby # app/models/closure.rb class Closure < ApplicationRecord belongs_to :card belongs_to :user, default: -> { Current.user } end ``` Migration: ```ruby class CreateClosures < ActiveRecord::Migration[7.1] def change create_table :closures, id: :uuid do |t| t.references :card, null: false, foreign_key: true, type: :uuid, index: { unique: true } t.references :user, null: false, foreign_key: true, type: :uuid t.timestamps end end end ``` ## View Integration Toggle buttons that switch between create/destroy: ```erb <%# app/views/cards/_card.html.erb %>