best/most elegant way to share objects between a stack of rack mounted apps/middlewares? - ruby

What is the best idiom to share an object between rack mounted applications/middlewares?
For example, this config.ru has two Sinatra apps mapped to differents endpoints:
class App1 < Sinatra::Base
# ...
end
class App2 < Sinatra::Base
# ...
end
map '/app1' do
run App1
end
map '/app2' do
run App2
end
Now if these two applications need to share an object, be it a database connector or any other object, what would be the best idiom for this?
I see basically two options:
1- Create a constant at the config.ru level and simply refer to that constant within the apps. For example:
SHARED_OBJECT = "hello world"
class App1 < Sinatra::Base
get '/' do
SHARED_OBJECT
end
end
class App2 < Sinatra::Base
get '/' do
SHARED_OBJECT
end
end
map '/app1' do
run App1
end
map '/app2' do
run App2
end
2- Create a singleton object at the config.ru level and use it within the apps. For example:
class SharedObject
include Singleton
def test
#test ||= "hello world"
end
end
class App1 < Sinatra::Base
get '/' do
SharedObject.instance.test
end
end
class App2 < Sinatra::Base
get '/' do
SharedObject.instance.test
end
end
map '/app1' do
run App1
end
map '/app2' do
run App2
end
Comments/suggestions?
Colin

I would move the shared object into a separate file and namespace it. If it were a database connection object, it might look like this:
# config.ru:
require 'lib/my_app/database'
...
class App1 < Sinatra::Base
get '/' do
MyApp::Database.with_connection do |conn|
...
end
end
end
# lib/my_app/database:
module MyApp
module Database
def self.with_connection(&block)
...
end
end
end
I see this as having several benefits:
it reduces the size of your config.ru file, improving readability
it separates concerns -- config.ru is about Rack applications; lib/my_app/database.rb is about databases
it makes it easier to extract. At some point down the line, you might want to separate App1 and App2 into separate projects and include the database connectivity as a gem. This is easy when the database code is in its own module and file.
Phrogz's comment about constants being enough and not needing singletons is wise.

Related

Organizing Sinatra "routing blocks" over multiple files

Any non-trivial Sinatra app will have more "routes" than one would want to put in one big Sinatra::Base descendant class. Say I wanted to put them in another class, what is idiomatic? What is that other class descended from? How do I "include" it in the main Sinatra class?
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" }
To give another way of doing things, you can always organise them by their use, for example:
class Frontend < Sinatra::Base
# routes here
get "/" do #…
end
class Admin < Sinatra:Base
# routes with a different focus here
# You can also have things that wouldn't apply elsewhere
# From the docs
set(:auth) do |*roles| # <- notice the splat here
condition do
unless logged_in? && roles.any? {|role| current_user.in_role? role }
redirect "/login/", 303
end
end
end
get "/my/account/", :auth => [:user, :admin] do
"Your Account Details"
end
get "/only/admin/", :auth => :admin do
"Only admins are allowed here!"
end
end
You can even set up a base class and inherit from that:
module MyAmazingApp
class Base < Sinatra::Base
# a helper you want to share
helpers do
def title=nil
# something here…
end
end
# standard route (see the example from
# the book Sinatra Up and Running)
get '/about' do
"this is a general app"
end
end
class Frontend < Base
get '/about' do
"this is actually the front-end"
end
end
class Admin < Base
#…
end
end
Of course, each of these classes can be split into separate files if you wish. One way to run them:
# config.ru
map("/") do
run MyAmazingApp::Frontend
end
# This would provide GET /admin/my/account/
# and GET /admin/only/admin/
map("/admin") do
MyAmazingApp::Admin
end
There are other ways, I suggest you get hold of that book or check out a few blog posts (some of the high scorers for this tag are a good place to start).

How can you set up scoped redirects for Sinatra apps

I have a series of sinatra applications that are set up such that each is responsible for one thing.
Let's say I have two apps like this:
class Foo < Sinatra::Base
get '/' do
'FOO!'
end
end
class Zoo < Sinatra::Base
get '/' do
'ZOO!'
end
get '/zoom' do
# do things
redirect '/'
end
end
Now let's say I have my config.ru as such:
require './application'
run Rack::URLMap.new('/' => Foo.new, '/zoo' => Zoo.new)
The problem I'm running into is when I try to do the redirect in the zoom action I get sent off to the index action of Foo instead of Zoo. Is there a clean way to do this such that my applications don't need to know how the routes are set up for the app?
You can use configurable redirects. See http://www.sinatrarb.com/2011/03/03/sinatra-1.2.0.html#configurable_redirects.
E.g.
class Zoo < Sinatra::Base
get '/' do
'ZOO!'
end
get '/zoom' do
# do things
redirect to('/')
end
end
Or alternatively, as mentioned in the link above, skip the to() call by enabling prefixed redirects in the Zoo app:
class Zoo < Sinatra::Base
enable :prefixed_redirects
...

