Usando Sinatra para proyectos más grandes a través de múltiples archivos

184

Parece que en Sinatra todos los controladores de ruta se escriben en un solo archivo, si entiendo bien, actúa como un controlador grande / pequeño. ¿Hay alguna manera de dividirlo en archivos independientes separados, así que cuando digamos que alguien llama "/" - se ejecuta una acción, y si se recibe algo como "/ posts / 2", se recibe otra acción - lógica similar que se aplica en PHP ?

mono espacial
fuente

Respuestas:

394

Aquí hay una plantilla básica para las aplicaciones de Sinatra que uso. (Mis aplicaciones más grandes tienen más de 200 archivos desglosados ​​de esta manera, sin contar las gemas del proveedor, que cubren entre 75 y 100 rutas explícitas. Algunas de estas rutas son rutas Regexp que cubren más de 50 patrones de ruta adicionales). Al usar Thin, ejecuta un aplicación como esta usando:
thin -R config.ru start

Editar : ahora mantengo mi propio esqueleto de Monje basado en las siguientes Riblits . Para usarlo para copiar mi plantilla como base para sus propios proyectos:

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

# Inside your empty project directory
monk init -s riblits

Diseño de archivo:

config.ru
app.rb
ayudantes /
  init.rb
  partials.rb
modelos /
  init.rb
  user.rb
rutas /
  init.rb
  login.rb
  main.rb
puntos de vista/
  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

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

 
modelos / user.rb

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

 
rutas / init.rb

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

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

 
rutas / main.rb

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

 
vistas / 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
fuente
11
Una cosa particularmente buena acerca de la estructura anterior, específicamente poner require "sequel"y la DBinicialización en models/init.rb, y usar require_relativepara todos los archivos, es que puede cd en su modelsdirectorio, abrir una consola IRB y escribir require './init'y tiene su base de datos completa y configuración de modelo cargada para exploración interactiva .
Phrogz
1
Gran ejemplo de estructura, perfecto para un novato de Sinatra como yo, saludos.
Barry Jordan
27
Usé un enfoque diferente. Codifique toda la lógica comercial como usuarios y servicios en ruby, sin requerir 'sinatra'. Esto hace que la lógica se mantenga por sí sola. Luego utilizo un solo archivo de aplicación para repartir las responsabilidades a varias clases, por lo que son aproximadamente 3 líneas de código por ruta. No hay muchas rutas en la aplicación típica, por lo que el archivo de mi aplicación en realidad no es tan largo.
Tom Andersen
1
¿Es una práctica común definir una clase en varios archivos? Está redefiniendo 'MyApp' una y otra vez en cada archivo. Soy nuevo en Ruby, así que me parece extraño. ¿Cuál es la razón detrás de esto?
0xSina
55
@ 0xSina No es raro en Ruby. No "define" una clase, la "vuelve a abrir". Por ejemplo, la Arrayclase está definida por la biblioteca principal, pero luego puede "monkeypatch" usando class Array; def some_awesome_method; endy a) se conserva toda la funcionalidad de matriz anterior, yb) todas las instancias de matriz obtendrán su nuevo código. Las clases en Ruby son solo objetos, y se pueden aumentar y cambiar en cualquier momento.
Phrogz
10

Absolutamente. Para ver un ejemplo de esto, recomiendo descargar la gema Monk, descrita aquí:

https://github.com/monkrb/monk

Puede 'instalar gema' a través de rubygems.org. Una vez que tenga la gema, genere una aplicación de muestra utilizando las instrucciones vinculadas anteriormente.

Tenga en cuenta que no tiene que usar Monk para su desarrollo real a menos que lo desee (de hecho, creo que puede no ser actual). El punto es ver cómo puede estructurar fácilmente su aplicación en el estilo MVC (con archivos de ruta similares a los del controlador) si lo desea.

Es bastante simple si observas cómo Monk lo maneja, principalmente una cuestión de requerir archivos en directorios separados, algo así como (tendrás que definir root_path):

