使用Sinatra通过多个文件进行大型项目开发

188

在Sinatra中,所有路由处理程序似乎都被写入单个文件中,如果我理解正确,它就像一个大/小控制器。是否有任何方法将其拆分为独立的单独文件,以便当某人调用“/”时执行一个操作,而如果接收到类似“/posts/2”的内容,则执行另一个操作-类似于PHP中应用的逻辑?

8个回答

401
这里是我使用的Sinatra应用程序基本模板,涉及IT技术相关内容。(我的较大型应用程序有200多个文件,像这样分解,不包括供应商的宝石,覆盖75-100个明确的路由。其中一些路由是Regexp路由,涵盖了50多个其他路由模式。)在使用Thin时,您可以使用以下命令运行应用程序:
thin -R config.ru start 编辑: 我现在正在维护自己的Monk骨架,基于下面的Riblits。要将其用作复制我的模板作为您自己项目的基础,请执行以下操作:
# Before creating your project
monk add riblits git://github.com/Phrogz/riblits.git

# Inside your empty project directory
monk init -s riblits

文件布局:

config.ru
app.rb
helpers/
  init.rb
  partials.rb
models/
  init.rb
  user.rb
routes/
  init.rb
  login.rb
  main.rb
views/
  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

 
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

12
require "sequel"DB 的初始化放在 models/init.rb 中,然后对所有文件使用 require_relative,是上述结构特别好的一点。这样,你可以进入 models 目录,打开 IRB 控制台,输入 require './init',就可以加载完整的数据库和模型设置,进行交互式探索了。 - Phrogz
1
很好的示例结构,非常适合像我这样的Sinatra新手,谢谢。 - Barry Jordan
27
我采用了一种不同的方法。将所有业务逻辑,如用户和服务,用 Ruby 编写,而不需要 require 'sinatra'。这样可以使逻辑独立存在。然后我使用一个单一的应用程序文件将职责分配给各个类,因此每条路由只有大约3行代码。在典型应用程序中没有太多路由,所以我的应用程序文件实际上并不长。 - Tom Andersen
5
在 Ruby 中这种情况很常见。你不是“定义”一个类,而是“重新打开”它。例如,Array 类由核心库定义,但是你可以通过使用 class Array; def some_awesome_method; end 来“monkeypatch”,并且 a) 所有以前的 Array 功能都被保留,b) 所有 Array 实例都将获得你的新代码。在 Ruby 中,类只是对象,可以随时进行增强和更改。 - Phrogz
1
clean_trace 在 Sinatra 中是一个有效选项吗?https://github.com/sinatra/sinatra/issues/148 - briangonzalez
显示剩余20条评论

10
当然。为了看到这一点的例子,我建议下载Monk gem,这里有描述:

https://github.com/monkrb/monk

你可以通过rubygems.org安装它。一旦你拥有了这个gem,按照上面链接的说明生成一个示例应用程序即可。
请注意,除非你想要使用Monk(实际上我认为它可能已经不是最新的),否则你不必在实际开发中使用它。重点是看看如何轻松地将你的应用程序结构化为MVC风格(使用类似控制器的路由文件)。
如果你查看Monk处理方式,你会发现它非常简单,主要是需要在不同的目录中引用文件,类似于以下代码(你需要定义root_path):
Dir[root_path("app/**/*.rb")].each do |file|
    require file
end

7
使用显式的 init.rb 相比上述方法的一个好处是可以控制加载顺序,以防存在相互依赖的文件。 - Phrogz

9

可以通过谷歌搜索“Sinatra boilerplate”来获取其他人如何布置他们的Sinatra应用程序的一些想法。从中你可能会找到符合你需求的模板,或者自己制作一个模板也不难。当你开发更多的Sinatra应用程序时,可以添加到你的模板中。

下面是我为我所有项目所使用的模板:

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


链接已损坏。 - mipmip

8
我知道这是一个旧的查询,但我仍然不敢相信没有人提到Padrino。你可以将其用作Sinatra框架的顶层,或者只添加您感兴趣的宝石。它非常强大!

我同意,你应该看看Padrino,它非常棒! - NicoPaez

5
在Sinatra中实现大型项目的模块化关键是学会使用底层工具。SitePoint有一篇非常好的教程,可以看到模块化的Sinatra应用程序和辅助工具。但是你应该特别注意一个重要细节。您可以保留多个Sinatra应用程序并使用Rackup将它们挂载起来。一旦您知道如何编写基本应用程序,请查看该教程的config.ru文件,并观察如何挂载独立的Sinatra应用程序。
一旦您学会了如何使用Rack运行Sinatra,整个新的模块化策略世界将为您打开。这显然鼓励尝试一些真正有用的东西:现在您可以依赖于每个子应用程序的单独Gem,这可能使您轻松地对模块进行版本控制。
不要低估使用gem-modules的能力。您可以在明确定义的环境中轻松测试实验性更改,并轻松部署它们。如果出现问题,同样容易恢复。
有一千种组织代码的方式,所以尝试获取类似Rails的布局并不会有害。然而也有一些很棒的文章关于如何自定义自己的结构。该文章涵盖了大多数Web开发者的其他常见需求。
如果你有时间,我鼓励你学习更多关于Rack的知识,这是任何基于Ruby的Web应用程序的共同基础。它可能对你的工作方式影响较小,但总有某些任务适合作为Rack中间件处理。

2

我处理在同一网站上托管不同项目的方法是使用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
    ...

我使用的另一个子项目的细节是将它们的名称、描述和路由添加到某种全局变量中,该变量被"/"用于制作指南主页,但我现在没有代码片段。


1

阅读此处的文档:

Sinatra扩展

似乎Sinatra允许您将应用程序分解为Ruby模块,可以通过Sinatra的“register”方法或“helpers”方法引入这些模块,如下所示:

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

1

当 Monk 对我不起作用时,我开始自己制作模板。

如果你想一想,绑定一组文件并没有什么特别的。Monk 的哲学在 2011 年早期的 RedDotRubyConf 上向我解释过,他们特别告诉我现在它几乎没有维护,所以使用它真的是可选的。

这对于那些想要使用 ActiveRecord 的人来说是一个很好的开始:

简单的 Sinatra MVC

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接