Pass arguments to new sinatra app

Simple question: I want to be able to pass options into my sinatra app in config.ru. How is that possible? My config.ru looks like this:
run MyApp
But I want to have this in my MyApp class to take arguments:
class MyApp < Sinatra::Base
def initialize(config)
#config = config
end
end
But I can't figure out a way to do this. Ideas?
Use set/settings
require 'sinatra/base'
class MyApp < Sinatra::Base
get '/' do
settings.time_at_startup.to_s
end
end
# Just arbitrarily picking time as it'll be static but, diff for each run.
MyApp.set :time_at_startup, Time.now
run MyApp
Use a config file. See Sinatra::ConfigFile in contrib (which also uses set and settings, but loads params from a YAML file)
If you want to configure with params, I figured out that you could do this:
require 'sinatra/base'
class AwesomeApp < Sinatra::Base
def initialize(app = nil, params = {})
super(app)
#bootstrap = params.fetch(:bootstrap, false)
end
end
rnicholson's response will be the best answer in most cases but if what you want is to have access to an instance variable in your routes, you can set these up using the before filter as explained in the Sinatra README:
Before filters are evaluated before each request within the same context as the routes will be and can modify the request and response. Instance variables set in filters are accessible by routes and templates:
before do
#note = 'Hi!'
request.path_info = '/foo/bar/baz'
end
get '/foo/*' do
#note #=> 'Hi!'
params['splat'] #=> 'bar/baz'
end

Sharing filters between applications

I am building a modular Padrino application to mount mutiple applications.
I am mounting a base application to /
class BaseApp < Padrino::Application
...
end
I am then mounting other appilications to other endpoints, such as /clients and these applications inherit from the base application:
class ClientsApp < BaseApp
...
end
This inheritence allows me to define all my settings, error handling and any included rack middleware in the app.rb for the BaseApp class.
So far so good. But I would also like to share before and after routing between apps. For example in my BaseApp controller code I want to do this:
BaseApp.controller do
before do
...
end
after do
...
end
get :index do
...
end
end
Rather than repeating these filters in my ClientsApp controller code, like so:
ClientsApp.controller do
before do
...
end
after do
...
end
get :index do
...
end
end
Is there anyway I can DRY up this code and specify the filters once in BaseApp and have them somehow inherited? I understand these filters are methods calls rather than methods.
Thanks!
You can use standard sinatra extensions, put under lib:
# lib/common_filters.rb
module CommonFilters
def self.registered(app)
app.before do
...
end
app.after do
...
end
end
end
Then in yours apps:
# app/app.rb
class MyApp < Padrino::Application
register CommonFilters
end

Is it possible to write a root route with a file extension in Sinatra?

I'm writing a JSON API with Sinatra and I'm separating the different resources into Sinatra::Base classes using the map command:
map('/people') { run Api::People }
Within Api::People, /people would be mapped as the root path /. I'd like /people.json to be handled via Api::People -- is this possible? I can't figure out how to write the route.
If you want a DRYer alternative:
%w(people people.json).each do |route|
map('/' + route) { run Api::People }
end
or you could include the slash in the array like %w(/path/to/api /path/to/api.json)
Looks like a second mapping is required:
map('/people') { run Api::People }
map('/people.json') { run Api::People }
When I add that, /people.json is sent to the root path of Api::People as I wanted.
The problem with this approach is that I have a lot of nested resources, with translates into a lot of repetitive mappings.
I've settled on a design that is both elegant and logically consistent. Did you know a Sinatra::Base class can mount other Sinatra::Base classes inside itself as middleware?
Once I figured that out, the solution is obvious:
config.ru
Dir['api/**/*.rb'].each {|file| require file }
run API::Router
api/router.rb
module API
class Router < Sinatra::Base
use Businesses
use People
use Users
get '*' do
not_found
end
end
end
api/businesses.rb
class API::Businesses < Sinatra::Base
use Locations
get '/businesses.json' do ... end
get '/businesses/:id.json' do ... end
end
api/businesses/locations.rb
class API::Businesses < Sinatra::Base
class Locations < Sinatra::Base
before { #business = Business.find_by_id( params[:business_id] ) }
get '/businesses/:business_id/locations.json' do ... end
get '/businesses/:business_id/locations/:id.json' do ... end
end
end
An additional benefit is that all routes are complete, so you don't have to constantly remember what '/' is actually mapping to.

Resources