Extending router.cr for a Rails like DSL without the overhead.
Supports many of the helpers that Rails provides for controllers. i.e. before and after filters
require "action-controller"
# Abstract classes don't generate routes
abstract class Application < ActionController::Base
before_action :ensure_authenticated
rescue_from DivisionByZeroError do |error|
render :bad_request, text: error.message
end
private def ensure_authenticated
render :unauthorized unless cookies["user"]
end
end
# Full inheritance support (concrete classes generate routes)
class Books < Application
# this is automatically configured based on class name and namespace
# it can be overriden here
base "/books"
# route => "/books/"
def index
book = params["book"]
redirect_to Books.show(id: book) if book
render json: ["book1", "book2"]
end
# route => "/books/:id"
def show
# Using the Accepts header will select the appropriate response
# If the Accepts header isn't present it defaults to the first in the block
# None of the code is executed (string interpolation, xml builder etc)
# unless it is to be sent to the client
respond_with do
text "the ID was #{params["id"]}"
json({id: params["id"]})
xml do
XML.build(indent: " ") do |xml|
xml.element("id") { xml.text params["id"] }
end
end
end
end
# Websocket support
# route => "/books/realtime"
ws "/realtime", :realtime do |socket|
SOCKETS << socket
socket.on_message do |message|
SOCKETS.each { |socket| socket.send "Echo back from server: #{message}" }
end
socket.on_close do
SOCKETS.delete(socket)
end
end
SOCKETS = [] of HTTP::WebSocket
end
require "action-controller"
class MyResource < ActionController::Base
base "/resource"
before_action :check_id, only: show
def index
render text: "index"
end
def show
render json: {id: params["id"]}
end
put "/custom/route", :route_name do
render :accepted, text: "simple right?"
end
private def check_id
if params["id"] == "12"
redirect "/"
end
end
end
Results in the following high performance code being generated:
class MyResource < ActionController::Base
getter logger : Logger
getter render_called
getter action_name : Symbol
getter params : HTTP::Params
getter cookies : HTTP::Cookies
getter request : HTTP::Request
getter response : HTTP::Server::Response
def initialize(context : HTTP::Server::Context, params : Hash(String, String), @action_name)
@render_called = false
@request = context.request
@response = context.response
@cookies = @request.cookies
@params = @request.query_params
@logger = settings.logger
# Add route params to the HTTP params
# giving preference to route params
params.each do |key, value|
values = @params.fetch_all(key) || [] of String
values.unshift(value)
@params.set_all(key, values)
end
end
def index
@render_called = true
ctype = @response.headers["Content-Type"]?
@response.content_type = "text/plain" unless ctype
@response.print("index")
return
end
def show
@render_called = true
ctype = @response.headers["Content-Type"]?
@response.content_type = "application/json" unless ctype
output = {id: params["id"]}
if output.is_a?(String)
@response.print(output)
else
@response.print(output.to_json)
end
return
end
def route_name
@render_called = true
@response.status_code = 202
ctype = @response.headers["Content-Type"]?
@response.content_type = "text/plain" unless ctype
@response.print("simple right?")
return
end
private def check_id
if params["id"] == "12"
@response.status_code = 302
@response.headers["Location"] = "/"
@render_called = true
end
end
def self.draw_routes(router)
# Supports inheritance
super(router)
# Implement the router.cr compatible routes:
router.get "/resource/" do |context, params|
instance = MyResource.new(context, params)
instance.index
context
end
router.get "/resource/:id" do |context, params|
instance = MyResource.new(context, params)
instance.check_id unless instance.render_called
if !instance.render_called
instance.show
end
context
end
router.put "/resource/custom/route" do |context, params|
instance = MyResource.new(context, params)
instance.route_name
context
end
end
end