agentskills.codes
PL

plutonium-behavior

Use BEFORE writing or overriding a Plutonium controller, policy, or interaction class. Covers controller hooks, policy methods, permitted attributes, relation_scope, interaction structure, outcomes, and chaining. The single source for "how does this resource actually do things".

Install

mkdir -p .claude/skills/plutonium-behavior && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14694" && unzip -o skill.zip -d .claude/skills/plutonium-behavior && rm skill.zip

Installs to .claude/skills/plutonium-behavior

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Use BEFORE writing or overriding a Plutonium controller, policy, or interaction class. Covers controller hooks, policy methods, permitted attributes, relation_scope, interaction structure, outcomes, and chaining. The single source for "how does this resource actually do things".
279 charsno explicit “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)

About this skill

Plutonium Behavior — Controllers, Policies, Interactions

The behavior layer is intentionally thin: controllers route, policies authorize, interactions act. Registering an action and rendering it lives in [[plutonium-resource]] — this skill covers how to write the controller hook, policy method, or interaction class behind it.

For tenant-scoped relation_scope and entity scoping, load [[plutonium-tenancy]].

🚨 Critical (read first)

  • Use generators. pu:res:scaffold creates the base trio (controller/policy/interaction-base); pu:res:conn creates portal-specific versions. Never hand-write them.
  • Don't override CRUD actions. Use hooks (resource_params, redirect_url_after_submit, presentation hooks). Overriding create/update usually breaks authorization, params filtering, or both.
  • create? and read? default to false. Always override them explicitly. Derived methods (update?, show?, etc.) inherit automatically.
  • permitted_attributes_for_* must be explicit in production. Dev auto-detection works; production raises.
  • ActiveRecord::RecordInvalid is NOT rescued automatically in interactions. Always rescue when using create! / update! / save!, return failed(e.record.errors).
  • Return succeed(...) or failed(...) from execute — the controller can't tell what happened otherwise.
  • Redirect is automatic on success — only use with_redirect_response for a different destination.
  • relation_scope must end up calling default_relation_scope(relation) somewhere in the chain. Prefer calling it explicitly. super works when extending a parent policy (e.g., a package base) that itself calls it. See [[plutonium-tenancy]].
  • For has_cents fields, use the virtual name (:price), not :price_cents in permitted_attributes_for_*.
  • Custom action ⇒ policy method. action :publish needs def publish? on the policy (undefined methods return false).
  • Named custom routes. When adding custom routes, always pass as: so resource_url_for can build URLs.

