Actions

An Action in Play defines how a server should respond to a request. The methods, which define an Action, are mapped to a request in the routes file. For example, let's define an Action which displays the information of all the artists as a response:

def listArtist = Action {
  Ok(views.html.home(Artist.fetch))
}

Now, to use this Action, we should map it to a request in the routes file.

GET     /api/artist       controllers.Application.listArtist

In this example, we fetch all the artists and send them with the view, as the response to the request.

Note

The term api used in the route file is just a URL prefix and is not mandatory.

Run the application and access http://localhost:9000/api/artist from the browser. A table with the available artist is visible.

Action takes a request and yields a result. It is an implementation of the EssentialAction trait. It is defined as:

trait EssentialAction extends (RequestHeader => Iteratee[Array[Byte], Result]) with Handler {
 
  def apply() = this

}

object EssentialAction {
  def apply(f: RequestHeader => Iteratee[Array[Byte], Result]): EssentialAction = new EssentialAction {
    def apply(rh: RequestHeader) = f(rh)
  }
}

Iteratee is a concept borrowed from functional languages. It is used to process chunks of data in an incremental manner. We will dig deeper into it in Chapter 6, Reactive Data Streams.

The apply method accepts a function, which transforms a request into a result. The RequestHeader and other chunks of data represent the request. In short, the apply method takes in a request and returns a result.

Let's see some of the ways in which an action can be defined.

Actions with parameters

We might come across a situation where we need to define an Action, which takes a value from the request path. In this case, we will need to add the parameters required for the method signature and pass them in the routes file. An example of this would be the method to fetch artists by their selected names. In the controller, add the following:

  def fetchArtistByName(name:String) = Action { 
     Ok(views.html.home(Artist.fetchByName(name))) 
  } 

The mapping for this in the routes file will be:

GET    /api/artist/:name       controllers.Application.fetchArtistByName(name)

Note

If it's not specified explicitly, keep in mind that the type of parameter in the path is set to String by default. The type can be specified in the method call. So, the route defined is equivalent to:

GET    /api/artist/:name        controllers.Application.fetchArtistByName(name:String)

Similarly, we could add more parameters if required.

Now, take the use case of a search query. We want the action to accept query parameters, such as name and country. The action is defined as:

def search(name: String, country: String) = Action { 
    val result = Artist.fetchByNameOrCountry(name, country) 
    if(result.isEmpty){
      NoContent
       } 
    else {
      Ok(views.html.home(result))
    }
  }

If there are no artists matching the criteria, the response is empty, and shows a status code 204 (no content). If it doesn't, the response status is 200 = (Ok), and shows the result as the response body.

The entry corresponding to this Action in the routes file will be the following:

GET    /api/search/artist        controllers.Application.search(name:String,country:String)     

We do not use any parameters in the path, but query parameters whose labels correspond to the method's parameter names in the routes file should be included.

This will result in a valid URL: http://localhost:9000/api/search/artist?name=Franz&country=Austria

What if we decided to make country an optional parameter?

Let's modify the route to accommodate this change:

GET    /api/search/artist    controllers.Application.search(name:String?="",country:String?="") 

This allows us to make queries by the name as well, so, both the URLs will now look like this: http://localhost:9000/api/search/artist?name=Franz and http://localhost:9000/api/search/artist?name=Franz&country=Austria are now supported.

Here, we made the country parameter optional by setting a default value for it in the route definition. Alternatively, we could define an Action to accept a parameter of the Option type:

def search2(name: Option[String], country: String) = Action { 
    val result = name match{ 
      case Some(n) => Artist.fetchByNameOrCountry(n, country) 
      case None => Artist.fetchByCountry(country) 
    } 
    if(result.isEmpty){
      NoContent
    } 
    else { 
      Ok(views.html.home(result)) 
    } 
  }

Then, the route will be as follows:

GET    /api/search2/artist    controllers.Application.search2(name:Option[String],country:String)

We can now make requests with or without passing the name of the country:

http://localhost:9000/api/search2/artist?country=Austria

http://localhost:9000/api/search2/artist?name=Franz&country=Austria

In the examples shown in this section, we didn't need to use the request to generate our result but in some cases, we would use the request to generate a relevant result. However, to do this, understanding the format of the request content is crucial. We'll see how this is done in the following section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.35.247