One of the strengths of Ratpack is the asynchronous execution model. An important class is the Promise
class. An instance of the class will represent a value that is available later. We can invoke several operations that need be applied to a value when a Promise
is activated. Usually the activation happens when we subscribe to a Promise
using the then
method. We can use the route
method for a Promise
to have a different action when a certain predicate is true. The action will stop the flow of operations, so methods that are executed after the route
method are not executed anymore if the predicate is true. If the predicate is false then those methods are invoked.
The Promise
class has a method onNull
as a shorthand for the route
method where the predicate checks if the value is null
. For example we could have a service in our application that returns a Promise<User>
. If the value is null
we want some special behaviour like sending a 404
status code to the client. With the following code we could achieve this:
import ratpack.jackson.Jackson import static ratpack.groovy.Groovy.ratpack ratpack { bindings { bind(UserService, DefaultUserService) } handlers { UserService userService -> get('user/:username') { final String username = pathTokens.username userService .findUser(username) // If user is not found, then value // of Promise object is null. .onNull { clientError(404) } .map(Jackson.&json) .then { JsonRender userAsJson -> render(userAsJson) } } } }
In the following Spock specification we can see both the onNull
and route
methods and how they work:
package com.mrhaki.ratpack.promise import ratpack.exec.Promise import ratpack.func.Predicate import ratpack.test.exec.ExecHarness import spock.lang.AutoCleanup import spock.lang.Specification class PromiseValueRoutingSpec extends Specification { @AutoCleanup ExecHarness execHarness = ExecHarness.harness() def "when Promise value is null then special action is used"() { given: String execResult when: execHarness.run({ Promise.value(promiseValue) // If Promise has null value then // execute the action, otherwise // proceed. .onNull { execResult = 'null value' } // Promise value was not null, so // we get to this action block .then { String value -> execResult = value } }) then: execResult == result where: promiseValue || result 'Ratpack rules' || 'Ratpack rules' '' || '' null || 'null value' } def "when a Promise value meets a predicate a custom action is used"() { given: String execResult and: // Create Ratpack predicate for checking if Optional object // is empty. final Predicate<Optional<String>> emptyOptional = { Optional<String> optional -> // Check if value is present !optional.present } when: execHarness.run({ Promise.value(promiseValue) // If Promise has null value then // execute the action, otherwise // proceed. .onNull { execResult = 'null value' } // Custom check for the Promise value // which executes the given action when // the predicate is true. .route(emptyOptional) { execResult = 'empty optional value' } // Promise value was not null and // optional value was present, so // we get to this action block .then { Optional<String> optional -> execResult = optional.get() } }) then: execResult == result where: promiseValue || result Optional.empty() || 'empty optional value' Optional.of('Ratpack rules') || 'Ratpack rules' null || 'null value' } }
Written with Ratpack 1.1.1