Utilisation de Sinatra pour des projets plus importants via plusieurs fichiers

185

Il semble que dans Sinatra tous les gestionnaires de route sont écrits dans un seul fichier, si je comprends bien, il agit comme un contrôleur grand / petit. Y a-t-il un moyen de le diviser en fichiers indépendants séparés, alors quand disons que quelqu'un appelle "/" - une action est exécutée, et si une action comme "/ posts / 2" est reçue, alors une autre action - logique similaire appliquée en PHP ?

spacemonkey
la source

Réponses:

394

Voici un modèle de base pour les applications Sinatra que j'utilise. (Mes applications plus volumineuses ont plus de 200 fichiers répartis comme ceci, sans compter les gemmes du fournisseur, couvrant 75 à 100 itinéraires explicites. Certains de ces itinéraires sont des itinéraires Regexp couvrant plus de 50 modèles d'itinéraire supplémentaires.) Lorsque vous utilisez Thin, vous exécutez un application comme celle-ci en utilisant:
thin -R config.ru start

Edit : Je maintiens maintenant mon propre squelette Monk basé sur les Riblits ci-dessous . Pour l'utiliser pour copier mon modèle comme base de vos propres projets:

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

# Inside your empty project directory
monk init -s riblits

Disposition du fichier:

config.ru
app.rb
aides /
  init.rb
  partials.rb
des modèles/
  init.rb
  user.rb
itinéraires /
  init.rb
  login.rb
  main.rb
vues /
  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

 
modèles / 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

 
vues / 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
la source
12
Une chose particulièrement intéressante à propos de la structure ci-dessus - en particulier en mettant require "sequel"et l' DBinitialisation dans models/init.rb, et en utilisant require_relativepour tous les fichiers - est que vous pouvez cd dans votre modelsrépertoire, ouvrir une console IRB et taper require './init'et que votre base de données complète et la configuration du modèle sont chargées pour l'exploration interactive .
Phrogz
1
Excellent exemple de structure, parfait pour un Sinatra noob comme moi, bravo.
Barry Jordan le
27
J'ai utilisé une approche différente. Codez toute la logique métier comme les utilisateurs et les services en ruby, sans nécessiter de «sinatra». Cela rend la logique autonome. Ensuite, j'utilise un seul fichier d'application pour répartir les responsabilités entre différentes classes, soit environ 3 lignes de code par itinéraire. Il n'y a pas beaucoup de routes dans l'application typique, donc mon fichier d'application n'est en fait pas si long.
Tom Andersen
1
Est-ce une pratique courante de définir une classe dans plusieurs fichiers? Vous redéfinissez "MyApp" encore et encore dans chaque fichier. Je suis nouveau dans le rubis donc ça me semble bizarre. Quelle est la raison derrière cela?
0xSina
5
@ 0xSina Ce n'est pas rare dans Ruby. Vous ne «définissez» pas une classe, vous la «rouvrez». Par exemple, la Arrayclasse est définie par la bibliothèque principale, mais vous pouvez plus tard "monkeypatch" en utilisant class Array; def some_awesome_method; endet a) toutes les fonctionnalités Array précédentes sont conservées, et b) toutes les instances Array recevront votre nouveau code. Les classes dans Ruby ne sont que des objets et peuvent être augmentées et modifiées à tout moment.
Phrogz
10

Absolument. Pour voir un exemple de cela, je recommande de télécharger la gemme Monk, décrite ici:

https://github.com/monkrb/monk

Vous pouvez l'installer via rubygems.org. Une fois que vous avez la gemme, générez un exemple d'application en utilisant les instructions ci-dessus.

