Multiple Sinatra Apps Using Rack-Mount

Multiple Sinatra apps using rack-mount

I think you'll prefer Rack::URLMap - it will probably look something like this:

run Rack::URLMap.new("/" => App.new, 
"/api" => Api.new)

That should go in your config.ru file.

How to mount a Sinatra application inside another Sinatra app?

Take a look at https://stackoverflow.com/a/15699791/335847 which has some ideas about namespacing.

Personally, I would use the config.ru with mapped routes. If you're really in that space between "should this be a separate app or is it just helpful to organize it like this" it allows that, and then later you can still farm off one of the apps on its own without changing the code (or only a little). If you're finding that there's a lot of duplicated set up code, I'd do something like this:

# base_controller.rb

require 'sinatra/base'
require "haml"
# now come some shameless plugs for extensions I maintain :)
require "sinatra/partial"
require "sinatra/exstatic_assets"

module MyAmazingApp
class BaseController < Sinatra::Base
register Sinatra::Partial
register Sinatra::Exstatic

end

class Blog < BaseController
# this gets all the stuff already registered.
end

class Foo < BaseController
# this does too!
end
end

# config.ru

# this is just me being lazy
# it'd add in the /base_controller route too, so you
# may want to change it slightly :)
MyAmazingApp.constants.each do |const|
map "/#{const.name.downcase}" do
run const
end
end

Here's a quote from Sinatra Up and Running:

Not only settings, but every aspect of a Sinatra class will be inherited by its subclasses. This includes defined routes, all the error handlers, extensions, middleware, and so on.

It has some good examples of using this technique (and others). Since I'm in shameless plug mode I recommend it, even though I've nothing to do with it! :)

Using routes from one sinatra app from another when racked up

If these apps are racked up in Rack together, they have no real connectivity. What Rack does call .call(env) on whatever app you have racked up and that responds with [status,env,body] You can have layers in the middle that act on and modify env. Your Rack is triggering #call on whatever app you mapped to respond to that path. The called app has no concept of what is running in Rack. Nor can it call another app unless there is a handle for it in env that was populated upstream.

But you have to consider why you decided to make these 2 different apps in the first place. I would imagine it is due to the fact that there could be multiple apps using the API. Or you enjoy the decoupled nature. So in that case you have to think if you would want some coupling through Rack at all. What if the API was on another server? Any internal link between the 2 through Rack would break the UI application. If you always know your API will be self contained on the same machine you could just as well make it a library and not do HTTP calls.

So even if it is possible to call routes from a co racked up app I would never do it. The whole reason for having API servers is to have a decoupled layer. And you should access that layer via HTTP so it would work if it were on same box or across the world.

rackup mounting apps vs rails mounting for sinatra apps

Interesting there is any significant difference apart from the fact that in the latter part(application mounted using rackup)

any requests to

/url1

/url2

would be server directly from the mounted rack application without actually passing the request in the middleware stack of rails and then detect valid rack application for the given mounted path .

Which would happen in first part where your sinatra application is mounted inside Rails defined in routes.rb of your file

So I your are trying to mount your sinatra application in routes.rb instead of config.ru then consider the fact that your request would be passed all along rails middleware stack
where routing middleware detect the appropriate path as passed the request to desired sinatra application

A Simple check You can do for this is try hitting your path i.e /url1 or /url2 in both the technique and you would see sinatra application routes.rb would log the request in your rails application whereas the other one would not

Hope this help

Using Sinatra for larger projects via multiple files

