Search code examples
rubysinatra

Using Sinatra for larger projects via multiple files


It seems that in Sinatra all route handlers are being written into a single file, if I understand right it acts as a one large/small controller. Is there any way to split it into separate independent files, so when let's say somebody calls "/" - one action is executed, and if something like "/posts/2" is received then another action - similar logic that is applied in PHP?


Solution

  • 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