Profile picture Schedule a Meeting
c a n d l a n d . n e t

GoBuffalo Nested Resources

Dusty Candland | | gobuffalo, buffalo, go, golang

Buffalo is a web framework written in Go by Mark Bates. I've been working more with Go and started using Buffalo. Mark has done an awesome job with it! Thanks Mark!

Pretty quickly into my app I wanted nested resources. Support for them seems pretty much there. It takes some work, but all in all it was pretty easy to get going. Here are some notes about how I approached it.

Getting started

I started following some of the videos on the Buffalo site. From there I generated some resources; User, Org, Index. I wanted Indexes to be nested under Orgs.

Routing

actions/app.go

indices := app.Resource("/orgs/{org_id}/indices", IndicesResource{&buffalo.BaseResource{}})
indices.Middleware.Use(LoadOrg)

There we grab a reference to the resource group, named indices and then add the LoadOrg middleware.

Middleware

Every action in the Indices resource should have the Org available. I was looking for some BeforeAction hook or something, but didn't find any. Enter middleware.

actions/orgs.go

func LoadOrg(next buffalo.Handler) buffalo.Handler {
	return func(c buffalo.Context) error {
		tx := c.Value("tx").(*pop.Connection)
		user := c.Value("current_user").(*models.User)
		o := &models.Org{}
		if err := tx.BelongsToThrough(user, "org_users").Find(o, c.Param("org_id")); err != nil {
			c.Error(404, err)
		}
		c.Set("org", o)
		return next(c)
	}
}

This does two things...

  1. Make sure the Org is available to the User.
  2. Set org in the Context

Actions

The actions need some updating, mostly updating them to scope the queries to the Org, make sure new Indexes get added to the Org, and changing some of the redirect URLs.

actions/indices.go

func (v IndicesResource) scope(c buffalo.Context) *pop.Query {
	tx := c.Value("tx").(*pop.Connection)
	org := c.Value("org").(*models.Org)
	return tx.BelongsTo(org)
}

func (v IndicesResource) newIndex(c buffalo.Context) *models.Index {
	orgID, err := uuid.FromString(c.Param("org_id"))
	if err != nil {
		c.Error(404, err)
	}
	return &models.Index{OrgID: orgID}
}

Those two helper functions help scope the queries and set the OrgID on new model instances.

I'm not showing all the changes needed, they'll be pretty obvious.

Views

I was kinda worried about this, but it turned out to already be supported. I'm not sure if it's part of Mux or Buffalo yet, but either way it works.

What works? You're wondering... links!

templates/indices/*.html

<!-- Generated Version -->
<li><a href="<%= newIndicesPath() %>" class="btn btn-primary">Create New Index</a></li>

<!-- Nested Version -->
<li><a href="<%= newOrgIndicesPath({org_id: org.ID}) %>" class="btn btn-primary">Create New Index</a></li>

Adding Org or org to the path function and passing in the org_id value was all that was needed. And the org is in the Context already thanks to out middleware above!

Would love to know if I missed something already built in to the generators or a better way to handle any of this, but pretty happy with the results.

Webmentions

These are webmentions via the IndieWeb and webmention.io. Mention this post from your site: