gobuffalo

We've already created a dynamic business-card website, so what can be added now? Actually, we've connected it to the database but haven't used the feature, that makes managing data really easy and fun. If you'd like to have a neat interface to view and edit stuff from the database, then you'll love Buffalo resources.

You might want to check out previous parts of this tutorial first:

What is a resource?

In the world of Buffalo, a resource definition would start with a model, as it is generally built around one. We've generated models before, so you probably already know that it has to have something more in it. Besides the database stuff (with Go structs and migrations) it has its own HTTP handlers that allow you to create, edit and delete items of that type. You can build one yourself, but this post describes how you can make the most of what Buffalo has to offer and generate it instead.

Generating one

In order to generate a resource we need to call a generate subcommand, then provide its name and the list of fields' definitions (as we did with models previously):

$ buffalo g resource skill name:text category:nulls.text level:text
Buffalo version development

--> actions/skills.go
--> actions/skills_test.go
--> locales/skills.en-us.yaml
--> templates/skills/_form.html
--> templates/skills/edit.html
--> templates/skills/index.html
--> templates/skills/new.html
--> templates/skills/show.html
--> buffalo db g model skill name:text category:nulls.text level:text
v3.30.1

--> models/skill.go
--> models/skill_test.go
...
> migrations/20170903173340_create_skills.up.fizz
> migrations/20170903173340_create_skills.down.fizz
...

As you can see, there is clearly more going on than before. On top of models files, we have actions and templates which are necessary to create an interface for managing skills data. That's not all because we need to run migrations to make changes in the database as well:

buffalo db migrate

Now it's time to see what exactly was generated. Let's start with actions because it's the core of everything, and take a look at the handlers:

