Multiple Sinatra .90 applications in one process
I recently started work on a new application based on the current Sinatra edge code. I'm not new to Ruby, but I'm definitely new to Sinatra. So I thought I'd start a small series of posts based on the things I find either interesting, or difficult. Today's post probably falls into both categories.
One of the things that drew me to Sinatra was the absolute simplicity in it's approach to web apps. One of the keys to this simplicity is leaning heavily on Rack. Rack provides a variety of what they call Middleware, small bits of code that insert themselves into the request/response process, handling things like caching, session management, or even JSON parsing.
Now that we're through with the background, let's hit my find of the day.
As I started working I found that I felt more comfortable with my application being defined inside of a class instead of just floating around in files (the default method of building a Sinatra app). It turns out this is one of the lesser documented parts of the new Sinatra .9 release. (In all fairness this seems to be due to the fact that a lot of the infrastructure that allows this to happen is either brand new, or heavily refactored in this release)
It turns out I could do a whole lot more, such as making each portion of my application it's own standalone Sinatra application, then using some Rack Middleware to mount each portion into it's own URL space. Why would I want to do this? I think it will make for a very extensible, and potentially scalable infrastructure. All of that aside, let's see HOW you do this!
For starters you need to define a class for your application to live in.
There's a simple set of classes that function as little application stubs. Notice that we require sinatra/base instead of just sinatra. This is one of the new things in the .9 release. By requiring just sinatra/base we get all of the sinatra goodness, without any of the intrusive top-level methods. Since we're going to encapsulate our application in our own classes, this is perfect for us! For these little applications to fully work we'll have to define some views, which I'll leave up to you (or you can check the full project files at GitHub at the end of this post).
Now here is where we define our Rack magic. We'll create a config.ru file, which uses what's called RackUp syntax to specify some options for Rack compliant servers to use to configure and start our application. For this example I'm using Thin, but you can use whatever you prefer!
Up until line 15 it's pretty standard boilerplate for getting any Sinatra application going, but after that is where things get interesting. Rack automatically includes the URLMap Middleware which gives us access to the 'map' method. This method takes two parameters, a URL prefix, and a block specifying that applications configuration. We could do any configuration we can do at the top level inside of that block, giving some pretty powerful per-application configuration.
And that's it! Start it with thin -p 4567 -R config.ru start, or the equivalent. You should be able to navigate to / and see your first index, and /blog and /blog/list respectively to see their content. Notice how our Blog class never uses the /blog URL prefix anywhere. This would let us pass the exact same class into a different application, at a different URL space, and the application wouldn't be any wiser for it.
Want to see the whole thing already setup? Check out the GitHub link below for the full sample application.
http://github.com/tannerburson/multi-sinatra-sample/tree/master
One of the things that drew me to Sinatra was the absolute simplicity in it's approach to web apps. One of the keys to this simplicity is leaning heavily on Rack. Rack provides a variety of what they call Middleware, small bits of code that insert themselves into the request/response process, handling things like caching, session management, or even JSON parsing.
Now that we're through with the background, let's hit my find of the day.
As I started working I found that I felt more comfortable with my application being defined inside of a class instead of just floating around in files (the default method of building a Sinatra app). It turns out this is one of the lesser documented parts of the new Sinatra .9 release. (In all fairness this seems to be due to the fact that a lot of the infrastructure that allows this to happen is either brand new, or heavily refactored in this release)
It turns out I could do a whole lot more, such as making each portion of my application it's own standalone Sinatra application, then using some Rack Middleware to mount each portion into it's own URL space. Why would I want to do this? I think it will make for a very extensible, and potentially scalable infrastructure. All of that aside, let's see HOW you do this!
For starters you need to define a class for your application to live in.
There's a simple set of classes that function as little application stubs. Notice that we require sinatra/base instead of just sinatra. This is one of the new things in the .9 release. By requiring just sinatra/base we get all of the sinatra goodness, without any of the intrusive top-level methods. Since we're going to encapsulate our application in our own classes, this is perfect for us! For these little applications to fully work we'll have to define some views, which I'll leave up to you (or you can check the full project files at GitHub at the end of this post).
Now here is where we define our Rack magic. We'll create a config.ru file, which uses what's called RackUp syntax to specify some options for Rack compliant servers to use to configure and start our application. For this example I'm using Thin, but you can use whatever you prefer!
Up until line 15 it's pretty standard boilerplate for getting any Sinatra application going, but after that is where things get interesting. Rack automatically includes the URLMap Middleware which gives us access to the 'map' method. This method takes two parameters, a URL prefix, and a block specifying that applications configuration. We could do any configuration we can do at the top level inside of that block, giving some pretty powerful per-application configuration.
And that's it! Start it with thin -p 4567 -R config.ru start, or the equivalent. You should be able to navigate to / and see your first index, and /blog and /blog/list respectively to see their content. Notice how our Blog class never uses the /blog URL prefix anywhere. This would let us pass the exact same class into a different application, at a different URL space, and the application wouldn't be any wiser for it.
Want to see the whole thing already setup? Check out the GitHub link below for the full sample application.
http://github.com/tannerburson/multi-sinatra-sample/tree/master
9 Comments:
Thank you for this blog post, which I saw on the sinatra list today. It helped me wrap my head around the fact that urls in a Sinatra::Base extended class are relative to the url the class is mounted at in the rack config, which is very, very cool. I tossed together a small example app of my own to verify that this is how it works, inspired by your blog post:
http://github.com/gnugeek/gnugeek-sinatra-example/tree/master
Wonderful post! The more I see Rack in use the more I'm amazed it's not gaining more traction.
Because you've required 'sinatra/base' over 'sinatra' you no longer need the boilerplate disable and sets.
If you are interested in compact apps and having multiple apps co-exist with different "mounts", you might also look at Waves, which includes a compact Foundation, similar to Sinatra, but possibly providing more flexibility.
Thanks for the info!!
I am experimenting with modularizing my web app into multiple sinatra apps and I running Passenger on development mode but my sinatra apps won't get reloaded.
Any suggestions?
@macario I actually don't mess much with the reloading and even less with Passenger. The reloading support tends to be pretty fragile between Rack and Sinatra's different methods. Supposedly the Sinatra team is working on a completely new reloading method. Sorry that's not much help!
There have been several posts to the sinatrarb mailing list asking about reloading, you might check if any of the suggestions there help.
Thanks, I found a solution:
# config.ru
require 'my_sinatra_app'
set :environment, ENV['RACK_ENV'].to_sym
use Rack::Reloader, 0
use Rack::ShowExceptions
map "/my_app" do
run MySinatraApp
end
# my_sinatra_app.rb
require 'sinatra'
class MySinatraApp < Sinatra::Application
set :run, true
get '/' do
'Hello!'
end
end
require 'sinatra' must be at top of each sinatra app file.
Great article, I'm now on the path of refactoring one of my merb apps into a sinatra app. I have a second php app that I'm porting to sinatra and these two apps are actually used together. So I want them to work on their own and also together...
Question: How do you create links in your views so the "map" part is taken into consideration?
%a{ :href=>"/list"} Blog List
Will go to /list and not /blog/list - what do you look up to get /blog?
%a{ :href="#{what_goes_here?}/list"}
Thanks :)
@SoreGums I've taken to doing it one of two ways. One is to set a property on the particular class called PATH and call that in the map block to set what the path prefix will be.
The other method is to use, I believe, request.path_info, which should return the base path.
Post a Comment
Links to this post:
Create a Link
<< Home