Utilizzo di Sinatra per progetti più grandi tramite più file


184

Sembra che in Sinatra tutti i gestori di route vengano scritti in un unico file, se ho capito bene, agisce come un controller grande / piccolo. C'è un modo per dividerlo in file indipendenti separati, quindi quando diciamo che qualcuno chiama "/" - viene eseguita un'azione e se viene ricevuto smth come "/ posts / 2", allora un'altra azione - logica simile che viene applicata in PHP ?

Risposte:


394

Ecco un modello di base per le app Sinatra che uso. (Le mie app più grandi hanno più di 200 file suddivisi in questo modo, senza contare le gemme vendor, che coprono 75-100 route esplicite. Alcune di queste route sono route Regexp che coprono altri 50+ pattern di route.) Quando si utilizza Thin, si esegue un app come questa usando:
thin -R config.ru start

Modifica : ora sto mantenendo il mio scheletro Monk basato sul sotto chiamato Riblits . Per usarlo per copiare il mio modello come base per i tuoi progetti:

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

# Inside your empty project directory
monk init -s riblits

Layout del file:

config.ru
app.rb
aiutanti /
  init.rb
  partials.rb
Modelli/
  init.rb
  user.rb
itinerari/
  init.rb
  login.rb
  main.rb
visualizzazioni/
  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'

 
aiutanti / init.rb

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

require_relative 'nicebytes'
MyApp.helpers NiceBytes

 
aiutanti / 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

 
aiutanti / 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

 
modelli / 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'

 
modelli / user.rb

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

 
percorsi / init.rb

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

 
percorsi / 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

 
percorsi / 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

11
Una cosa particolarmente bella della struttura di cui sopra - in particolare l'inserimento require "sequel"e l' DBinizializzazione models/init.rbe l'utilizzo require_relativedi tutti i file - è che puoi inserire un CD nella tua modelsdirectory, aprire una console e un tipo IRB require './init'e avere il tuo database completo e la configurazione del modello caricati per l'esplorazione interattiva .
Phrogz,

1
Grande struttura di esempio, perfetta per un noob di Sinatra come me, evviva.
Barry Jordan,

27
Ho usato un approccio diverso. Codifica tutte le logiche aziendali come utenti e servizi in ruby, senza richiedere "sinatra". Questo rende la logica autonoma. Quindi uso un singolo file dell'app per distribuire le responsabilità a varie classi, quindi circa 3 righe di codice per route. Non ci sono molti percorsi nell'applicazione tipica, quindi il mio file dell'app non è poi così lungo.
Tom Andersen,

1
È una pratica comune definire una classe in più file? Stai ridefinendo 'MyApp' più e più volte in ogni file. Sono nuovo di rubino quindi mi sembra strano. Qual è la ragione dietro questo?
0xSina

5
@ 0xSina Non è raro in Ruby. Non "definisci" una classe, la "riapri". Ad esempio, la Arrayclasse viene definita dalla libreria principale, ma è possibile successivamente "monkeypatch" utilizzando class Array; def some_awesome_method; ende a) tutte le funzionalità precedenti dell'array e b) tutte le istanze dell'array otterranno il nuovo codice. Le classi in Ruby sono solo oggetti e possono essere aumentate e modificate in qualsiasi momento.
Phrogz,

10

Assolutamente. Per vedere un esempio di questo, ti consiglio di scaricare la gemma Monk, descritta qui:

https://github.com/monkrb/monk

Puoi 'gem installarlo' tramite rubygems.org. Una volta che hai la gemma, genera un'app di esempio usando le istruzioni collegate sopra.

Nota che non devi usare Monk per il tuo sviluppo reale a meno che tu non voglia (in effetti penso che potrebbe non essere attuale). Il punto è vedere come puoi strutturare facilmente la tua app nello stile MVC (con file di route separati come controller) se vuoi.

È abbastanza semplice se guardi come Monk lo gestisce, principalmente una questione di richiedere file in directory separate, qualcosa del tipo (dovrai definire root_path):

Dir[root_path("app/**/*.rb")].each do |file|
    require file
end

7
Una cosa bella dell'uso di un esplicito init.rbrispetto a quanto sopra è che puoi controllare l'ordine di caricamento, nel caso tu abbia file interdipendenti.
Phrogz,

10

Effettua una ricerca su Google per "Caldaia Sinatra" per ottenere alcune idee su come altri stanno presentando le loro applicazioni Sinatra. Da questo probabilmente puoi trovarne uno adatto alle tue esigenze o semplicemente crearne uno tuo. Non è troppo difficile da fare. Man mano che sviluppi più app Sinatra, puoi aggiungerle al tuo bollettino.

Ecco cosa ho realizzato e utilizzato per tutti i miei progetti:

https://github.com/rziehl/sinatra-boilerplate


7