type SkillsResource struct {
    buffalo.Resource
}
...
func (v SkillsResource) List(c buffalo.Context) error {
...
func (v SkillsResource) Show(c buffalo.Context) error {
...
func (v SkillsResource) New(c buffalo.Context) error {
...
func (v SkillsResource) Create(c buffalo.Context) error {
...
func (v SkillsResource) Edit(c buffalo.Context) error {
...
func (v SkillsResource) Update(c buffalo.Context) error {
...
func (v SkillsResource) Destroy(c buffalo.Context) error {
...

The file might feel a bit overwhelming (it has 178 lines in my case), but if you take a moment you won't have trouble understanding what happens here. It all starts with a struct implementing buffalo.Resource interface and all the functions below are defined there. They are responsible for handling requests to show all items (List), show one (Show), adding a new item (Create), editing (Update) and deleting them (Destroy). Also, it generates two endpoints that display pages with forms to create a new item (New) and edit existing (Edit). Before we display them, we need to do one small thing. Remember when in the first part of this tutorial we changed how our base template looks? We made changes to templates/application.html and stored the default one in templates/old_application.html to keep /routes endpoint pretty. We need to do that as well here, so we need to change all

c.Render(CODE, r.HTML(TEMPLATE_NAME))

to

c.Render(CODE, r.HTML(TEMPLATE_NAME, "old_application.html`))

Now, once we have the handlers ready, we should check out what the templates look like. First take a look on the list of all skills:

// templates/skills/index.html
<h1>Skills</h1>
<p>
<a href="<%= newSkillsPath() %>" class="btn btn-primary">Create New Skill</a>
</p>

<table class="table table-striped">
<thead>
<th>&nbsp;</th>
</thead>
<tbody>
    <%= for (skill) in skills { %>
    <tr>
    <td><%= skill.Name %></td>
    <td><%= skill.Category %></td>
    <td>
        <div class="pull-right">
            <a href="<%= skillPath({ skill_id: skill.ID }) %>" class="btn btn-info">View</a>
            <a href="<%= editSkillPath({ skill_id: skill.ID }) %>" class="btn btn-warning">Edit</a>
            <a href="<%= skillPath({ skill_id: skill.ID }) %>" data-method="DELETE" data-confirm="Are you sure?" class="btn btn-danger">Destroy</a>
        </div>
        </td>
    </tr>
    <% } %>
</tbody>
</table>

<div class="text-center">
<%= paginator(pagination) %>
</div>

It's very simple, yet powerful, as it displays a list of entries with some data (to be honest, I needed to add table cells with Name and Category) and buttons to create a view, edit or delete them. There is also, obviously, a button to create the new one. A template displaying the details of the item (show.html) is fairly simple, more interesting stuff happens in the remaining two:

// templates/skills/new.html
<h1>New Skill</h1>

<%= form_for(skill, {action: skillsPath(), method: "POST"}) { %>
    <%= partial("skills/form.html") %>
    <a href="<%= skillsPath() %>" class="btn btn-warning" data-confirm="Are you sure?">Cancel</a>
<% } %>


// templates/skills/edit.html
<h1>Edit Skill</h1>

<%= form_for(skill, {action: skillPath({ skill_id: skill.ID }), method: "PUT"}) { %>
    <%= partial("skills/form.html") %>
    <a href="<%= skillsPath({ skill_id: skill.ID }) %>" class="btn btn-warning" data-confirm="Are you sure?">Cancel</a>
<% } %>

Both views are very similar to each other (because they both display the same form), the difference is one of them displays an empty one, while the other fills it with some data on load. The main role is played by form_for helper function from gobufalo/tags library, which introduces additional helpers to display appropriate input fields for data types, handle validation, etc. Since it's the same form in both views, there is a partial template used:

// tempaltes/skills/_form.html
<%= f.TextArea("Name", {rows: 10}) %>
<%= f.TextArea("Category", {rows: 10}) %>
<%= f.TextArea("Level", {rows: 10}) %>
<button class="btn btn-success" role="submit">Save</button>

Yes, you guessed it, t.TextArea(..) renders an awesome <textarea> input. We know, however, that both Name and Category should contain rather short values, so we can replace these text areas with simple text inputs using f.InputTag(..) instead.

Ok, enough of all this theory, I want to see it in action. Let's start at /skills:

Nothing to see here, let's add something:

Can we save it? It feels like it:

But is it saved? It's on the list, so it must be:

One problem though, is my Go really Good? Nah, I'll delete it:

It's cool, let's see how the data looks in the database. First, I'll add some data and then query our Postgres:

# SELECT * FROM skills;
        created_at         |         updated_at         |                  id                  | name |       category        |  level   
----------------------------+----------------------------+--------------------------------------+------+-----------------------+----------
2017-09-03 20:35:44.899385 | 2017-09-03 20:35:44.899386 | 7cf7395c-5281-4eee-9582-b76b911f9c5d | Go   | Programming languages | Mediocre
(1 row)

Now, remember when I mentioned previously that id, created_at and updated_at are not that bad? Since Buffalo handles adding them, we can use them without having to think about it! And sooner or later they may be useful, especially a unique identifier of each record.

See them

We've seen the resources, made them work and verify that the end up in the database. Let's edit our /resume page and display skills as well. First, we'll group them by categories in the handler and pass a map (category -> list of skills) into the context:

func ResumeHandler(c buffalo.Context) error {
    tx := c.Value("tx").(*pop.Connection)
    ...
    skillset, err := buildSkillset(tx)
    if err != nil {
        return errors.Wrap(err, "failed to load skills date")
    }
    c.Set("skillset", skillset)

    return c.Render(200, r.HTML("resume.html"))
}
...
func buildSkillset(tx *pop.Connection) (map[string][]models.Skill, error) {
    var skills []models.Skill
    skillset := make(map[string][]models.Skill)
    if err := tx.All(&skills); err != nil {
        return nil, err
    }
    for _, s := range skills {
        category := s.Category.String
        if category == "" {
            category = "Other"
        }
        skillset[category] = append(skillset[category], s)
    }
    return skillset, nil
}

It's important to handle the case when skill.Category is empty (it can be), and in either case, use its string value in the map. Now comes an easy part of updating a template:

// templates/resume.html
<div class="skills">
    <h2>Skills</h3>
    <%= for (category, skills) in skillset { %>
        <div class="skills__category skills">
            <h4><%= category %></h4>
            <%= for (skill) in skills { %>
            <div class="skills__item skills-item">
                <div class="skills-item__name"><%= skill.Name %></div>
                <div class="skills-item__level"><%= skill.Level %></div>
            </div>
            <% } %>
        </div>
    <% } %>
</div>

It's finally time to see our resume shine with Mediocre Go:

The full source code of this example is available on Github.