PL

plutonium-behavior

Single source of truth for Plutonium resource behavior, covering controllers, policies, and interactions.

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)
Advanced

Key capabilities

  • Define authorization rules for actions
  • Implement business logic for resource modifications
  • Customize controller behavior with hooks
  • Specify permitted attributes for resource updates
  • Handle validation errors from database operations

How it works

This skill guides the implementation of Plutonium behavior layers by defining where authorization, business logic, and controller customizations should reside within controllers, policies, and interaction classes.

Inputs & outputs

You give it
User request to implement a controller, policy, or interaction
You get back
Functional controller hook, policy method, or interaction class

When to use plutonium-behavior

  • Write a new interaction class
  • Update controller policy methods
  • Define permitted attributes for a resource

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.
  • An interaction is a presentation object (it can only be built with a view_context). Logic may start in execute; the second caller — a job, an API controller, a rake task, the console — is the trigger to move it onto the model. Don't pre-extract, and don't invent a service layer. See Part 3 › Where the logic goes.
  • 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
"there's a button that does X" — the triggerInteraction (+ action in the definition)a hand-written controller action; an override of create/update
"doing X changes state / sends mail / charges a card" — the worka named model method the interaction calls (post.publish!) — inline in execute is fine while the button is the only callera service-object layer; three chained interactions
"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) — the buttonInteraction (+ action in definition)
The operation itself, once a job/API/task also needs itThe model (post.publish!) — see Part 3 › Where the logic goes
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

Don't add eager loading unprompted. Which associations a page renders is decided by the definition, so an includes list written now is a guess that goes stale when a column is added. Adding one is a performance change the user didn't ask for.

When a user actually reports a slow index or an N+1: suggest goldiloader first — it eager-loads on traversal, so it tracks whatever the definition renders and needs no list to maintain. Only hand-write def filtered_resource_collection = super.includes(...) if they decline the gem, and use the policy's relation_scope instead when the association is also read on show/export/typeahead. Full detail: [Guides › Perfor


Content truncated.

When not to use it

  • When the task is to register an action or render it
  • When the task involves tenant-scoped relation_scope or entity scoping
  • When the task is about custom interaction form templates or page classes

Limitations

  • Does not cover registering actions or rendering them.
  • Does not cover tenant-scoped relation_scope or entity scoping.
  • Does not cover custom interaction form templates or page classes.

How it compares

This skill provides a structured framework for implementing application behavior, separating concerns into policies for authorization, interactions for actions, and controllers for routing, unlike a monolithic approach.

Compared to similar skills

plutonium-behavior side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
plutonium-behavior (this skill)01moReviewAdvanced
architecture-patterns552moNo flagsAdvanced
kotlin-multiplatform323moReviewAdvanced
nodejs-best-practices286moNo flagsAdvanced

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

architecture-patterns

wshobson

Implement proven backend architecture patterns including Clean Architecture, Hexagonal Architecture, and Domain-Driven Design. Use when architecting complex backend systems or refactoring existing applications for better maintainability.

55214

kotlin-multiplatform

vitorpamplona

Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific, source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues. Triggers on: abstraction decisions ("should I share this?"), source set placement questions, expect/actual creation, build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.

32156

nodejs-best-practices

davila7

Node.js development principles and decision-making. Framework selection, async patterns, security, and architecture. Teaches thinking, not copying.

28120

workflow-orchestration-patterns

wshobson

Design durable workflows with Temporal for distributed systems. Covers workflow vs activity separation, saga patterns, state management, and determinism constraints. Use when building long-running processes, distributed transactions, or microservice orchestration.

10117

java-pro

sickn33

Master Java 21+ with modern features like virtual threads, pattern matching, and Spring Boot 3.x. Expert in the latest Java ecosystem including GraalVM, Project Loom, and cloud-native patterns. Use PROACTIVELY for Java development, microservices architecture, or performance optimization.

3492

arm-cortex-expert

sickn33

Senior embedded software engineer specializing in firmware and driver development for ARM Cortex-M microcontrollers (Teensy, STM32, nRF52, SAMD). Decades of experience writing reliable, optimized, and maintainable embedded code with deep expertise in memory barriers, DMA/cache coherency, interrupt-driven I/O, and peripheral drivers.

2975

Search skills

Search the agent skills registry