Loading...

March 7, 2017

Ratpacked: Implement Custom Rendering With Renderable Interface

Ratpack uses renderers to render output. We can create our own renderer by implementing the Renderer interface. The renderer class needs to implement a render method that has the object we want to render as argument. Alternatively we can add the logic to render a object to the class definition of that object. So instead of having a separate renderer class for a class, we add the render logic to the class itself. To achieve this we must implement the Renderable interface for our class. Ratpack provides a RenderableRenderer in the registry that knows how to render classes that implement the Renderable interface.

In the following example we have a Recipe class that implements the Renderable interface:

// File: src/main/java/mrhaki/ratpack/Recipe.java
package mrhaki.ratpack;

import ratpack.handling.Context;
import ratpack.render.Renderable;

import static ratpack.jackson.Jackson.json;

public class Recipe implements Renderable {
    
    private final String name;

    public Recipe(final String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    /**
     * Render object as JSON.
     * 
     * @param context Ratpack context.
     */
    @Override
    public void render(final Context context) throws Exception {
        context.byContent(content -> content
                .plainText(() -> context.render(this.toString()))
                .json(() -> context.render(json(this))));
    }
    
    public String toString() {
        return String.format("Recipe::name=%s", this.name);
    }
}

Let's write a specification to test how the Recipe is rendered:

// File: src/test/groovy/mrhaki/ratpack/RecipeRenderableSpec.groovy
package mrhaki.ratpack

import groovy.json.JsonSlurper
import ratpack.test.embed.EmbeddedApp
import spock.lang.Specification

class RecipeRenderableSpec extends Specification {
    
    def app = EmbeddedApp.fromHandler { ctx ->
        ctx.render(new Recipe('macaroni'))
    }
    
    def httpClient = app.httpClient
    
    void 'render Recipe as plain text'() {
        when:
        def response = httpClient.requestSpec { request -> 
            request.headers.set 'Accept', 'text/plain'
        }.get()
        
        then:
        response.statusCode == 200
        
        and:
        response.body.text == 'Recipe::name=macaroni'
    }

    void 'render Recipe as JSON'() {
        when:
        def response = httpClient.requestSpec { request ->
            request.headers.set 'Accept', 'application/json'
        }.get()

        then:
        response.statusCode == 200

        and:
        def recipe = new JsonSlurper().parseText(response.body.text)
        recipe.name == 'macaroni'
    }
}

Written with Ratpack 1.4.5.