🛑 Before you write behavior: place it in the right layer (ASK — don't infer)

"Make X happen" doesn't say where X lives. Put it in the wrong layer and you get authorization that doesn't authorize, a 500 on the happy path, or a CRUD override that breaks params/auth. First place the requirement, then confirm names against the real code (next section):

The requirement (in plain words)Goes inNOT in
"only <role/owner> may do X" — who is allowedPolicy def x?a condition: proc — that only hides the button; the route stays live and callable
"doing X changes state / sends mail / charges a card" — the workInteraction execute, registered as an actiona hand-written controller action; an override of create/update
"after create/update go to Y" · "munge a param" · "reshape the index query"Controller hook (redirect_url_after_submit, resource_params, filtered_resource_collection)overriding create/update/index
"which fields are visible / editable"Policy permitted_attributes_for_*the definition — that only controls how a field renders

Then resolve the specifics:

  1. A custom action needs BOTH: an interaction (the work) and a policy def <action>? (the authorization). Miss the policy method ⇒ the action silently returns false (dead button). Put the role check in condition: ⇒ it isn't enforced — a direct POST still runs.
  2. create?/read? default to false — override explicitly; derived methods (update?/show?/…) inherit.
  3. Any create!/update!/save! in execute ⇒ rescue ActiveRecord::RecordInvalidfailed(e.record.errors). Not auto-rescued — otherwise a validation failure 500s.
  4. has_cents ⇒ permit :price, never :price_cents.
  5. New vs editing — never re-scaffold a controller/policy/interaction that's been customized.

Never ship a guessed role method, column, enum value, or association as applied code. user.finance?, record.status_approved?, expense.submitted_by either exist in the app or they don't — confirm them before writing, don't assume. Fall back to AskUserQuestion only for genuine product choices (what the rule should be), never for facts you can read.

✅ Before you edit: verify the ground truth (CHECK — read it, don't ask for it)

You have file access — inspect; don't ask the user to describe their own app.

CheckHowWhy it matters
File already customizedRead app/policies/<x>_policy.rb, the controller, app/interactions/*Edit incrementally — re-scaffolding clobbers customizations
The role/method you authorize on existsgrep the user model for def finance? / enum :role / has_role?user.finance? 500s (or is silently false) if absent
The columns/enum your interaction writesRead the model + db/schema.rb for the enum value, approved_by/approved_at, the submitter assocupdate!(status: :approved) raises if the value/column is missing
Action not already wiredgrep the definition for action :<x>; grep the policy for def <x>?Avoids duplicate or dead actions
Cross-resource accessUse authorized_resource_scope / allowed_to?, never raw where/findRaw queries bypass the other resource's tenancy + visibility

Inspect with your own tools before proposing code.

🛠 Use the generator — and know what's hand-authored

TaskHowVerify first
Base trio (controller + policy + interaction-base)pu:res:scaffoldNew resource
Portal-specific controller/policypu:res:conn … --dest=portalResource exists
A custom-action interactionHand-author in app/interactions/<name>_interaction.rb (subclass ResourceInteraction) — there is NO pu:res:interaction generator; don't invent one
Edit an existing customized policy/controller/interactionHand-edit the fileIt was already generated — re-scaffolding clobbers it

Part 1 — Controllers

Plutonium controllers ship full CRUD out of the box; nearly all customization lives in definitions / policies / interactions. The controller stays thin.

Base classes

# app/controllers/resource_controller.rb (installed once)
class ResourceController < ApplicationController
  include Plutonium::Resource::Controller
end

# app/controllers/posts_controller.rb (per resource, generated by pu:res:scaffold)
class PostsController < ::ResourceController
  # Empty — all CRUD inherited
end

What you get for free

ActionRoutePurpose
indexGET /postsList with pagination, search, filters, sorting
showGET /posts/:idDisplay single record
newGET /posts/newForm
createPOST /postsCreate
editGET /posts/:id/editForm
updatePATCH /posts/:idUpdate
destroyDELETE /posts/:idDelete

Plus interactive-action routes for every action declared in the definition.

Where customization belongs

ConcernLives in
Field rendering (inputs, displays, columns)Definition
Search, filters, scopes, sortingDefinition
Custom operations (publish, archive, import)Interaction (+ action in definition)
Authorization rulesPolicy
Form/show/page chromeDefinition (custom page classes)
Custom redirect logicController hook
Param mungingController hook
Custom index query shapeController hook
Presentation of parent/entity fieldsController hook

Override hooks

All hooks are private methods. Override only the ones you need.

Redirect hooks

class PostsController < ::ResourceController
  private

  # Where to go after create/update: "show" (default), "edit", "new", "index"
  def preferred_action_after_submit = "edit"

  # Custom URL after create/update (overrides preferred_action_after_submit)
  def redirect_url_after_submit = posts_path

  # Custom URL after destroy
  def redirect_url_after_destroy = posts_path
end

Parameter hook

def resource_params
  params = super
  params[:tags] = params[:tags].split(",") if params[:tags].is_a?(String)
  params
end

Index query hook

def filtered_resource_collection
  base = current_authorized_scope
  base = base.featured if params[:featured]
  current_query_object.apply(base, raw_resource_query_params)
end

Presentation hooks

Control whether parent / scoped-entity fields appear in forms and displays. Defaults are false (hidden, since they're inferred from the URL/portal).

def present_parent?         = true   # show parent field on displays
def submit_parent?          = true   # include parent field in forms (default: tracks present_parent?)
def present_scoped_entity?  = true
def submit_scoped_entity?   = true

Custom actions

Prefer interactive actions (definition + interaction) for anything with business logic. The only reason to hand-write a controller action is unusual flows (custom response shapes, external service callbacks, etc.).

class PostsController < ::ResourceController
  def publish
    authorize_current!(resource_record!, to: :publish?)
    resource_record!.update!(published: true)
    redirect_to resource_url_for(resource_record!), notice: "Published!"
  end
end

Route must be named:

resources :posts do
  member { post :publish, as: :publish }   # `as:` required!
end

Key methods

Resource access

resource_class            # The model class
resource_record!          # Current record (raises if not found)
resource_record?          # Current record (nil if not found)
resource_params           # Permitted params for create/update
current_parent            # Parent record for nested routes
current_scoped_entity     # Tenant e

---

*Content truncated.*

Search skills

Search the agent skills registry