Verwenden von Sinatra für größere Projekte über mehrere Dateien

184

Es scheint, dass in Sinatra alle Routen-Handler in eine einzige Datei geschrieben werden. Wenn ich das richtig verstehe, fungiert sie als ein einziger großer / kleiner Controller. Gibt es eine Möglichkeit, es in separate unabhängige Dateien aufzuteilen? Wenn also jemand "/" aufruft, wird eine Aktion ausgeführt, und wenn etwas wie "/ posts / 2" empfangen wird, wird eine andere Aktion - eine ähnliche Logik, die in PHP angewendet wird ?

Raumschlüssel
quelle

Antworten:

394

Hier ist eine grundlegende Vorlage für Sinatra-Apps, die ich verwende. (In meinen größeren Apps sind mehr als 200 Dateien wie diese aufgeschlüsselt, wobei die Edelsteine ​​des Anbieters nicht berücksichtigt werden und 75 bis 100 explizite Routen abgedeckt werden. Einige dieser Routen sind Regexp-Routen, die weitere 50 Routenmuster abdecken.) Wenn Sie Thin verwenden, führen Sie eine aus App wie diese mit:
thin -R config.ru start

Bearbeiten : Ich pflege jetzt mein eigenes Mönchsgerüst basierend auf den unten genannten Riblits . So kopieren Sie meine Vorlage als Grundlage für Ihre eigenen Projekte:

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

# Inside your empty project directory
monk init -s riblits

Dateilayout:

config.ru
app.rb.
Helfer /
  init.rb.
  partials.rb
Modelle /
  init.rb.
  user.rb
Routen /
  init.rb.
  login.rb
  main.rb
Ansichten /
  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'

 
Helfer / init.rb.

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

require_relative 'nicebytes'
MyApp.helpers NiceBytes

 
helfer / partielle.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

 
Routen / init.rb.

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

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

 
routen / main.rb.

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

 
Ansichten / 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
Phrogz
quelle
11
Eine besonders schöne Sache an der obigen Struktur - insbesondere das Einfügen require "sequel"und DBInitialisieren in models/init.rbund Verwenden require_relativefür alle Dateien - ist, dass Sie in Ihr modelsVerzeichnis cd , eine IRB-Konsole öffnen und eingeben können require './init'und Ihr vollständiges Datenbank- und Modell-Setup für die interaktive Erkundung geladen haben .
Phrogz
1
Tolle Beispielstruktur, perfekt für einen Sinatra-Noob wie mich, Prost.
Barry Jordan
27
Ich habe einen anderen Ansatz gewählt. Codieren Sie alle Geschäftslogiken wie Benutzer und Dienste in Ruby, ohne dass Sinatra erforderlich ist. Dadurch steht die Logik für sich. Dann verwende ich eine einzelne App-Datei, um die Verantwortlichkeiten auf verschiedene Klassen zu verteilen, also ungefähr 3 Codezeilen pro Route. In der typischen Anwendung gibt es nicht viele Routen, daher ist meine App-Datei eigentlich gar nicht so lang.
Tom Andersen
1
Ist es üblich, eine Klasse in mehreren Dateien zu definieren? Sie definieren 'MyApp' in jeder Datei immer wieder neu. Ich bin neu in Ruby, also kommt es mir komisch vor. Was ist der Grund dafür?
0xSina
5
@ 0xSina Es ist nicht ungewöhnlich in Ruby. Sie "definieren" eine Klasse nicht, sondern "öffnen sie erneut". Zum Beispiel wird die ArrayKlasse von der Kernbibliothek definiert, aber Sie können später "monkeypatch" verwenden, indem Sie class Array; def some_awesome_method; enda) alle vorherigen Array-Funktionen beibehalten und b) alle Array-Instanzen Ihren neuen Code erhalten. Klassen in Ruby sind nur Objekte und können jederzeit erweitert und geändert werden.
Phrogz
10

Absolut. Um ein Beispiel dafür zu sehen, empfehle ich, das hier beschriebene Monk-Juwel herunterzuladen:

https://github.com/monkrb/monk

Sie können es über rubygems.org 'gem installieren'. Sobald Sie den Edelstein haben, erstellen Sie eine Beispiel-App anhand der oben verlinkten Anweisungen.

Beachten Sie, dass Sie Monk nicht für Ihre eigentliche Entwicklung verwenden müssen, es sei denn, Sie möchten (tatsächlich denke ich, dass es möglicherweise nicht aktuell ist). Der Punkt ist zu sehen, wie Sie Ihre App einfach im MVC-Stil (mit separaten Controller-ähnlichen Routendateien) strukturieren können, wenn Sie möchten.

Es ist ziemlich einfach, wenn Sie sich ansehen, wie Monk damit umgeht. Meistens müssen Dateien in separaten Verzeichnissen benötigt werden (Sie müssen root_path definieren):

Dir[root_path("app/**/*.rb")].each do |file|
    require file
