What do you use Sinatra for?
Sinatra is not Rails. It is a micro-framework used for simple websites where you may need to just define a few actions. You can make a Sinatra application as complex as you want to, but you'll hit a point where you code has become a crazy mess sooner than with Rails.
While not 100% accurate, Sinatra fits mostly into the Page Controller architectural pattern, and Rails is a clear MVC implementation.
To answer your questions specifically:
- It is not intended to replace Rails
- It can run side by side
- You could create a twitter clone in Sinatra
Benefits of Sinatra for a web service like controller
The simplest useful Ruby web application you could build would be a Rack application. Sinatra is a lightweight DSL which sits on top of Rack to make coding the controllers and views more convenient. You can build more sophisticated applications by including more add-ons like ActiveRecord
or Rack::Oauth
, etc
Rails 2 is a more feature rich framework which includes a plethora of additional features already in the framework. Some applications don't need all that so some developers prefer things like Sinatra which is minimal.
However the distinction between Rails and Sinatra has blurred quite a bit since Rails 3. The new version allows everything from just Rack to full Rails in the stack to you can tailor it to suit your needs. The raison d'etre for some of the intermediate frameworks such as Sinatra is weaker than it used to be.
So take a look at Rails 3, start minimally and grow to suit your needs.
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
Use Sinatra to serve php files
You could use rack-legacy, which allows Sinatra to serve PHP files. It simply uses php-cgi
to run the scripts. For example, put phpMyAdmin under directory admin
and put something along these lines to config.ru
:
require 'app'
map "/admin" do
use Rack::Legacy::Php, 'admin'
use Rack::Static, :urls => ['/'], :root => 'admin'
run lambda{|env| [200, {'Content-type' => 'text/plain'}, 'OK']}
end
map "/" do
run Sinatra::Application
end
(If you're not familiar with using config.ru
with your Sinatra app, see this part of Sinatra docs).
I'd suggest to configure Apache instead if possible. It strikes me as a cleaner solution and it would be also more efficient, but that's probably not a problem if you're only using it for phpMyAdmin.
Sinatra, where to place the require statments
I recommend:
- Require your main app file only from your
config.ru
. - Require Sinatra and views gems in your main app
- Create individual
init.rb
files for each of your helpers, models, and routes, and require those in your main app. - Require DB-related gems in models/init.rb
Here's an example of the layout I use:
Using Sinatra for larger projects via multiple files
Note that by loading DB-related gems and setting up your DB in your models/init.rb you can (from IRB) load just that file and have your full model stack available for poking at.
How do I use Sinatra and Sequel to get an array from a checkbox HTML form?
You don't actually need Sequel to receive an item from a post
request. Sequel is a database, so you would save the returned item to the database after receiving it from your post
. So, you don't actually need it in order to return an array in this case.
Also, forms and checkboxes are not specific to Sinatra but are specific to HTML.
When you submit the form as a post
request, it is sent back to the server and Sinatra where it can be handled. Sinatra provides a params
method which contains a hash of the values of the form that was submitted.
In the example below, I use a special syntax (item[]
) to provide the values named item
as an array within the params hash. For example, this is what the params hash returns {"item"=>["Bacon", "Eggs (dozen)"]}
with both checkboxes checked.
require 'sinatra'
get '/results' do
erb :results
end
post '/results' do
params[:item].inspect
end
__END__
@@ results
<form action="/results" method="post" name="checkbox_form">
<input type="checkbox" name="item[]" value="Bacon">Bacon (1 lb., sliced)<br/>
<input type="checkbox" name="item[]" value="Eggs (dozen)">Eggs (1 dozen)<br/>
<input type="submit" value="Calculate"/>
</form>
The get
block receives a get request from a user's browser and handles it. In this example, it returns an HTML form to the user.
The post
block receives a post request from a user's browser after they have submitted the form. The user is shown the response from the server which in this case is an array of the checkbox values.
Architecture for a modular, component-based Sinatra Application
This is similar to include's proposal, but it doesn't require access to the rackup file.
Write your various Handlers like:
class FoodHandler < Sinatra::Base
get '/chunky/:food' do
"Chunky #{params[:food]}!"
end
end
Then in your main application file:
require './lib/handlers/food_handler.rb'
class Main < Sinatra::Base
enable :sessions
... bla bla bla
use FoodHandler
end
I've used this kind of structure to build some fairly complex Sinatra apps. It scales just as well as Rails.
EDIT
To have your config file define the routes, you could do something like this:
class PlacesHandler < Sinatra::Base
# Given your example, this would define 'places/paris' and 'places/losangeles'
CONFIG['components'].select { |c| c['compontent_type'] == 'Mapper' }.each do |c|
get c['route'] do
@latitude = c['component_settings']['latitude']
@longitude = c['component_settings']['longitude']
end
end
end
How can I use a side sinatra app when deploying?
Here is an article from Thoughtbot, which shows two approaches on how to use Sinatra application inside your Rails app.
The simpler method is to use Rails router:
require 'my_sinatra_app'
MyRailsApp::Application.routes.draw do
mount MySinatraApp.new => '/sinatra'
end
It's a preferable approach unless you need a custom middleware for each app.
Related Topics
Good Resources to Learn MACruby
Accessing Headers from Sinatra
Ruby/Rails Collection to Collection
What the Purpose of Bind/Unbind Methods in Ruby
Working with a Large Data Object Between Ruby Processes
Ruby Metaprogramming, How Does Rspec's 'Should' Work
How to Best Create a Random Float in a Range Between Two Floats
Failing to Bundle Install Tiny_Tds on MAC Os X 10.8 with Homebrew Freetds
Generate a 'Link_To' to the Controller Action 'Edit', Dynamically
Sass: Dealing with The Ie 4095 Selectors Per Stylesheet Restriction
Ruby Block and Returning Something from Block
Rails Include Only Selected Columns from Relation