The Controller
Scorched consists almost entirely of the Scorched::Controller
. The Controller is the class from which your application class inherits. All the code examples provided in the documentation are assumed to be wrapped within a controller class.
class MyApp < Scorched::Controller
# We are now within the controller class.
# Most examples are assumed to be within this context.
end
Your application's root controller (named MyApp
in the example above), should be configured as the run target in your rackup file:
# config.ru
require './myapp.rb'
run MyApp
Sub-Controllers
One of the core features of Scorched is the fact that Controller's are intended to be inheritable and nestable as sub-controllers. This is perhaps the main advantage of Scorched over the likes of Sinatra and Padrino, and is one of the key innovations of the framework.
For the most part, you will find yourself breaking up your application into many discrete controllers. This will allow you to scope your configuration, filters, error handlers, and conditions, to logical groups of routes. This can save a lot of code repetition, keeping your code DRY, hence the name Scorched.
A sub-controller is any controller that's mapped by another controller. ControllerB
in the following example, is one example of a sub-controller.
class ControllerA < Scorched::Controller
get '/' do
"Hello there"
end
after do
response.body[0] << '.' # Always forgetting to add my periods
end
end
class ControllerB < Scorched::Controller
get '/' do
"I'm apparently a sub-controller"
end
end
ControllerA << {pattern: '/sub', target: ControllerB}
The previous example is awfully crude, but it hopefully demonstrates that there's absolutely no magic happening here. In that previous example, a request for /sub
will pass through ControllerA
and onto ControllerB
, before heading back out the way it came. Hence, a request for /sub
will yield I'm apparently a sub-controller.
; note the period added by ControllerA's after filter (more on after filters later).
While in the previous example, ControllerB
counts as a sub-controller in that it's nested within ControllerA, at least from a routing perspective, ControllerA
and ControllerB
are completely unrelated; they share nothing.
A lot can be gained by not only nesting ControllerB
within ControllerA
, but by also inheriting from it.
class ControllerA < Scorched::Controller
render_defaults[:dir] = 'views'
render_defaults[:layout] = :main
conditions[:user] = proc { |usernames|
[*usernames].include? session['username']
}
get '/' do
bold "Hello there"
end
def bold(str)
"<strong>#{str}</strong>"
end
end
class ControllerB < ControllerA
render_defaults[:layout] = :controller_b
get '/', user: 'bob' do
bold "I'm apparently a sub-controller"
end
end
ControllerA << {pattern: '/sub', target: ControllerB}
Now that ControllerB
is inheriting from ControllerA
, it not only gets access to all its methods, such as the very helpful bold
, but it also inherits anything configured on ControllerA
, such as rendering defaults, filters, middleware, conditions, etc. Very handy.
You can use nesting and inheritance exclusively, or together, depending on your needs.
Controller Helper
There is a more succinct way of mapping and defining sub-controllers, and that's with the controller
helper. Here's the previous example cut-down and re-written using the controller
helper.
class MyApp < Scorched::Controller
get '/' do
bold "Hello there"
end
controller '/sub' do
render_defaults[:layout] = :controller_b
get '/', user: ['bob', 'jeff'] do
bold "I'm apparently a sub-controller"
end
end
def bold(str)
"<strong>#{str}</strong>"
end
end
The controller
helper takes an optional URL pattern as it's first argument, an optional parent class as its second, and finally a mapping hash as its third optional argument, where you can define a priority, conditions, or override the URL pattern. Of course, the controller
helper takes a block as well, which defines the body of the new controller class.
The optional URL pattern defaults to '/'
which means it's essentially a match-all mapping. In addition, the generated controller has :auto_pass
set to true
by default (refer to configuration documentation for more information). This is a handy combination for grouping a set of routes in their own scope, with their own methods, filters, configuration, etc.
class MyApp < Scorched::Controller
get '/' do
format "Hello there"
end
controller do
before { response['Content-Type'] = 'text/plain' }
get '/hello' do
'Hello'
end
def emphasise(str)
"**#{str}**"
end
end
get '/goodbye' do
'Goodbye'
end
after { response.body = emphasise(response.body.join) }
def emphasise(str)
"<strong>#{str}</strong>"
end
end
That example, while serving no practical purpose, hopefully demonstrates how you can combine various constructs with sub-controllers, to come up with DRY creative solutions.
Finally, the controller
helper can also be used a shortcut to map one controller to another, where the given class is mapped directly, instead of being the parent of a new controller class. These two examples are essentially equivalent:
ControllerA << {pattern: '/sub', target: ControllerB}
# Or
class ControllerA
controller '/', ControllerB # Note that no block was given
end
The Root Controller
Although you will likely have a main controller to serve as the target for Rack, Scorched does not have the concept of a root controller. It makes no differentiation between a sub-controller and any other controller. All Controllers are made equal.
You can arrange and nest your controllers in any way, shape or form. Scorched has been designed to not make any assumptions about how you structure your controllers, which again, can accommodate creative solutions.