end
TK-421
quelle
7
Eine nette Sache bei der Verwendung eines expliziten init.rbgegenüber dem oben genannten ist, dass Sie die Reihenfolge des Ladens steuern können, falls Sie voneinander abhängige Dateien haben.
Phrogz
10

Führen Sie eine Google-Suche nach "Sinatra Boilerplate" durch, um einige Ideen zu erhalten, wie andere ihre Sinatra-Anwendungen gestalten. Daraus können Sie wahrscheinlich eine finden, die Ihren Bedürfnissen entspricht, oder einfach Ihre eigenen erstellen. Es ist nicht zu schwer zu tun. Wenn Sie weitere Sinatra-Apps entwickeln, können Sie diese zu Ihrem Boilerplate hinzufügen.

Folgendes habe ich für alle meine Projekte erstellt und verwendet:

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

Robert Ziehl
quelle
7

Ich weiß, dass dies eine alte Abfrage ist, aber ich kann immer noch nicht glauben, dass niemand Padrino erwähnt hat. Sie können sie als Rahmen über Sinatra verwenden oder Stück für Stück nur die Edelsteine ​​hinzufügen, die Sie interessieren. Es tritt zehn Buttloads Arsch!

Steven Garcia
quelle
Ich stimme dir zu, du solltest dir Padrino ansehen, es rockt!
NicoPaez
2

Mein Ansatz, verschiedene Projekte auf derselben Site zu hosten, besteht darin, Folgendes zu verwenden sinatra/namespace:

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
    ...

Ein weiteres Detail über Teilprojekte, die ich verwendet habe, war das Hinzufügen ihrer Namen, Beschreibungen und Routen zu einer Art globaler Variable, die "/"zum Erstellen einer Leitfaden-Homepage verwendet wird, aber ich habe derzeit kein Snippet.

Nakilon
quelle
1

Lesen Sie die Dokumente hier:

Sinatra-Erweiterungen

Es scheint, dass Sie mit Sinatra Ihre Anwendung in Ruby-Module zerlegen können, die über die Sinatra-Methode "register" oder "helpers" wie folgt abgerufen werden können:

helpers.rb

require 'sinatra/base'

module Sinatra
  module Sample
    module Helpers

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

    end
  end
end

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
Erin Swenson-Healey
quelle
1

Als Monk nicht für mich arbeitete, fing ich an, selbst an Vorlagen zu arbeiten.

Wenn Sie darüber nachdenken, ist es nichts Besonderes, eine Reihe von Dateien zu binden. Die Mönchsphilosophie wurde mir Anfang 2011 während der RedDotRubyConf erklärt und sie haben mir ausdrücklich gesagt, dass es wirklich optional ist, sie zu verwenden, besonders jetzt, wo sie kaum gewartet wird.

Dies ist ein guter Anfang für diejenigen, die ActiveRecord verwenden möchten:

Einfache Sinatra MVC

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

kgpdeveloper
quelle
1

Der Schlüssel zur Modularität von Sinatra für größere Projekte ist das Erlernen der Verwendung der zugrunde liegenden Tools.

SitePoint hat ein sehr gutes Tutorial, in dem Sie modulare Sinatra-Apps und -Helfer sehen können. Sie sollten jedoch besonders auf ein wichtiges Detail achten. Sie behalten mehrere Sinatra-Apps und mounten sie mit Rackup. Wenn Sie wissen, wie man eine Basis-App schreibt, sehen Sie sich die Datei config.ru dieses Tutorials an und beobachten Sie, wie sie unabhängige Sinatra-Apps bereitstellen .

Sobald Sie lernen, Sinatra mit Rack zu betreiben, eröffnet sich eine völlig neue Welt von Modularitätsstrategien. Dies lädt natürlich etwas wirklich nützlich , um zu versuchen: jetzt können Sie auf mit einzelner Gems für jede verlassen Unter Anwendung , was könnten Sie leicht Version Ihre Module ermöglichen.

Unterschätzen Sie nicht die Möglichkeiten, Gem-Module für Ihre App zu verwenden. Sie können experimentelle Änderungen problemlos in einer gut abgegrenzten Umgebung testen und problemlos bereitstellen. Ebenso einfach, zurück zu kehren, wenn etwas schief geht.

Es gibt tausend Möglichkeiten, Ihren Code zu organisieren, sodass es nicht schaden würde, ein Layout zu erhalten, das Rails ähnelt. Es gibt jedoch auch einige großartige Beiträge zum Anpassen Ihrer eigenen Struktur. Dieser Beitrag deckt andere häufige Bedürfnisse der meisten Webentwickler ab.

Wenn Sie Zeit haben, empfehle ich Ihnen, mehr über Rack zu erfahren, die gemeinsame Basis für jede Ruby-basierte Webanwendung. Es hat möglicherweise einen weitaus geringeren Einfluss darauf, wie Sie Ihre Arbeit erledigen, aber es gibt immer bestimmte Aufgaben, die die meisten Leute mit ihren Apps erledigen, die besser als Rack-Middleware passen.

SystematicFrank
quelle