Dir[root_path("app/**/*.rb")].each do |file|
    require file
end
TK-421
fuente
77
Una cosa buena de usar un explícito init.rbfrente a lo anterior es que puedes controlar el orden de carga, en caso de que tengas archivos interdependientes.
Phrogz
10

Realice una búsqueda en Google de "Sinatra boilerplate" para obtener algunas ideas sobre cómo otros están presentando sus aplicaciones Sinatra. A partir de eso, probablemente pueda encontrar uno que se adapte a sus necesidades o simplemente hacer el suyo. No es muy difícil de hacer. A medida que desarrolle más aplicaciones de Sinatra, puede agregar a su repetitivo.

Esto es lo que hice y uso para todos mis proyectos:

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

Robert Ziehl
fuente
7

Sé que esta es una consulta antigua, pero todavía no puedo creer que nadie haya mencionado Padrino. Puedes usarla como marco encima de Sinatra, o poco a poco agregando solo las gemas que te interesan. ¡Patea diez traseros de culo!

Steven Garcia
fuente
Estoy de acuerdo, deberías echar un vistazo a Padrino, ¡es genial!
NicoPaez
2

Mi enfoque para alojar diferentes proyectos en el mismo sitio es usar sinatra/namespacede esta manera:

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

Otro detalle sobre los subproyectos que utilicé fue agregar sus nombres, descripción y rutas a algún tipo de variable global, que se utiliza "/"para hacer una página de inicio de la guía, pero no tengo un fragmento en este momento.

Nakilon
fuente
1

Leyendo los documentos aquí:

Extensiones Sinatra

Parece que Sinatra le permite descomponer su aplicación en módulos Ruby, que se pueden extraer mediante el método de "registro" o los métodos "auxiliares" de Sinatra, de la siguiente manera:

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
fuente
1

Cuando Monk no funcionó para mí, comencé a trabajar en plantillas yo mismo.

Si lo piensa, no hay nada especial en atar un conjunto de archivos. La filosofía de los monjes me fue explicada a principios de 2011 durante RedDotRubyConf y me dijeron específicamente que es realmente opcional usarla, especialmente ahora que apenas se mantiene.

Este es un buen comienzo para aquellos que desean usar ActiveRecord:

Sinatra MVC simple

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

kgpdeveloper
fuente
1

La clave para la modularidad en Sinatra para proyectos más grandes es aprender a usar las herramientas subyacentes.

SitePoint tiene un muy buen tutorial desde donde puede ver aplicaciones y ayudantes modulares de Sinatra. Sin embargo, debe prestar especial atención a un detalle importante. Mantiene múltiples aplicaciones Sinatra y las monta con Rackup. Una vez que sepa cómo escribir una aplicación básica, mire el archivo config.ru de ese tutorial y observe cómo se montan las aplicaciones independientes de Sinatra.

Una vez que aprenda a ejecutar Sinatra con Rack, se abrirá un mundo completamente nuevo de estrategias de modularidad. Obviamente, esto invita a probar algo realmente útil: ahora puede confiar en tener Gemas individuales para cada aplicación secundaria , lo que podría permitirle versionar fácilmente sus módulos.

No subestimes el poder de usar gem-modules para tu aplicación. Puede probar fácilmente los cambios experimentales en un entorno bien delimitado y desplegarlos fácilmente. Igualmente fácil de revertir si algo sale mal.

Hay miles de formas de organizar su código, por lo que no estaría de más tratar de obtener un diseño similar a Rails. Sin embargo, también hay algunas publicaciones excelentes sobre cómo personalizar su propia estructura. Esa publicación cubre otras necesidades frecuentes de la mayoría de los desarrolladores web.

Si tiene tiempo, le animo a que aprenda más sobre Rack, el terreno común para cualquier aplicación web basada en Ruby. Puede tener un impacto mucho menor en la forma en que realiza su trabajo, pero siempre hay ciertas tareas que la mayoría de las personas realizan en sus aplicaciones que se adaptan mejor como middleware Rack.

Frank sistemático
fuente