Using Sinatra For Larger Projects Via Multiple Files

Using Sinatra for larger projects via multiple files

Here is a basic template for Sinatra apps that I use. (My larger apps have 200+ files broken out like this, not counting vendor'd gems, covering 75-100 explicit routes. Some of these routes are Regexp routes covering an additional 50+ route patterns.) When using Thin, you run an app like this using:

thin -R config.ru start

Edit: I'm now maintaining my own Monk skeleton based on the below called Riblits. To use it to copy my template as the basis for your own projects:

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

# Inside your empty project directory
monk init -s riblits

File Layout:


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

Sinatra Routing - Separate Files

You'll need to restart the webserver to load routes that were added while it was running. Routes are loaded into memory when app.rb is invoked and Sinatra is launched. The route itself looks fine and it appears routes.rb is being imported successfully via Dir[File.join(File.dirname(__FILE__), "lib", "*.rb")].each { |lib| require lib }.

If you're running the server directly through terminal Ctrl+X, Ctrl+C should shut it down, then restart it via rackup config.ru* or ruby app.rb. You may confirm the route is recognized by making a get request through your browser to: http://127.0.0.1:4567/test.

For the rackup config.ru command to work, you can change config.ru to something like:

# config.ru
require './app'
run Sinatra::Application

This is just a deployment convenience.

Edit: @shaun, because Todo extends Sinatra::Base it's fine to use run Todo in your case.

Organizing Sinatra routing blocks over multiple files

You can just re-open the class in different files.

# file_a.rb

require 'sinatra'
require_relative "./file_b.rb"

class App < Sinatra::Base
get("/a") { "route a" }
run!
end

# file_b.rb

class App < Sinatra::Base
get("/b") { "route b" }
end

If you really want different classes you can do something like this, but it's a little ugly:

# file_a.rb

require 'sinatra'
require_relative "./file_b.rb"

class App < Sinatra::Base
get("/a") { "route a" }
extend B
run!
end

# file_b.rb

module B
def self.extended(base)
base.class_exec do
get("/b") { "route b" }
end
end
end

I'm pretty sure these two are the easiest ways to do it. When you look inside the source code of how Sinatra actually adds routes from a method like get, it's pretty hairy.

I guess you could also do something goofy like this, but I wouldn't exactly call it idiomatic:

# file_a.rb

require 'sinatra'

class App < Sinatra::Base
get("/a") { "route a" }
eval File.read("./file_b.rb")
run!
end

# file_b.rb

get("/b") { "route b" }

How to upload multiple files via Sinatra and avoid NoMethodError - undefined method `read' for #File:0x0000000xxxxxx0:String

When you run join() on your tempfiles:

mz= params['images'].map{|f| f[:tempfile] }.join(";")

You're taking a a bunch of File objects and forcing them into strings using to_s(). But the default to_s for a File is to create this generally useless thing:

"#<File:0x0000000xxxxxx0>"

Which is why you're getting the error message that you are.


As for how to fix it, the solution is simply to not turn your files into strings. I don't entirely understand why you're taking an array, joining it into a string, then immediately splitting the string back into an array. I would just not do any of that:

post '/upload2' do
puts params

filename = params["images"][0][:filename]
puts filename

tempfile = params["images"][0][:tempfile]
puts tempfile

path = "/home/user/Descargas/sinatra_ajax-master/#{filename}"

File.open(path, 'wb') do |f|
f.write(tempfile.read)
end

erb :index

end

Sinatra with multiple environment config

I finally came up with a solution using self.included class method:

# config.rb
require 'sinatra/custom_logger'

module Config
def self.included(base_klass)
base_klass.extend(ClassMethods)

base_klass.helpers(Sinatra::CustomLogger)

base_klass.class_eval do
configure :development do
logger = MyCustomLogger.new(
param1,
param2,
param3,
paramx
)


set :logger, logger
end

configure :production do
# other stuff
end
end
end

module ClassMethods; end
end

then

require_relative 'config'

class MyApp < Sinatra::Base
include Config

Multiple file uploads in Sinatra

The only thing that puts me off here is that you don't use the POST method – maybe your issue has to do with that. Anyway, the following code works perfectly for me. I hope this will give you a hint how to fix your code.

require 'sinatra'

get '/' do
<<-HTML
<html>
<head><title>Multi file upload</title></head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="images[]" multiple />
<input type="submit" />
</form>
</body>
</html>
HTML
end

post '/upload' do
content_type :text

res = "I received the following files:\n"
res << params['images'].map{|f| f[:filename] }.join("\n")
res
end

Sinatra Multiple Models / Helpers with same name

Quick solution is to use load instead of require, because load reloads file each time you ask it.
But much better solution is to require all libraries and make namespaces. Google for module in ruby as namespace

Build MVC structure on top of Sinatra

Sinatra is already "VC" - you have views separated from your routes (controllers). You can choose to break it into multiple files if you like; for more on that, see this answer (mine):

Using Sinatra for larger projects via multiple files

To add an "M" (model), pick a database framework. Some people like ActiveRecord. Some people like DataMapper. There are many more from which you might choose. I personally love and highly recommend Sequel. My answer linked above also suggests a directory structure and shell for including the models. Once you distribute appropriate logic between your models and controllers, you have your "MVC".

Note that MVC is not about separate files, but separation of concerns. If you set up a Sinatra application as I suggest above, but have your views fetching data from your models, or you have your routes directly generating HTML (not through a "helper"), then you don't really have MVC. Conversely, you can do all of the above in a single file and still have an MVC application. Just put your data-integrity logic in your models (and more importantly, in the database itself), your presentation logic in your views and reusable helpers, and your mapping logic in your controllers.

Serving large static files with Sinatra

I think I can just use send_file (see here) - but if anyone has any other suggestions I have open ears!



Related Topics



Leave a reply



Submit