Использование Sinatra для больших проектов через несколько файлов

184

Кажется, что в Синатре все обработчики маршрутов записываются в один файл, если я правильно понимаю, он действует как один большой / маленький контроллер. Есть ли способ разбить его на отдельные независимые файлы, поэтому, когда, скажем, кто-то вызывает "/" - выполняется одно действие, а если получено что-то вроде "/ posts / 2", тогда другое действие - аналогичная логика, которая применяется в PHP ?

spacemonkey
источник

Ответы:

394

Вот базовый шаблон для приложений Sinatra, которые я использую. (В моих больших приложениях разбито более 200 файлов, не считая драгоценных камней вендора, охватывающих 75-100 явных маршрутов. Некоторые из этих маршрутов являются маршрутами Regexp, охватывающими дополнительные 50+ шаблонов маршрутов.) При использовании Thin вы запускаете приложение, как это с помощью:
thin -R config.ru start

Редактировать : я теперь поддерживаю свой собственный скелет Монаха, основанный на ниже названном 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
хелперы /
  init.rb
  partials.rb
модели /
  init.rb
  user.rb
маршруты /
  init.rb
  login.rb
  main.rb
Просмотры/
  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'

 
хелперы / init.rb

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

require_relative 'nicebytes'
MyApp.helpers NiceBytes

 
хелперы / 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

 
хелперы / 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

 
модели / 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'

 
модели / user.rb

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

 
маршруты / init.rb

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

 
маршруты / 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

 
маршруты / main.rb

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

 
просмотров / 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
источник
11
Одна особенно приятная вещь в вышеупомянутой структуре - в частности, добавление require "sequel"и DBинициализация models/init.rbи использование require_relativeдля всех файлов - это то, что вы можете перейти в свой modelsкаталог, открыть консоль IRB и ввести текст, require './init'и у вас есть полная база данных и модель, загруженная для интерактивного исследования. ,
Phrogz
1
Отличный пример структуры, идеально подходит для Синатры, как я, ура.
Барри Джордан
27
Я использовал другой подход. Кодируйте всю бизнес-логику, такую ​​как пользователи и сервисы, в ruby, не требуя sinatra. Это заставляет логику стоять самостоятельно. Затем я использую один файл приложения, чтобы распределить обязанности между различными классами, примерно по 3 строки кода на маршрут. В типичном приложении не так много маршрутов, поэтому файл моего приложения на самом деле не такой длинный.
Том Андерсен
1
Является ли обычной практикой определение класса в нескольких файлах? Вы переопределяете 'MyApp' снова и снова в каждом файле. Я новичок в рубине, так что мне это кажется странным. В чем причина этого?
0xSina
5
@ 0xSina Это не редкость в Ruby. Вы не «определяете» класс, вы «открываете его». Например, Arrayкласс определяется базовой библиотекой, но позже вы можете «monkeypatch», используя class Array; def some_awesome_method; endи а) все предыдущие функциональные возможности Array, и б) все экземпляры Array получат ваш новый код. Классы в Ruby являются просто объектами и могут быть дополнены и изменены в любое время.
Phrogz
10

Абсолютно. Чтобы увидеть пример этого, я рекомендую скачать камень Monk, описанный здесь:

https://github.com/monkrb/monk

Вы можете «установить его» через rubygems.org. После того, как вы получите драгоценный камень, создайте пример приложения, используя инструкции, приведенные выше.

Обратите внимание, что вам не нужно использовать Monk для вашей реальной разработки, если вы не хотите (на самом деле я думаю, что это может быть не актуально). Суть в том, чтобы увидеть, как вы можете легко структурировать свое приложение в стиле MVC (с отдельными файлами маршрутов, подобными контроллеру), если хотите.

Это довольно просто, если вы посмотрите на то, как Monk справляется с этим, в основном вопрос требует наличия файлов в отдельных каталогах, что-то вроде (вам нужно определить root_path):

Dir[root_path("app/**/*.rb")].each do |file|
    require file
end
ТК-421
источник
7
Хорошим преимуществом использования явного init.rbи вышеописанного является то, что вы можете контролировать порядок загрузки, если у вас есть взаимозависимые файлы.
Phrogz
10

Выполните поиск в Google по «шаблону Sinatra», чтобы получить некоторые идеи о том, как другие выкладывают свои приложения Sinatra. Из этого вы, вероятно, можете найти тот, который соответствует вашим потребностям или просто сделать свой собственный. Это не так уж сложно сделать. По мере того, как вы будете разрабатывать больше приложений для Sinatra, вы сможете добавить их в свой шаблон.

Вот что я сделал и использовал для всех моих проектов:

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

Роберт Зиль
источник
7

Я знаю, что это старый запрос, но я до сих пор не могу поверить, что никто не упомянул Падрино. Вы можете использовать его как основу поверх Sinatra или по частям, добавляя только те камни, которые вас интересуют. Это пинает десять ягодиц задницы!

Стивен Гарсия
источник
Я согласен, вы должны взглянуть на Падрино, он потрясающий!
NicoPaez
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
    ...

Еще одна деталь, касающаяся подпроектов, которые я использовал, заключалась в добавлении их имен, описания и маршрутов в какую-то глобальную переменную, которая используется "/"для создания домашней страницы руководства, но сейчас у меня нет фрагмента.

Nakilon
источник
1

Читая документы здесь:

Расширения Синатры

Похоже, что 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

маршрутизации / 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

Когда Монах не работал на меня, я сам начал работать с шаблонами.

Если вы думаете об этом, нет ничего особенного в связывании набора файлов. Философия монаха была объяснена мне в начале 2011 года во время RedDotRubyConf, и они специально сказали мне, что использовать ее действительно необязательно, особенно сейчас, когда она почти не поддерживается.

Это хорошее начало для тех, кто хочет использовать ActiveRecord:

Простая Синатра MVC

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

kgpdeveloper
источник
1

Ключом к модульности в Sinatra для крупных проектов является обучение использованию базовых инструментов.

SitePoint имеет очень хороший учебник, из которого вы можете увидеть модульные приложения и помощники Sinatra. Однако стоит обратить особое внимание на одну важную деталь. Вы храните несколько приложений Sinatra и монтируете их с помощью Rackup. Если вы знаете, как написать простое приложение, посмотрите файл config.ru этого руководства и посмотрите, как они монтируют независимые приложения Sinatra.

Как только вы научитесь управлять Sinatra с Rack, откроется новый мир стратегий модульности. Это, очевидно, побуждает попробовать что-то действительно полезное: теперь вы можете рассчитывать на наличие отдельных Gems для каждого подприложения , что может позволить вам легко создавать версии ваших модулей.

Не стоит недооценивать возможности использования гем-модулей для вашего приложения. Вы можете легко протестировать экспериментальные изменения в хорошо разграниченной среде и легко развернуть их. Одинаково легко вернуться назад, если что-то пойдет не так.

Существует тысяча способов организации вашего кода, поэтому не мешало бы получить компоновку, аналогичную Rails. Однако есть также несколько хороших постов о том, как настроить свою собственную структуру. Этот пост покрывает другие частые потребности большинства веб-разработчиков.

Если у вас есть время, я призываю вас больше узнать о Rack, общем для любого веб-приложения на основе Ruby. Это может оказать гораздо меньшее влияние на то, как вы выполняете свою работу, но всегда есть определенные задачи, которые большинство людей выполняют в своих приложениях, которые лучше подходят в качестве промежуточного программного обеспечения Rack.

SystematicFrank
источник