So che questa è una vecchia domanda, ma non riesco ancora a credere che nessuno abbia menzionato Padrino. Puoi usarlo come una struttura in cima a Sinatra, o aggiungere frammentariamente solo le gemme che ti interessano. Calcia dieci calci in culo!


Sono d'accordo, dovresti dare un'occhiata a Padrino, è incredibile!
NicoPaez,

2

Il mio approccio per ospitare diversi progetti sullo stesso sito è di utilizzare sinatra/namespacein questo modo:

server.rb

require "sinatra"
require "sinatra/namespace"

if [ENV["LOGNAME"], ENV["USER"]] == [nil, "naki"]
    require "sinatra/reloader"
    register Sinatra::Reloader
    set :port, 8719
else
    set :environment, :production
end

for server in Dir.glob "server_*.rb"
    require_relative server
end

get "/" do
    "this route is useless"
end

server_someproject.rb

module SomeProject
    def self.foo bar
       ...
    end
    ...
end

namespace "/someproject" do
    set :views, settings.root
    get "" do
        redirect request.env["REQUEST_PATH"] + "/"
    end
    get "/" do
        haml :view_someproject
    end
    post "/foo" do
        ...
        SomeProject.foo ...
    end
end

view_someproject.haml

!!!
%html
    ...

Un altro dettaglio sui sottoprogetti che ho usato è stato quello di aggiungere i loro nomi, descrizione e percorsi a una sorta di variabile globale, che viene utilizzata "/"per creare una home page della guida, ma non ho uno snippet al momento.


1

Leggendo i documenti qui:

Estensioni di Sinatra

Sembra che Sinatra ti permetta di scomporre la tua applicazione in moduli Ruby, che possono essere inseriti attraverso il metodo "registra" o i metodi "aiutanti" di Sinatra, in questo modo:

helpers.rb

require 'sinatra/base'

module Sinatra
  module Sample
    module Helpers

      def require_logged_in()
        redirect('/login') unless session[:authenticated]
      end

    end
  end
end

di routing / foos.rb

require 'sinatra/base'

module Sinatra
  module Sample
    module Routing
      module Foos

        def self.registered(app)           
          app.get '/foos/:id' do
            # invoke a helper
            require_logged_in

            # load a foo, or whatever
            erb :foos_view, :locals => { :foo => some_loaded_foo }
          end   
        end  

      end
    end     
  end
end

app.rb

#!/usr/bin/env ruby

require 'sinatra'

require_relative 'routing/foos'

class SampleApp < Sinatra::Base

  helpers Sinatra::Sample::Helpers

  register Sinatra::Sample::Routing::Foos

end

1

Quando Monk non ha funzionato per me, ho iniziato a lavorare su modelli da solo.

Se ci pensate, non c'è niente di speciale nel legare un set di file. La filosofia del monaco mi è stata spiegata all'inizio del 2011 durante RedDotRubyConf e mi hanno specificamente detto che è davvero facoltativo usarlo, soprattutto ora che è appena mantenuto.

Questo è un buon inizio per coloro che vogliono usare ActiveRecord:

Sinatra MVC semplice

https://github.com/katgironpe/simple-sinatra-mvc


1

La chiave per la modularità su Sinatra per progetti più grandi è imparare a usare gli strumenti sottostanti.

SitePoint ha un ottimo tutorial da cui puoi vedere le app e gli helper modulari di Sinatra. Tuttavia, è necessario prestare particolare attenzione a un dettaglio importante. Continui a più applicazioni Sinatra e montare loro con Rackup. Una volta che sai come scrivere un'app di base, guarda config.ru file di quel tutorial e osserva come montano app Sinatra indipendenti.

Una volta che avrai imparato a gestire Sinatra con Rack, si aprirà un nuovo mondo di strategie di modularità. Questo ovviamente invita a provare qualcosa di veramente utile: ora puoi fare affidamento su gemme individuali per ogni subapplicazione , cosa potrebbe permetterti di versioni facilmente dei tuoi moduli.

Non sottovalutare la potenza dell'utilizzo dei moduli gem per la tua app. Puoi facilmente testare le modifiche sperimentali in un ambiente ben delimitato e distribuirle facilmente. Altrettanto facile tornare indietro se qualcosa va storto.

Esistono mille modi per organizzare il codice, quindi non sarebbe male cercare di ottenere un layout simile a Rails. Tuttavia, ci sono anche alcuni post interessanti su come personalizzare la propria struttura. Quel post copre altre esigenze frequenti della maggior parte degli sviluppatori web.

Se hai tempo, ti incoraggio a saperne di più su Rack, il terreno comune per qualsiasi applicazione web basata su Ruby. Potrebbe avere un impatto molto minore sul modo in cui svolgi il tuo lavoro, ma ci sono sempre alcuni compiti che la maggior parte delle persone svolge sulle proprie app che si adattano meglio come un middleware Rack.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.