How Mix in Routes in Sinatra for a Better Structure

How mix in routes in Sinatra for a better structure

You don't do include with Sinatra. You use extensions together with register.

I.e. build your module in a separate file:

require 'sinatra/base'

module Sinatra
module OtherRoutes
def self.registered(app)
app.get "/route1" do
...
end
end
end
register OtherRoutes # for non modular apps, just include this file and it will register
end

And then register:

class Server < Sinatra::Base
register Sinatra::OtherRoutes
...
end

It's not really clear from the docs that this is the way to go for non-basic Sinatra apps. Hope it helps others.

Sinatra: Using routes from other files

You can use included hook to implement what you want.

require 'sinatra'
require 'lib/web'

module NoCI
class Main < Sinatra::Base
include NoCI::Web
end
end

lib/web.rb

module NoCI
module Web
def self.included(base)
base.get '/' do
'Hello World!'
end
end
end
end

The code inside included will execute when you include this module, with the NoCI::Base as base.

Writing a Sinatra Extension using options in routes

You're asking for dynamic routes, but Sinatra compiles the route information so it won't work the way you're looking for.

As a work around, you might consider defining a catch-all route, and checking the route information inside the catch-all, e.g.

get %r{^/(*)/bar$} do |capture|
if settings.url_prefix == capture # or perhaps check against request.path_info
# get file
else
status 404
end
end

Obviously, there are still many things to be done there, but you get the drift.

How do I config.ru properly in modular Sinatra application.?

With URLMap you specify a base url where the app should be mounted. The path specified in the map is not used when determining which route to use within the app itself. In other words the app acts as if it's root is after the path used in URLMap.

For example, your code will respond to the following paths:

  • /: will be routed to the / route in MyApp

  • /another: will go to the / route in AnotherRoute. Since AnotherRoute extends MyApp this will be the same as / in MyApp (but in a different instance).

    URLMap sees /another and uses it to map to AnotherRoute, stripping this part of the request from the path. AnotherRoute then only sees /.

  • /another/another: will be routed to the two /another routes in AnotherRoute. Again, the first another is used by the URLMap to route the request to AnotherRoute. AnotherRoute then only sees the second another as the path.

    Note that this path will respond to both GET and POST requests, each being handled by the appropriate block.

It's not clear what you're trying to do, but I think you can achieve what you want by running an instance of AnotherRoute, with a config.ru that is just:

run AnotherRoute.new

Since AnotherRoute extends MyApp, the / route will be defined for it.

If you're looking for a way to add routes to an existing Sinatra application, you could create a module with an included method that adds the routes, rather than use inheritance.

Architecture for a modular, component-based Sinatra Application

This is similar to include's proposal, but it doesn't require access to the rackup file.

Write your various Handlers like:

class FoodHandler < Sinatra::Base
get '/chunky/:food' do
"Chunky #{params[:food]}!"
end
end

Then in your main application file:

require './lib/handlers/food_handler.rb'

class Main < Sinatra::Base
enable :sessions
... bla bla bla
use FoodHandler
end

I've used this kind of structure to build some fairly complex Sinatra apps. It scales just as well as Rails.

EDIT

To have your config file define the routes, you could do something like this:

class PlacesHandler < Sinatra::Base
# Given your example, this would define 'places/paris' and 'places/losangeles'
CONFIG['components'].select { |c| c['compontent_type'] == 'Mapper' }.each do |c|
get c['route'] do
@latitude = c['component_settings']['latitude']
@longitude = c['component_settings']['longitude']
end
end
end

View routes in Sinatra

I'm not sure if this is a security concern here (I'm not that into all the details of Sinatra) but I tend to be paranoid when using user specified data like for example params['page'] in your example. As said I'm not sure if Sinatra sanitizes the content and would make this example impossible, but imagine it said something like ../db_connection.yml. So Sinatra would be told to load the haml file pages/../db_connection.yml which might actually exist and display it to the user showing them your database configuration.

If you don't have any weird symlinks in your pages directory it would probably be enough to replace all double occurences of a dot with something like .gsub(/\.+/, ".") of the passed string (or replace all dots if you don't need them in the name to be even more paranoid). I'm not sure if there are any multi-byte insecurities where someone could do something ugly with encodings though and it might be useless to do the replacing at all because the exploit would work nevertheless.

Edit: Short reading into the Sinatra manual yielded that

By the way, unless you disable the path traversal attack protection (see below), the request path might be modified before matching against your routes.

So it seams that it should be secure to just use the params value without any special filtering, you may however like to look into the documentation some more (especially the security sections). However I don't think it's too much of a security issue if it's possible to figure out if a file in your pages directory exists or not.

How to render archive-like structure in sinatra of old wordpress posts?

You have a few options for getting index.html to work in your public folders. The fastest option will be to have your load balancing server (e.g. Nginx or Apache) serve the static files. Nginx and Apache basically do this out of the box.

Another way is to manually read and send the files from a Sinatra route. You’ll get more control this way, but lose a little speed and use more memory. You might do that like this:

get "/:year/:month/:day/:title" do |*path|
File.read(File.join(settings.public, path, "index.html"))
end

You can get a list of your posts by globbing with the Dir class:

get "/archive" do
@posts = Dir["#{settings.public}/**/index.html"].map do |post|
File.dirname(post).sub(settings.public, "")
end

haml :archive
end

How does Sinatra define and invoke the get method?

It is spread out in a few places, but if you look in lib/sinatra/main.rb, you can see this line at the bottom:
include Sinatra::Delegator

If we go into lib/sinatra/base.rb we see this chunk of code around like 1470.

  # Sinatra delegation mixin. Mixing this module into an object causes all
# methods to be delegated to the Sinatra::Application class. Used primarily
# at the top-level.
module Delegator #:nodoc:
def self.delegate(*methods)
methods.each do |method_name|
define_method(method_name) do |*args, &block|
return super(*args, &block) if respond_to? method_name
Delegator.target.send(method_name, *args, &block)
end
private method_name
end
end

delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout,
:before, :after, :error, :not_found, :configure, :set, :mime_type,
:enable, :disable, :use, :development?, :test?, :production?,
:helpers, :settings
class << self
attr_accessor :target
end

self.target = Application
end

This code does what the comment says: if it is included, it delegates all calls to the list of delegated methods to Sinatra::Application class, which is a subclass of Sinatra::Base, which is where the get method is defined. When you write something like this:

require "sinatra"

get "foo" do
"Hello World"
end

Sinatra will end up calling the get method on Sinatra::Base due to the delegation it set up earlier.



Related Topics



Leave a reply



Submit