Worked example
This wires a small blog into a grouped, filtered, authorized admin: authors and their posts, with scopes, a custom action, a role policy, and a dashboard panel.
The models#
Two ORM models, an author with many posts:
class Author is Model {
has-many 'posts';
}
class Post is Model {
belongs-to 'author';
self.validate: 'title', { :presence };
}
Registering the resources#
Both resources go under a Content menu group. Posts get columns, a filter, a
scope, a form, and a custom member action; authors get a nested form for their
posts.
use MVC::Keayl::Admin;
MVC::Keayl::Admin.configure(site-title => 'Blog Admin', mount-path => '/admin');
MVC::Keayl::Admin.register(Post, {
menu(group => 'Content', icon => 'file-text', priority => 1);
column('title', :sortable);
column('author', format => 'link-to-show');
column('published', :sortable);
filter('title', :as<string>);
filter('published', :as<boolean>);
scope('drafts', -> $relation { $relation.where({ published => False }) });
scope('published', -> $relation { $relation.where({ published => True }) }, :default);
field('title');
field('body', :as<text>);
field('published', :as<boolean>);
field('author-id', :as<select>, collection => { Author.all.all.map({ .id => .read-attribute('name') }) });
permit('title', 'body', 'published', 'author-id');
member-action('publish', -> $controller, $record {
$record.update({ published => True });
$controller.redirect-to($controller.path-for('post', $record.id));
}, :confirm('Publish this post?'));
});
MVC::Keayl::Admin.register(Author, {
menu(group => 'Content', icon => 'people', priority => 2);
column('name', :sortable);
field('name');
nested('posts', :multiple, {
field('title');
field('published', :as<boolean>);
});
permit('name');
});
Authorizing access#
A role policy: editors manage posts but only view authors; admins do everything.
use MVC::Keayl::Admin::Authorization::Role;
MVC::Keayl::Admin.authorize-with(
MVC::Keayl::Admin::Authorization::Role.new(
permissions => {
editor => <index show create update publish>,
admin => <*>,
},
)
);
Forbidden actions return a 403, and the controls for them (the new button, row edit and delete, the publish action, batch destroy) are hidden from a role that lacks them.
A dashboard panel and ordering#
Order the menu groups and add a dashboard panel listing recent posts:
MVC::Keayl::Admin.menu-group-order('Content');
MVC::Keayl::Admin.dashboard-block(title => 'Recent posts', {
'<ul class="list-unstyled mb-0">'
~ Post.all.order('created_at DESC').limit(5).all.map({ '<li>' ~ .read-attribute('title') ~ '</li>' }).join
~ '</ul>'
});
The result#
- A
Contentgroup in the sidebar with Posts and Authors, Posts first. - A sortable, filterable post index with
draftsandpublishedscope tabs, defaulting to published, plus CSV and JSON export of the current view. - A post form with a body text area and an author select, and an author form that edits its posts inline.
- A
Publishaction on the post show page, gated by the policy. - A dashboard listing the resources and a recent-posts panel.