class ShowInvoiceController < Scorched::Controller
get do
# …
end
end
class Routes < Scorched::Controller
map pattern: '/invoices/:id', method: ['GET'], target: ShowInvoiceController
map pattern: '/invoices/:id', method: ['PUT'], target: UpdateInvoiceController
map pattern: '/invoices/:id', method: ['DELETE'], target: DestroyInvoiceController
# ...
end
class DeepExample < Scorched::Controller controller '/:type' do controller '/:id' do get '/*' do request.breadcrumb.each { |v| p v } 'Cool' end end endend
run DeepExample
class Invoice def initialize(id) @id = id end def get "Invoice ##{@id}" end def save(data) "Saved invoice ##{@id}" end def delete "Deleted invoice ##{@id}" endend
class App < Scorched::Controller get('/invoices/*') { |id| Invoice.new(id).get } route('/invoices/*') { |id| Invoice.new(id).save(request.POST) } route('/invoices/*') { |id| Invoice.new(id).delete }end
run App
class RouteMapper < Scorched::ImaginaryMapper
post '/invoices', InvoiceController, :create
get '/invoices/:id', InvoiceController, :show
get '/invoices/:id/edit', InvoiceController, :edit
delete '/invoices/:id', InvoiceController, :delete
post '/invoices/:id/line', InvoiceController, :create
get '/invoices/:id/lines', InvoiceController, :show
# … 200+ contrived routes
end
class InvoiceController < Scorched::ImaginaryController
def create
# request, views, params, available here
end
def show
# code..
end
def edit
# code..
end
def delete
# code..
end
end
require File.expand_path('../../lib/scorched.rb', __FILE__)
class Invoices < Scorched::Controller get '/:id' do |k,v| "Get invoice " end put '/*' do |id| "Save invoice ##{id}" endend
class Products < Scorched::Controller # ...end
class Catalog < Scorched::Controller map pattern: '/products', target: Products # ...end
class Admin < Scorched::Controller # ...
end
class App < Scorched::Controller
map pattern: '/invoices', target: Invoices map pattern: '/catalog', target: Catalog map pattern: '/admin', target: Adminend
run App
module Admin
class BaseController < ImaginaryController
before do
# authorize administrator
end
end
end
module Admin
class DogsController < BaseController
after do
# add 'bark' to body response
end
def index
# list all dogs code
end
end
end
module Admin
class CatsController < BaseController
def index
# list all cats code
end
end
end
class GoatsController < ImaginaryController
def index
# list all goats code
end
end
class RequestRouter < Imagery::Router
get '/admin/animals/dogs', Admin::DogController, :index
get '/cats', Admin::CatsController, :index
get '/admin/animals/goats', GoatsController, :index
end
run RequestRouter
get '/myresource/blah/:id' do
# my wunderbar control block
end
class Cont1 < Scorched::Controller
map pattern: '/segmented', target: Cont2
end
class Cont2 < Scorched::Controller
map pattern: '/routes', target: Cont3
end
class Cont3 < Scorched::Controller
map pattern: '/arenot', target: Cont4
end
class Cont4 < Scorched::Controller
get '/maintainable' do
# faint.
end
end
class Cont1 < Imaginary::Controller
def index
end
end
class Routes < Proper::Router
get '/whole/routes/are/very/maintainable', Cont1, :index
end
class RequestRouter < Imagery::Router
get '/im/happy/to/change/in', Admin::DogController, :index
get '/the/future/without/affecting', Admin::CatsController, :index
get '/the/rest/of/the/application', GoatsController, :index
end
class RouteMapper < Scorched::ImaginaryMapper
post '/invoices', InvoiceController, :create
get '/invoices/:id', InvoiceController, :show
get '/invoices/:id/edit', InvoiceController, :edit
delete '/invoices/:id', InvoiceController, :delete
post '/invoices/:id/lines', InvoiceLineController, :create
get '/invoices/:id/lines', InvoiceLineController, :index
# … 200+ contrived routes
end
class InvoiceController < Scorched::ImaginaryController
def create
# request, views, params, available here
end
def show
# code..
end
def edit
# code..
end
def delete
# code..
end
end
class InvoiceLineController < Scorched::ImaginaryController
def index
# code..
end
def create
# request, views, params, available here
end
end
You also miss out on some benefits of Scorched which can keep your application more implicit and DRY, which would be easier to maintain. For example, if you have a deep URL such as in the example you gave with Cont1, Cont2, Cont3, etc, if the request passes through all of these controllers, each controller has an opportunity to have it's own conditions, filters, and other behaviours. You miss out on some of this if you go directly to the end controller.
class BaseController < ImaginaryController
before do
authenticate_session
end
def authenticate_session
# authenticate session code
end
end
class AdminController < BaseController
before do
authorize_admin
end
def authorize_admin
# authorize admin code
end
end
class CatsController < AdminController
def index
# code...
end
def create
# code...
end
end
class Router < Framework::Router
get '/blah/cats', CatsController, :index
get '/another/route/for/cats', CatsController, :index
end
Controller/Class inheritance and nesting are two separate concepts which are at their most powerful when used together. With your centralised routes, you get inheritance, but you miss out on the potential benefits gained by nesting.
With your example #1 vs. example #2 in your previous post, I find example #1 more desirable. It's more implicit, which is the same reason most people prefer "convention over configuration".
`Cont4` in example #1, is pretty much exactly the same as `Cont1` in example #2, except instead of just defining methods as the endpoints, you're defining the route endpoints at the same time, killing two birds with one stone.
In the end though, I do suppose it comes down to personal preference. There's not a huge difference between the two approaches, but I do personally prefer example #1 using nested controllers.
class RootController < Scorched::Controllerend
AdminController = RootController.controller '/admin', conditions: {media_type: 'text/html'} doend
CatsController = AdminController.controller '/cats' do get '/' do '<strong>Meow!</strong>' endend
class Root < Scorched::Controller
endpoint '/some/quite/deep/path/:type/:id', conditions: {method: 'GET', media_type: 'text/html', some_other_conditions: true}, MyController, :view
endpoint '/some/quite/deep/path/:type/:id/edit', conditions: {method: 'GET', media_type: 'text/html', some_other_conditions: true}, MyController, :edit
endpoint '/some/quite/deep/path/:type/:id/edit', conditions: {method: 'POST', media_type: 'text/html', some_other_conditions: true}, MyController, :save
end
class Root < Scorched::Controller
controller '/some/quite/deep/path', conditions: {media_type: 'text/html', some_other_conditions: true} do
endpoint '/:type/:id', conditions: {method: 'GET'}, MyController, :view
endpoint '/:type/:id/edit', conditions: {method: 'GET'}, MyController, :edit
endpoint '/:type/:id/edit', conditions: {method: 'POST'}, MyController, :save
end
end
endpoint '/:type/:id', conditions: {method: 'GET'}, MyController, :view
get '/:type/:id', MyController, :view
Do you think you would use anonymous nested controllers in your route mapping controller?
class Root < Scorched::ImaginaryRouter
scope '/some/quite/deep/path', conditions: {media_type: 'text/html', some_other_conditions: true} do
get '/:type/:id', MyController, :view
get '/:type/:id/edit', MyController, :edit
post '/:type/:id/edit', MyController, :save
end
end
In this case, when a JSON request comes in, it's going to fail that condition, and move onto the next matching route. If there isn't another matching route, an appropriate 406 Not Acceptable status is returned.
class Root < Scorched::ImaginaryRouter
scope '/admin', conditions: {media_type: 'text/html', some_other_conditions: true} do
get '/:type/:id', MyController, :view
get '/:type/:id/edit', MyController, :
edit
end
end
Another possibility is that the AdminController may define some funky compatibility mode for XML to JSON (for lack of a better example). It may define a `before` filter that converts XML to JSON (or vice versa), modifying the request headers appropriately, and then converting the format back to what the client expects in an `after` filter. This compatibility layer which modifies the request headers may alter the routing of the request as it descends through the chain of nested controllers.
class FunkyCompatibilityXmlMode
def initialize app
@app = app
end
def call env
# modify request here...
app.call(env)
#modify response here...
end
end
class RootController < Scorched::Controller
end
class AdminController < RootController
use FunkyCompatibilityXmlMode
end
class CatsController < AdminController
def show
end
end
route('/*') { }
# Or if you want even more control
map(pattern: '/*$', target: proc { })
class RootController < EIF::Controller
def index
'Hello'
end
def create
'Creating it now'
end
end
class CustomerController < EIF::Controller
def index
'Hello customer'
end
end
class OrderController < EIF::Controller
def index
'Me order'
end
end
Class Router < EIF::Router
get '/', 'root#index'
post '/', 'root#create'
get '/customer', 'customer#index'
get '/order', 'order#index'
end
app = EIF::App.new
app.register_router(Router)
app.register_controller(RootController)
app.register_controller(CustomerController)
app.register_controller(OrderController)
run app
def self.inherited(klass)
klass.get('/') { invoke_action :index }
klass.get('/new') { invoke_action :new }
klass.post('/') { invoke_action :create }
klass.get('/:id') { invoke_action :show }
klass.get('/:id/edit') { invoke_action :edit }
klass.route('/:id', method: ['PATCH', 'PUT']) { invoke_action :update }
klass.delete('/:id') { invoke_action :delete }
end
App.controller '/customer', Customer
App.controller '/order', Order
App.controller '/', Root
get '/order', 'order#index'
app = EIF::App.new
app.register_router(Router)
app.register_controller(RootController)
app.register_controller(CustomerController)
app.register_controller(OrderController)
With that said, my rails-style routing example shows that it's possible to implement your own predefined routes in a base class to reduce repetition
klass.get('/') { invoke_action :index }
klass.get('/new') { invoke_action :new }
klass.post('/') { invoke_action :create }
klass.get('/:id') { invoke_action :show }
klass.get('/:id/edit') { invoke_action :edit }
klass.route('/:id', method: ['PATCH', 'PUT']) { invoke_action :update }
klass.delete('/:id') { invoke_action :delete }
get '/order', 'order#index'