Usando Sinatra para projetos maiores através de vários arquivos

184

Parece que em Sinatra todos os manipuladores de rota estão sendo gravados em um único arquivo; se bem entendi, ele atua como um controlador grande / pequeno. Existe alguma maneira de dividi-lo em arquivos independentes separados, portanto, quando digamos que alguém chame "/" - uma ação é executada e se smth como "/ posts / 2" for recebido, outra ação - lógica semelhante que é aplicada no PHP ?

spacemonkey
fonte

Respostas:

394

Aqui está um modelo básico para aplicativos Sinatra que eu uso. (Meus aplicativos maiores têm mais de 200 arquivos divididos assim, sem contar as gemas dos fornecedores, cobrindo de 75 a 100 rotas explícitas. Algumas dessas rotas são rotas Regexp que cobrem mais de 50 padrões de rota.) Ao usar o Thin, você executa um aplicativo como este usando:
thin -R config.ru start

Edit : Agora estou mantendo meu próprio esqueleto de monge com base no chamado Riblits abaixo . Para usá-lo para copiar meu modelo como base para seus próprios projetos:

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

# Inside your empty project directory
monk init -s riblits

Layout do arquivo:

config.ru
app.rb
ajudantes /
  init.rb
  parcials.rb
modelos /
  init.rb
  user.rb
rotas /
  init.rb
  login.rb
  main.rb
Visualizações/
  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 / parcials.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
Phrogz
fonte
11
Uma particularmente agradável coisa sobre o supra-estrutura especificamente colocando require "sequel"ea DBinicialização em models/init.rb, e usando require_relativepara todos os arquivos-é que você pode CD no modelsdiretório, abra um console IRB e tipo require './init'e você tem o seu banco de dados e modelo de configuração completa carregado para exploração interativa .
Phrogz
1
Um ótimo exemplo de estrutura, perfeito para um Sinatra noob como eu, aplaude.
Barry Jordan
27
Eu usei uma abordagem diferente. Codifique toda a lógica de negócios como usuários e serviços em ruby, sem a necessidade de 'sinatra'. Isso faz com que a lógica se mantenha por si própria. Então eu uso um único arquivo de aplicativo para distribuir responsabilidades para várias classes, portanto, cerca de 3 linhas de código por rota. Não há muitas rotas no aplicativo típico, portanto, meu arquivo de aplicativo não é tão longo.
Tom Andersen
1
É uma prática comum definir uma classe em vários arquivos? Você está redefinindo 'MyApp' repetidamente em todos os arquivos. Eu sou novo no Ruby, então isso me parece estranho. Qual é a razão por trás disso?
0xSina
5
@ 0xSina Não é incomum em Ruby. Você não "define" uma classe, você "reabre". Por exemplo, a Arrayclasse é definida pela biblioteca principal, mas você pode "monkeypatch" mais tarde usando class Array; def some_awesome_method; ende a) todas as funcionalidades anteriores da matriz são preservadas eb) todas as instâncias da matriz receberão seu novo código. As classes em Ruby são apenas objetos e podem ser aumentadas e alteradas a qualquer momento.
Phrogz
10

Absolutamente. Para ver um exemplo disso, recomendo baixar a gema Monk, descrita aqui:

https://github.com/monkrb/monk

Você pode 'instalá-lo' via rubygems.org. Depois de obter a gema, gere um aplicativo de exemplo usando as instruções acima.

Observe que você não precisa usar o Monk para o seu desenvolvimento real, a menos que queira (na verdade, acho que pode não ser atual). O objetivo é ver como você pode estruturar facilmente seu aplicativo no estilo MVC (com arquivos de rota semelhantes ao controlador), se desejar.

É bem simples se você observar como o Monk lida com isso, principalmente uma questão de exigir arquivos em diretórios separados, algo como (você terá que definir o caminho_ raiz):

Dir[root_path("app/**/*.rb")].each do |file|
    require file
