
plutonium-portal
by radioactive-labs
Build production-ready Rails apps in minutes, not days. Convention-driven, fully customizable, AI-ready.
SKILL.md
name: plutonium-portal description: Plutonium portals - web interfaces with authentication, entity scoping, and routes
Plutonium Portals
Portals are Rails engines that provide web interfaces for specific user types.
Creating a Portal
rails g pu:pkg:portal dashboard
Generator Options
| Option | Description |
|---|---|
--auth=NAME | Rodauth account to authenticate with (e.g., --auth=user) |
--public | Grant public access (no authentication) |
--byo | Bring your own authentication |
# Non-interactive examples
rails g pu:pkg:portal admin --auth=admin
rails g pu:pkg:portal api --public
rails g pu:pkg:portal custom --byo
Without flags, the generator prompts interactively:
- Rodauth account - Use existing Rodauth authentication
- Public access - No authentication required
- Bring your own - Implement custom
current_user
Portal Engine
# packages/dashboard_portal/lib/engine.rb
module DashboardPortal
class Engine < Rails::Engine
include Plutonium::Portal::Engine
config.after_initialize do
# Optional: multi-tenancy
scope_to_entity Organization, strategy: :path
end
end
end
Authentication
Rodauth Integration
# packages/dashboard_portal/app/controllers/dashboard_portal/concerns/controller.rb
module DashboardPortal
module Concerns
module Controller
extend ActiveSupport::Concern
include Plutonium::Portal::Controller
include Plutonium::Auth::Rodauth(:user) # Use :user account
end
end
end
Public Access
module DashboardPortal
module Concerns
module Controller
extend ActiveSupport::Concern
include Plutonium::Portal::Controller
include Plutonium::Auth::Public
end
end
end
Custom Authentication
module DashboardPortal
module Concerns
module Controller
extend ActiveSupport::Concern
include Plutonium::Portal::Controller
include Plutonium::Auth::Public
def current_user
@current_user ||= User.find_by(api_key: request.headers["X-API-Key"])
end
end
end
end
Entity Scoping (Multi-tenancy)
Automatically scope all data to a parent entity.
Path Strategy
Entity ID in URL path:
module AdminPortal
class Engine < Rails::Engine
include Plutonium::Portal::Engine
config.after_initialize do
scope_to_entity Organization, strategy: :path
end
end
end
Routes become: /organizations/:organization_id/posts
Custom Strategy
Implement your own lookup method:
module AdminPortal
class Engine < Rails::Engine
include Plutonium::Portal::Engine
config.after_initialize do
scope_to_entity Organization, strategy: :current_organization
end
end
end
# In controller concern
module AdminPortal
module Concerns
module Controller
extend ActiveSupport::Concern
include Plutonium::Portal::Controller
private
# Method name must match strategy
def current_organization
@current_organization ||= Organization.find_by!(subdomain: request.subdomain)
end
end
end
end
Accessing the Scoped Entity
current_scoped_entity # The current Organization/Account/etc.
scoped_to_entity? # true if scoping is active
Model Requirements
Models must have an association path to the scoped entity:
# Direct association (preferred)
class Post < ResourceRecord
belongs_to :organization
end
# Through association
class Comment < ResourceRecord
belongs_to :post
has_one :organization, through: :post
end
# Complex (define custom scope)
class AuditLog < ResourceRecord
scope :associated_with_organization, ->(org) {
joins(:user).where(users: { organization_id: org.id })
}
end
Routes
Portal Routes
# packages/dashboard_portal/config/routes.rb
DashboardPortal::Engine.routes.draw do
root to: "dashboard#index"
# Register resources
register_resource ::Post
register_resource Blogging::Comment
# Custom routes
get "settings", to: "settings#index"
end
Custom Routes on Resources
Add member or collection routes with a block:
register_resource ::Post do
member do
get :preview
get :analytics
post :publish
end
collection do
get :archived
post :bulk_publish
end
end
This generates:
GET /posts/:id/previewGET /posts/:id/analyticsPOST /posts/:id/publishGET /posts/archivedPOST /posts/bulk_publish
Mounting in Main App
# config/routes.rb
Rails.application.routes.draw do
# With authentication constraint
constraints Rodauth::Rails.authenticate(:user) do
mount DashboardPortal::Engine, at: "/dashboard"
end
# Or without
mount PublicPortal::Engine, at: "/public"
end
Controller Hierarchy
Resource Controllers
Portal resource controllers inherit from the feature package's controller:
::PostsController (feature package controller)
↓
DashboardPortal::PostsController (portal-specific)
Controllers are auto-created if not defined. When accessing DashboardPortal::PostsController:
- If file exists, use it
- Otherwise, dynamically create inheriting from
::PostsController - Include
DashboardPortal::Concerns::Controller
Non-Resource Controllers
For portal pages not tied to a resource (dashboard, settings, etc.), inherit from PlutoniumController:
# packages/dashboard_portal/app/controllers/dashboard_portal/dashboard_controller.rb
module DashboardPortal
class DashboardController < PlutoniumController
def index
# Dashboard home page
end
end
end
Portal-Specific Overrides
Override Definition
# packages/dashboard_portal/app/definitions/dashboard_portal/post_definition.rb
class DashboardPortal::PostDefinition < ::PostDefinition
# Hide certain actions from this portal
# Add portal-specific scopes
scope :my_posts, -> { where(user: current_user) }
end
Override Policy
# packages/dashboard_portal/app/policies/dashboard_portal/post_policy.rb
class DashboardPortal::PostPolicy < ::PostPolicy
include DashboardPortal::ResourcePolicy
def destroy?
false # No deletion in user portal
end
def permitted_attributes_for_create
%i[title content] # Fewer fields than admin
end
end
Override Controller
# packages/dashboard_portal/app/controllers/dashboard_portal/posts_controller.rb
class DashboardPortal::PostsController < ::PostsController
include DashboardPortal::Concerns::Controller
private
def preferred_action_after_submit
"index"
end
end
Layout and Views
Portal Layout
<!-- packages/dashboard_portal/app/views/layouts/dashboard_portal.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag "application" %>
</head>
<body>
<nav><!-- Portal navigation --></nav>
<main><%= yield %></main>
</body>
</html>
Dashboard Controller
# packages/dashboard_portal/app/controllers/dashboard_portal/dashboard_controller.rb
module DashboardPortal
class DashboardController < PlutoniumController
def index
# Dashboard home page
end
end
end
Multiple Portals Example
# Admin portal - full access
module AdminPortal
class Engine < Rails::Engine
include Plutonium::Portal::Engine
config.after_initialize do
scope_to_entity Organization, strategy: :path
end
end
end
# User dashboard - limited access
module DashboardPortal
class Engine < Rails::Engine
include Plutonium::Portal::Engine
config.after_initialize do
scope_to_entity Organization, strategy: :path
end
end
end
# Public portal - read-only, no auth
module PublicPortal
class Engine < Rails::Engine
include Plutonium::Portal::Engine
end
end
Each portal can:
- Have different authentication
- Show different fields
- Allow different actions
- Use different layouts
Related Skills
plutonium-package- Package overview (features vs portals)plutonium-rodauth- Authentication setup and configurationplutonium-connect-resource- Connecting resources to portalsplutonium-policy- Portal-specific policiesplutonium-definition- Portal-specific definitionsplutonium-controller- Portal-specific controllers
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon
