fntz / spray-routing-ext

rails like routing for spray.io. Project no longer supported

GitHub

project no longer supported. Use finagle/akka-http/http4s

Extension for create a Rails like routes for http://spray.io/

Api doc: doc

Example: blog

Library add a resource, match0, get0, post0, delete0, put0, root route directives.

Install

In Build.scala

"com.github.fntzr"  %% "spray-routing-ext" % "0.2.3"  // For scala 2.10
"com.github.fntzr"  %% "spray-routing-ext" % "0.3.3"  // For scala 2.11

Also you need add into you Build.scala file akka, spray-can, and spray-routing or spray-routing-shapeless2 for scala2.11. Also version 0.3.3 require shapeless 2.1.0 in scope.

Methods

Include and use

import com.github.fntzr.spray.routing.ext._

trait ApplicationRouteService extends Routable {
  def route = ....
}

Simple http methods

In package defined a get0, post0, delete0, and put0 methods for handle request by Get, Post, Delete or Put http method.

  //routes
  get0[Controller]("path") ~                                          // GET http://example.com/path 
  get0[Controller]("path-name" ~> "controllerAction") ~               // GET http://example.com/path-name
  delete0[Controller](("delete-path" / IntNumber) ~> "anotherAction") // DELETE http://example.com/delete-path/1
  
  //in controller:
  
  trait Controller {
    def path = { }
    
    def controllerAction = { ... }
                       ^^^^
                      without arguments
    
    def anotherAction(num: Int) = { ... }
                     ^^^^^^^^^^
                       IntNumber        
  }     

Helper methods for routing

This is a match0, root and scope methods.

  • math0 - used for define routes for few methods together
  • root - just alias for `get0[Controller]("/" ~> "action")
  • scope - define route path with nested block, all url paths will be start with path
  match0[Controller]("path-name") ~                                   // GET example.com/path-name
  match0[Controller]("another-path" ~> "action", List(GET, DELETE) ~  // GET | DELETE example.com/another-path
  root[Controller]("action")                                          // GET example.com/
  scope("scope") {
    get0[Controller]("get")                                           // GET example.com/get
  }

Resource

In Rails framework resource it's a set routes (index, show, edit, create, update, delete, new) which operate a Model. All urls will be started with /model/. Example:

case class Model(title: String, description: String)

trait Controller {
  def index =  ...
  def show(title: String) = ...
  def edit(title: String) = ...
  def create(model: Model) = ...
  def update(title: String) = ...
  def delete(title: String) = ...
  def fresh = ... // it's a `new` path
}

//route
  
  resource[Controller, Model](Segment)
                             ^^^^^^^^^
                          define subroute, by default subroute is a IntNumber     
  

This code generate urls:

GET    example.com/model/index  
GET    example.com/model/post-title
GET    example.com/model/post-title/edit
POST   example.com/model/
PUT    example.com/model/post-title
DELETE example.com/model/post-title
GET    example.com/model/new 

In resource you might exclude unnecessary methods, or define nested block, and you might define own separator

More resources

Note: For Controller define method fresh instead of new, because in scala, new reserved keyworld.

Controllers

Controllers it's only scala traits. When spray-routing-ext generate code inherited from you Controller, and you might use in a controller predefined value - request: spray.http.HttpRequest, but you might explicitly extends BaseController, which contain one abstract method.

trait MyController extends BaseController {
  def myAction = {
    val headers = request.headers
    ...
  }
}

respondTo helper

Sometimes you need according to Accept header send request with right Content-Type

  
  def method = {
    val accept = ... // extract from request Accept header 
    
    if accept is a html
      complete ...
    else if accept is a json and request with ajax 
      complete ...
    else
      reject
  }

Now possible reduce the code

trait MyController extends BaseController with RespondToSupport {
  def method = {
    respondTo {
      case `text/html`    =>    <html><body>....</body></html>
      case `application/json` if isAjax  => """ { json: "ok" } """ 
    }
  }
}

Now, when we request content with Accept: text/html will be got a <html><body>....</body></html> response, when we request content with Accept: application/json with ajax, will be got """ { json: "ok" } """, otherwise will be got 406 Not Acceptable Response Error.

Form support

spray-routing-ext adds a postForm method:

case class Model(id: Int, title: String)

postForm[Controller, Model]("action")
postForm[Controller, Model]("post-path" ~> "anotherAction", exclude("id")

// in controller

trait Controller {
  
  def action(model: Model) = ... 
            ^^^^^^^^^^^^^^
            got a Model instance
  
  def anotherAction(map: Map[String, Any]) = ....
                   ^^^^^^^^^^^^^^^^^^^^^^^^
                   got a Map, because we excluded `id` attribute 
}

DI

Often need a pass into controllers some values (bd connection, actor path, predefined values...)

It's a simple:

trait RouteService extends Routable {
  def route(db: ActorRef, render: Render) =  { // method take a `db` connection and `renderer`
    resource[PostController, Post]
  }
}

class MyService extends Actor with RouteService {
  val db = ...
  val render = ....
  def receive = runRoute(route(db, render))
                        ^^^^^^^^^^^^^^^^^^^^^
                          pass `db` connection and `renderer`
                               
}

trait Injection {
  val db: ActorRef
  val render: Render
}

//then into PostController 

trait PostController extends BaseController with RespondToSupport with Injection {
                                                                      ^^^^^^^^^^^^
   ....                                                                     
}

When application srart, in PostController possible use a db and render.

Usage

Define routes

import com.github.fntzr.spray-routing-ext._

trait MyRoutes extends Routable {
  val route = resource[PostController, Post] {
    pathPrefixt("foo") {
      get {
        complete{"foo"}
      }
    }
  } ~ get0[OtherController]("route") ~
  post0[Controller0](("foo" / IntNumber) ~> "action") ~
  match0[Controller0]("bar", List(GET, POST, PUT)) ~
  resource[ModelController, Model] ~
  root[PostController]("index")
}

Define controllers

trait PostController extends BaseController {
  def index = {
    complete("index")
  }
  def show(id: Int) = {
    complete(s"show:$id")
  }
  //others...
}

More info

My post about: spray-routing-ext: Extend spray-routing

License

Copyright (c) 2014-2015 fntzr mike.fch1@gmail.com

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.