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


184

Здається, що в Сінатрі всі обробники маршрутів записуються в один файл, якщо я правильно розумію, він діє як один великий / малий контролер. Чи є спосіб розділити його на окремі незалежні файли, тому, коли скажімо, хтось викликає "/" - виконується одна дія, і якщо smth на зразок "/ posts / 2" отримується ще одна дія - подібна логіка, що застосовується в PHP ?

Відповіді:


394

Ось основний шаблон для додатків Sinatra, який я використовую. (У моїх більших додатках розбиті подібні 200 файлів, без урахування дорогоцінних каменів постачальника, що охоплюють 75-100 явних маршрутів. Деякі з цих маршрутів - це маршрути Regexp, що охоплюють додаткові шаблони маршрутів 50+). подібний додаток за допомогою:
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

 
помічники / партії.рб

# 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

11
Особливо приємна річ у вищевказаній структурі - зокрема, введення require "sequel"та DBініціалізація models/init.rbта використання require_relativeдля всіх файлів - це те, що ви можете modelsвводити файли у свій каталог, відкривати консоль IRB та вводити, require './init'і у вас повно завантажена база даних та модель для інтерактивного дослідження. .
Фрогз

1
Прекрасна структура прикладу, ідеально підходить для нота-синатри, як я, привіт.
Баррі Джордан

27
Я використовував інший підхід. Зашифруйте всю ділову логіку, як користувачі та сервіси в рубіні, не вимагаючи «sinatra». Це робить логіку самостійною. Тоді я використовую один файл додатків, щоб розподілити відповідальність перед різними класами, тобто приблизно 3 рядки коду на маршрут. У типовому додатку не так багато маршрутів, тому файл мого додатка насправді не так вже й довгий.
Том Андерсен

1
Чи поширена практика визначати клас у кількох файлах? Ви переосмислюєте "MyApp" знову і знову у кожному файлі. Я новачок у рубіні, тому мені здається дивним. У чому причина цього?
0xSina

5
@ 0xSina Це не рідкість у Рубі. Ви не "визначаєте" клас, ви "відкриваєте його". Наприклад, Arrayклас визначається основною бібліотекою, але пізніше ви можете «monkeypatch» використовувати, використовуючи class Array; def some_awesome_method; endа) всі попередні функції масиву збережені, і b) всі екземпляри масиву отримають ваш новий код. Заняття в Ruby - це лише об'єкти, які можуть бути доповнені та змінені в будь-який час.
Фрогз

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

7
Одна приємна річ у використанні явного init.rbпроти вказаного вище - це те, що ви можете контролювати порядок завантаження, якщо у вас є взаємозалежні файли.
Фрогз

10

Пошукуйте в Google "плиту котла Sinatra", щоб отримати деякі ідеї щодо того, як інші викладають свої програми Sinatra. З цього ви, ймовірно, можете знайти той, який відповідає вашим потребам або просто зробити свій власний. Це не надто важко зробити. Коли ви розробляєте більше додатків Sinatra, ви можете додати їх до своєї котлової панелі.

Ось що я зробив і використовую для всіх своїх проектів:

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


7

Я знаю, що це старий запит, але я все ще не можу повірити, що ніхто не згадав Padrino. Ви можете використовувати його як рамку на вершині Сінатри, або додавати лише частинки, які вас цікавлять. Він б'є десять прикладів попки!


Я погоджуюся, вам слід поглянути на Падріно, воно гойдається!
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
    ...

Ще одна деталь про підпроекти, якими я користувався, полягала в тому, щоб додати їхні імена, опис та маршрути до якоїсь глобальної змінної, яка використовується "/"для створення домашньої сторінки довідника, але у мене зараз немає фрагмента.


1

Читання документів тут:

Розширення синатри

Здається, що Sinatra дозволяє розкласти додаток на модулі Ruby, які можна запустити за допомогою методу "регістр" Сінатри або "помічників", наприклад:

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


1

Ключ до модульності Синатри для великих проектів - навчитися використовувати основні інструменти.

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

Як тільки ви навчитеся запускати Sinatra з Rack, відкриється цілий новий світ стратегій модульності. Це, очевидно, пропонує запросити щось дійсно корисне: тепер ви можете розраховувати на наявність індивідуальних дорогоцінних каменів для кожної додаткової програми , що може дати вам змогу легко оновлювати модулі.

Не варто недооцінювати потужність використання gem-модулів для вашої програми. Ви можете легко протестувати експериментальні зміни у чітко розмежованому середовищі та легко їх розгортати. Так само легко повернути назад, якщо щось піде не так.

Існує тисяча способів впорядкувати свій код, тому не завадить спроба отримати макет, схожий на Rails. Однак є також кілька чудових публікацій про те, як налаштувати власну структуру. Ця публікація охоплює інші часті потреби більшості веб-розробників.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.