Here is a basic template for Sinatra apps that I use. (My larger apps have 200+ files broken out like this, not counting vendor'd gems, covering 75-100 explicit routes. Some of these routes are Regexp routes covering an additional 50+ route patterns.) When using Thin, you run an app like this using:

thin -R config.ru start

Edit: I'm now maintaining my own Monk skeleton based on the below called Riblits. To use it to copy my template as the basis for your own projects:

# Before creating your project
monk add riblits git://github.com/Phrogz/riblits.git

# Inside your empty project directory
monk init -s riblits

File Layout:


config.ru
app.rb
helpers/
init.rb
partials.rb
models/
init.rb
user.rb
routes/
init.rb
login.rb
main.rb
views/
layout.haml
login.haml
main.haml

 

config.ru

root = ::File.dirname(__FILE__)
require ::File.join( root, 'app' )
run MyApp.new

 

app.rb

# encoding: utf-8
require 'sinatra'
require 'haml'

class MyApp < Sinatra::Application
enable :sessions

configure :production do
set :haml, { :ugly=>true }
set :clean_trace, true
end

configure :development do
# ...
end

helpers do
include Rack::Utils
alias_method :h, :escape_html
end
end

require_relative 'models/init'
require_relative 'helpers/init'
require_relative 'routes/init'

 

helpers/init.rb

# encoding: utf-8
require_relative 'partials'
MyApp.helpers PartialPartials

require_relative 'nicebytes'
MyApp.helpers NiceBytes

 

helpers/partials.rb

# encoding: utf-8
module PartialPartials
def spoof_request(uri,env_modifications={})
call(env.merge("PATH_INFO" => uri).merge(env_modifications)).last.join
end

def partial( page, variables={} )
haml page, {layout:false}, variables
end
end

 

helpers/nicebytes.rb

# encoding: utf-8
module NiceBytes
K = 2.0**10
M = 2.0**20
G = 2.0**30
T = 2.0**40
def nice_bytes( bytes, max_digits=3 )
value, suffix, precision = case bytes
when 0...K
[ bytes, 'B', 0 ]
else
value, suffix = case bytes
when K...M then [ bytes / K, 'kiB' ]
when M...G then [ bytes / M, 'MiB' ]
when G...T then [ bytes / G, 'GiB' ]
else [ bytes / T, 'TiB' ]
end
used_digits = case value
when 0...10 then 1
when 10...100 then 2
when 100...1000 then 3
else 4
end
leftover_digits = max_digits - used_digits
[ value, suffix, leftover_digits > 0 ? leftover_digits : 0 ]
end
"%.#{precision}f#{suffix}" % value
end
module_function :nice_bytes # Allow NiceBytes.nice_bytes outside of Sinatra
end

 

models/init.rb

# encoding: utf-8
require 'sequel'
DB = Sequel.postgres 'dbname', user:'bduser', password:'dbpass', host:'localhost'
DB << "SET CLIENT_ENCODING TO 'UTF8';"

require_relative 'users'

 

models/user.rb

# encoding: utf-8
class User < Sequel::Model
# ...
end

 

routes/init.rb

# encoding: utf-8
require_relative 'login'
require_relative 'main'

 

routes/login.rb

# encoding: utf-8
class MyApp < Sinatra::Application
get "/login" do
@title = "Login"
haml :login
end

post "/login" do
# Define your own check_login
if user = check_login
session[ :user ] = user.pk
redirect '/'
else
redirect '/login'
end
end

get "/logout" do
session[:user] = session[:pass] = nil
redirect '/'
end
end

 

routes/main.rb

# encoding: utf-8
class MyApp < Sinatra::Application
get "/" do
@title = "Welcome to MyApp"
haml :main
end
end

 

views/layout.haml

!!! XML
!!! 1.1
%html(xmlns="http://www.w3.org/1999/xhtml")
%head
%title= @title
%link(rel="icon" type="image/png" href="/favicon.png")
%meta(http-equiv="X-UA-Compatible" content="IE=8")
%meta(http-equiv="Content-Script-Type" content="text/javascript" )
%meta(http-equiv="Content-Style-Type" content="text/css" )
%meta(http-equiv="Content-Type" content="text/html; charset=utf-8" )
%meta(http-equiv="expires" content="0" )
%meta(name="author" content="MeWho")
%body{id:@action}
%h1= @title
#content= yield

Organizing Sinatra routing blocks over multiple files

You can just re-open the class in different files.

# file_a.rb

require 'sinatra'
require_relative "./file_b.rb"

class App < Sinatra::Base
get("/a") { "route a" }
run!
end

# file_b.rb

class App < Sinatra::Base
get("/b") { "route b" }
end

If you really want different classes you can do something like this, but it's a little ugly:

# file_a.rb

require 'sinatra'
require_relative "./file_b.rb"

class App < Sinatra::Base
get("/a") { "route a" }
extend B
run!
end

# file_b.rb

module B
def self.extended(base)
base.class_exec do
get("/b") { "route b" }
end
end
end

I'm pretty sure these two are the easiest ways to do it. When you look inside the source code of how Sinatra actually adds routes from a method like get, it's pretty hairy.

I guess you could also do something goofy like this, but I wouldn't exactly call it idiomatic:

# file_a.rb

require 'sinatra'

class App < Sinatra::Base
get("/a") { "route a" }
eval File.read("./file_b.rb")
run!
end

# file_b.rb

get("/b") { "route b" }

Mount multiple Rack apps without adding a prefix to the url

The answer is indeed Rack::Cascade:

run Rack::Cascade.new( [API, Web] )


Related Topics



Leave a reply



Submit