Notez que vous n'êtes pas obligé d'utiliser Monk pour votre développement actuel sauf si vous le souhaitez (en fait, je pense que ce n'est peut-être pas à jour). Le but est de voir comment vous pouvez facilement structurer votre application dans le style MVC (avec des fichiers de routage de type contrôleur séparés) si vous le souhaitez.

C'est assez simple si vous regardez comment Monk le gère, principalement une question d'exiger des fichiers dans des répertoires séparés, quelque chose comme (vous devrez définir root_path):

Dir[root_path("app/**/*.rb")].each do |file|
    require file
end
TK-421
la source
7
Une bonne chose à propos de l'utilisation d'un explicite init.rbpar rapport à ce qui précède est que vous pouvez contrôler l'ordre de chargement, au cas où vous auriez des fichiers interdépendants.
Phrogz
10

Effectuez une recherche Google sur "Sinatra passe-partout" pour avoir des idées sur la manière dont les autres présentent leurs applications Sinatra. À partir de là, vous pouvez probablement en trouver un qui répond à vos besoins ou simplement créer le vôtre. Ce n'est pas trop difficile à faire. Au fur et à mesure que vous développez d'autres applications Sinatra, vous pouvez les ajouter à votre passe-partout.

Voici ce que j'ai fait et utilisé pour tous mes projets:

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

Robert Ziehl
la source
7

Je sais que c'est une vieille requête mais je n'arrive toujours pas à croire que personne n'ait mentionné Padrino. Vous pouvez l'utiliser comme un cadre au-dessus de Sinatra, ou en ajoutant au coup par coup seulement les gemmes qui vous intéressent. Il frappe dix fesses de cul!

Steven Garcia
la source
Je suis d'accord, tu devrais jeter un oeil à Padrino, ça déchire!
NicoPaez
3

La clé de la modularité sur Sinatra pour les grands projets est d'apprendre à utiliser les outils sous-jacents.

SitePoint a un très bon tutoriel à partir duquel vous pouvez voir les applications et les assistants modulaires de Sinatra. Cependant, vous devez porter une attention particulière à un détail important. Vous conservez plusieurs applications Sinatra et les montez avec Rackup. Une fois que vous savez comment écrire une application de base, regardez le fichier config.ru de ce didacticiel et observez comment ils montent des applications Sinatra indépendantes.

Une fois que vous aurez appris à exécuter Sinatra avec Rack, un tout nouveau monde de stratégies de modularité s'ouvrira. Cela invite évidemment à essayer quelque chose de vraiment utile: maintenant, vous pouvez compter sur des Gems individuels pour chaque sous-application , ce qui pourrait vous permettre de versionner facilement vos modules.

Ne sous-estimez pas la puissance de l'utilisation de modules gem pour votre application. Vous pouvez facilement tester les modifications expérimentales dans un environnement bien délimité et les déployer facilement. Tout aussi facile de revenir en arrière si quelque chose ne va pas.

Il existe mille et une façons d'organiser votre code, donc cela ne ferait pas de mal d'essayer d'obtenir une disposition similaire à Rails. Cependant, il existe également d' excellents articles sur la façon de personnaliser votre propre structure. Cet article couvre d'autres besoins fréquents de la plupart des développeurs Web.

Si vous avez le temps, je vous encourage à en savoir plus sur Rack, le terrain d'entente pour toute application Web basée sur Ruby. Cela peut avoir un impact bien moindre sur la façon dont vous effectuez votre travail, mais il y a toujours certaines tâches que la plupart des gens effectuent sur leurs applications qui conviennent mieux en tant que middleware Rack.

SystématiqueFrank
la source
2

Mon approche pour héberger différents projets sur le même site est d'utiliser sinatra/namespacede cette manière:

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 autre détail sur les sous-projets que j'ai utilisé était d'ajouter leurs noms, leur description et leurs itinéraires à une sorte de variable globale, qui est utilisée "/"pour créer une page d'accueil de guide, mais je n'ai pas d'extrait de code pour le moment.

Nakilon
la source
1

Lire la documentation ici:

Extensions Sinatra

Il semble que Sinatra vous permet de décomposer votre application en modules Ruby, qui peuvent être extraits via la méthode Sinatra "register" ou les méthodes "helpers", comme ceci:

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
la source
1

Quand Monk ne fonctionnait pas pour moi, j'ai commencé à travailler moi-même sur des modèles.

Si vous y réfléchissez, il n'y a rien de spécial à attacher un ensemble de fichiers. La philosophie du moine m'a été expliquée au début de 2011 lors de RedDotRubyConf et ils m'ont spécifiquement dit qu'il était vraiment facultatif de l'utiliser surtout maintenant qu'il est à peine entretenu.

C'est un bon début pour ceux qui souhaitent utiliser ActiveRecord:

Simple Sinatra MVC

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

kgpdeveloper
la source