Harbor Framework Getting started is easy!

Using Harbor::Router

Inevitably, this is one of the first classes you’ll need to work with when composing a Harbor application. You should find the router to have a fairly familiar interface, especially if you’ve looked at Sinatra. At its most trivial, it might look something like this:

Harbor::Router.new do
  get("/") { |request, response| response.puts "Hello World!" }
end

When a request comes in, Harbor will match it against your declared routes, and if a match is found the provided block will be called with an instance of Harbor::Request and Harbor::Response as the arguments. Notice that Harbor ignores the return value of router statements — only actions applied to the response object have an effect.

And, of course, the router supports other HTTP methods:

Harbor::Router.new do
  post("/") { |request, response| response.puts "Created" }
  put("/") { |request, response| response.puts "Updated" }
  delete("/") { |request, response| response.puts "Deleted" }
end

Alternative methods (such as PUT and DELETE) are handled either natively (from clients such as curl: curl -X PUT http://localhost:9292/) or by the POST parameter _method. Harbor’s simple form helper (Harbor::ViewContext::Helpers::Form) makes this easy:

<% form "/", :method => "put" do %>
  <input type="submit" value="Update">
<% end %>

If we wanted to show that form, then, we could do something like this:

Harbor::Router.new do
  get("/users/:id") do |request, response|
    response.render "users/form", :user => User.get(request["id"])
  end
end

Named parameters in the route declaration (:id, in this case) are stored in Request#params along with the other request parameters. As a shortcut, Request has hash-style accessors for getting to the params.

Controllers

Now that we’ve covered the basic interface for using Harbor’s router, we can address the next level of complexity: using controllers. If you’ve looked at Harbor’s API docs, though, you may have noticed that there is no Harbor::Controller. So when we talk about “controllers” we’re really just referring to ruby objects which implement “Controller” functionality. It’s through the Router and Harbor::Container that we can use plain ruby objects to handle responses.

services = Harbor::Container.new
Harbor::Router.new do
  using services, Users do
    get("/users/:id") do |users, request|
      users.show(request["id"])
    end
  end
end

The using method puts you into a special scope for declaring routes. Instead of getting a generic request and response as your block arguments, you’ll get a prepared instance of the class you passed in, and an optional second parameter which is the request. So what would our controller look like?

class Users
  attr_accessor :request, :response

  def show(id)
    response.render "users/form", :user => User.get(request["id"])
  end
end

If you’ve looked at Harbor::Container, you’ll know that it’s an accessor-based dependency injector, so for Users we’ve declared :request and :response as necessary services. What Harbor::Router::Using does is transform your route into something like this:

get("/users/id") do |request, response|
  controller = services.get("Users", :request => request, :response => response)
  yield(controller, response)
end

I think there’s just one more thing to explain, namely, how you’ll implement the Router in your application:

class MyApplication < Harbor::Application
  def self.routes(services)
    Harbor::Router.new do
      # ...
    end
  end
end

Otherwise, you can inject the router yourself:

router = Harbor::Router.new do
  # ..
end

run MyApplication.new(services, router, environment)