Skip to content

Chapter 2: Creating Your First Resource ​

In this chapter, you'll create the Post resource - the core of our blog application.

What is a Resource? ​

In Plutonium, a resource is a complete unit consisting of:

  • Model - The data structure (ActiveRecord)
  • Definition - How the resource renders (fields, actions, UI)
  • Policy - Who can do what (authorization)
  • Controller - HTTP handling (auto-generated or custom)

Generating a Feature Package ​

First, let's create a feature package to hold our blogging logic:

bash
rails generate pu:pkg:package blogging

This creates:

packages/blogging/
├── app/
│   ├── controllers/blogging/
│   ├── definitions/blogging/
│   ├── interactions/blogging/
│   ├── models/blogging/
│   ├── policies/blogging/
│   └── views/blogging/
└── lib/
    └── engine.rb

Generating the Post Resource ​

Now generate the Post resource inside the blogging package:

bash
rails generate pu:res:scaffold Post title:string body:text 'published:boolean?' --dest=blogging

This creates:

Model (packages/blogging/app/models/blogging/post.rb) ​

ruby
class Blogging::Post < Blogging::ResourceRecord
  # Add validations, associations, and business logic here
end

Add scopes for filtering posts by publication status:

ruby
class Blogging::Post < Blogging::ResourceRecord
  scope :published, -> { where(published: true) }
  scope :drafts, -> { where(published: [false, nil]) }
end

Definition (packages/blogging/app/definitions/blogging/post_definition.rb) ​

ruby
class Blogging::PostDefinition < Blogging::ResourceDefinition
  # Customize field rendering, forms, and UI here
end

Policy (packages/blogging/app/policies/blogging/post_policy.rb) ​

ruby
class Blogging::PostPolicy < Blogging::ResourcePolicy
  def create?
    true
  end

  def read?
    true
  end

  def permitted_attributes_for_create
    [:title, :body, :published]
  end

  def permitted_attributes_for_read
    [:title, :body, :published]
  end

  def permitted_associations
    %i[]
  end
end

Migration ​

ruby
class CreateBloggingPosts < ActiveRecord::Migration[8.0]
  def change
    create_table :blogging_posts do |t|
      t.string :title, null: false
      t.text :body, null: false
      t.boolean :published

      t.timestamps
    end
  end
end

Running the Migration ​

bash
rails db:prepare

Creating a Portal ​

Resources need a portal to be accessible via the web. Let's create a public admin portal so we can explore the UI right away — we'll add authentication in Chapter 3.

bash
rails generate pu:pkg:portal admin --public

This creates the AdminPortal package mounted at /admin. The --public flag wires the portal's controller with Plutonium::Auth::Public, so any visitor can access it. (Other options: --auth=ACCOUNT to gate via a Rodauth account, or --byo for your own auth.)

Connecting the Resource ​

Connect the Post resource to the admin portal:

bash
rails generate pu:res:conn Blogging::Post --dest=admin_portal

This:

  1. Adds routes for Post in the admin portal
  2. Creates a portal-specific controller
  3. Registers the resource with the portal

Starting the Server ​

bash
bin/dev

Visit http://localhost:3000/admin/blogging/posts. You should see an empty posts table with a "New Post" button:

Empty posts index

Click "New" — the form is automatically generated from your model's attributes. By default Plutonium opens it as a slideover (right) so you keep the index visible; visiting /admin/blogging/posts/new directly renders the same form as a standalone page (left):

Default — slideover from indexStandalone page (direct URL)
Slideover new formStandalone new form

To always render full-page instead, set modal false in the definition. To pick a different style, use modal :centered. See Reference › Resource › Definition › Modal.

Create a few posts and the table fills in:

Posts index with rows

Understanding Auto-Detection ​

Notice that we wrote almost no configuration code. Plutonium auto-detected:

  • Fields from the model's columns
  • Validations from the model's validators
  • Associations from belongs_to/has_many
  • Form inputs based on column types
  • Table columns from the policy's permitted attributes

This is Plutonium's core philosophy: convention over configuration. You only write code when you need to change the defaults.

Customizing Table Columns ​

By default, Plutonium shows all permitted attributes in the table. To customize which columns appear, use the policy's permitted_attributes_for_index method:

ruby
# packages/blogging/app/policies/blogging/post_policy.rb
module Blogging
  class PostPolicy < Blogging::ResourcePolicy
    # Control which columns appear in the index table
    def permitted_attributes_for_index
      [:title, :published, :created_at]
    end
  end
end

Refresh the page. The table now shows only the columns you specified.

What's Next ​

Our resource is working, but anyone can access it. In the next chapter, we'll add authentication with Rodauth.

Continue to Chapter 3: Setting Up Authentication →

Released under the MIT License.