end
TK-421
fonte
7
Uma coisa boa de usar um explícito em init.rbrelação ao acima é que você pode controlar a ordem de carregamento, caso tenha arquivos interdependentes.
Phrogz
10

Faça uma pesquisa no Google por "clichê Sinatra" para obter algumas idéias de como os outros estão organizando seus aplicativos Sinatra. A partir disso, você provavelmente encontrará um que atenda às suas necessidades ou simplesmente faça o seu. Não é tão difícil de fazer. À medida que você desenvolve mais aplicativos Sinatra, você pode adicionar ao seu clichê.

Aqui está o que eu fiz e usei para todos os meus projetos:

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

Robert Ziehl
fonte
7

Eu sei que essa é uma consulta antiga, mas ainda não consigo acreditar que ninguém tenha mencionado Padrino. Você pode usá-la como uma estrutura em cima de Sinatra, ou em partes, adicionando apenas as gemas que lhe interessam. Chuta dez buttloads de burro!

Steven Garcia
fonte
Concordo, você deve dar uma olhada em Padrino, é demais!
NicoPaez
2

Minha abordagem para hospedar projetos diferentes no mesmo site é usar sinatra/namespaceda seguinte maneira:

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

Outro detalhe sobre os subprojetos que usei foi adicionar seus nomes, descrição e rotas a algum tipo de variável global, usada "/"para criar uma página inicial do guia, mas não tenho um trecho no momento.

Nakilon
fonte
1

Lendo os documentos aqui:

Extensões de Sinatra

Parece que o Sinatra permite decompor seu aplicativo em Ruby Modules, que pode ser extraído pelo método "register" do Sinatra ou pelos métodos "helpers", como:

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

Quando Monk não funcionou para mim, comecei a trabalhar nos modelos.

Se você pensar bem, não há nada de especial em vincular um conjunto de arquivos. A filosofia do monge foi explicada para mim no início de 2011 durante o RedDotRubyConf e eles me disseram especificamente que é realmente opcional usá-lo, especialmente agora que dificilmente é mantido.

Este é um bom começo para quem deseja usar o ActiveRecord:

Simple Sinatra MVC

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

kgpdeveloper
fonte
1

A chave da modularidade no Sinatra para projetos maiores é aprender a usar as ferramentas subjacentes.

O SitePoint tem um tutorial muito bom, de onde você pode ver os aplicativos e ajudantes modulares do Sinatra. No entanto, você deve prestar atenção especial a um detalhe importante. Você mantém vários aplicativos Sinatra e os monta com o Rackup. Depois de saber como escrever um aplicativo básico, consulte o arquivo config.ru desse tutorial e observe como eles montam aplicativos Sinatra independentes.

Depois que você aprender a executar o Sinatra com o Rack, um novo mundo de estratégias de modularidade será aberto. Obviamente, isso convida a tentar algo realmente útil: agora você pode contar com Gems individuais para cada sub-aplicação , o que pode permitir que você faça uma versão fácil de seus módulos.

Não subestime o poder de usar módulos gem para seu aplicativo. Você pode testar facilmente alterações experimentais em um ambiente bem delimitado e implementá-las facilmente. Igualmente fácil de reverter se algo der errado.

Existem milhares de maneiras de organizar seu código, portanto, não faria mal tentar obter um layout semelhante ao Rails. No entanto, também existem ótimas postagens sobre como personalizar sua própria estrutura. Essa publicação cobre outras necessidades frequentes da maioria dos desenvolvedores da web.

Se você tiver tempo, recomendamos que você aprenda mais sobre o Rack, o terreno comum para qualquer aplicativo Web baseado em Ruby. Pode ter um impacto muito menor na maneira como você faz seu trabalho, mas sempre há certas tarefas que a maioria das pessoas executa em seus aplicativos que se encaixam melhor como um middleware de rack.

SystematicFrank
fonte