-
Notifications
You must be signed in to change notification settings - Fork 2
Home
Gin is a small Ruby web framework, built on Rack, which borrows from Sinatra expressiveness, and targets larger applications.
Gin provides a simple command for setting up a new application.
$ gin path/to/appname
A Hello World application requires at minimum a Gin::App instance and a Gin::Controller.
# my_app.rb
class HelloWorld < Gin::Controller
def index; "Hello World"; end
end
class MyApp < Gin::App
mount HelloWorld, "/"
end
# config.ru
require './my_app'
run MyApp.new
Gin tries to fill a need for a framework that's simple and lightweight, yet scalable for large applications. Gin is comparable to Sinatra in performance and memory consumption, but supports multiple controllers and filters.
- Easily mount multiple apps as Rack middleware
- Simple and fast RESTful routing
- Controller classes and action arguments
- Inheritable Before/After filter chains
- Response streaming
- Asset urls with cache-busting and CDN support
- Asset pipelining
- Configs for multiple environments
- Periodic config reloading
- Inheritable error handling
- In-app middleware
- Rack-Session and Rack-Protection
- Dev-mode auto-reloading
- Templates (layouts/views) with Tilt
- Helpers - those are called Modules in Ruby
- ORM - choose your own library
All specs are based on a simple Hello World application running with Puma-2.3.2 (1 worker, max 16 threads) on Ruby 1.9.3, on a 2.8GHz Core i7, OSX 10.7.5.
- Rack lambda app (baseline): 11 alloc/req
- Out of the box (prod): 153 alloc/req
- Out of the box (dev w/ reloading): 741 alloc/req
- With Rack::Session / Rack::Protection (prod): 357 alloc/req
- Sinatra 1.4.3 (prod): 198 alloc/req
- Rails 3.2.13 (prod): 1078 alloc/req
- Rails 4.0.0 (prod): 1179 alloc/req
Memory measured after warming up the app with 15000 requests.
- Rack lambda app (baseline): 42Mb
- Out of the box (prod): 48Mb
- Out of the box (dev w/ reloading): 68Mb
- With Rack::Session / Rack::Protection (prod): 53Mb
- Sinatra 1.4.3 (prod): 51Mb
- Rails 3.2.13 (prod): 110Mb
- Rails 4.0.0 (prod): 82Mb
- Rubinius 2.0.0-rc1 (prod): 70Mb
Benchmarking was run using 16 concurrent connections.
- Rack lambda app (baseline): 3763 req/s
- Out of the box (prod): 2279 req/s
- Out of the box (dev w/ reloading): 547 req/s
- With Rack::Session / Rack::Protection (prod): 1572 req/s
- Sinatra 1.4.3 (prod): 2111 req/s
- Rails 3.2.13 (prod): 622 req/s
- Rails 4.0.0 (prod): 526 req/s
- Rubinius 2.0.0-rc1 (prod): 1532 req/s
The App class is the central location for setting up loading and configuration of your web app. Your app must inherit Gin::App, and load and mount the controllers from that class.
Routes are defined in the application class with the use of the 'mount' method. This method takes for argument a controller class, an optional base path, and an optional block. By default, Gin assigns a route to all actions in a controller following the REST pattern, and uses the controller name for the base path. A block may be given to specify custom routes
class FooApp < Gin::App
require 'foo_app/controllers/home'
require 'foo_app/controllers/user'
# Creates only defined routes, then adds all defaults for remaining actions.
mount HomeController, "/" do
any :home, "/" # Routes all verbs with root path to home
get :login, "/session/new"
get :article, "/news/:id"
defaults
end
# Creates RESTful routes under /user, such as GET /user/:id
mount UserController
end
Gin assigns names to these routes by default (e.g. UserController#show will be named :show_user), but custom names are also supported by passing a second Symbol argument to the routing methods.
class FooApp < Gin::App
require 'foo_app/controllers/home_controller'
mount HomeController, "/" do
get :index, :home
post :login, "/session/new", :create_session
end
end
The App environment name is accessible both from the App instance and class. It defaults to ENV['RACK_ENV'] or 'development' if no RACK_ENV is defined.
environment
environment 'test'
Helper instance methods for checking the environment.
@app.development?
@app.test?
@app.staging?
@app.production?
Gin Apps support their own internal Rack middleware stack, which is empty by default. This stack does not run if no route is found and your app is attached to the main Rack stack as middleware.
You can mount your own middleware to the App's stack with the 'use' class method, similar to Rack's implementation.
use MyMiddleware, *args
Gin also comes with Rack::Cookies::Session and Rack::Protection middlewares built-in but turned off by default. You can enable these with the following class methods.
Set using Rack sessions or not. Defaults to false.
sessions true
sessions :domain => 'foo.com'
Get or set the session secret. Defaults to a new random String on boot.
session_secret
session_secret "SOME LONG SECRET STRING"
Set using Rack::Protection or not. Defaults to false.
protection true
protection :except => [:path_traversal, :session_hijacking]
The error delegate is a controller used for handling non-existing routes and otherwise uncaught errors. It defaults to Gin::Controller but it may be useful to set to your ApplicationController if you have one.
error_delegate
error_delegate AppController
Get or set the current root dir. Defaults to the app script's directory.
root_dir
root_dir "/path/to/new/dir"
Get or set the current public dir. Defaults to root_dir + '/public'
public_dir
public_dir "/path/to/public"
Get or set the current config dir. Defaults to root_dir + '/config'
config_dir
config_dir "/path/to/configs"
Get or set the current layouts dir. Defaults to root_dir + '/layouts'. Wildcards "*" in the path are replaced by the controller name at render time to allow per-controller directories.
layouts_dir
layouts_dir "/path/to/layouts"
Get or set the current views dir. Defaults to root_dir + '/views'. Wildcards "*" in the path are replaced by the controller name at render time to allow per-controller directories.
views_dir
views_dir "/path/to/views"
Get or set the asset host or asset host rules.
asset_host
asset_host "http://assets.example.com"
asset_host do |path|
# do something based on the asset path/name
end
Add a custom mime-type definition.
mime_type :json, "application/json"
Get or set the Host header on responses if not set by the called action. If the enforce options is given, the app will only respond to requests that explicitly match the specified host, or regexp. This is useful for running multiple apps on the same middleware stack that might have conflicting routes, or need explicit per-host routing (such as an admin app).
hostname 'host.com'
hostname 'admin.host.com:443', :enforce => true
hostname 'admin.host.com', :enforce => /^admin\.(localhost|host\.com)/
Gin controllers follow only a few rules:
- ALL instance methods are actions (put your helper methods in a module or parent class not mounted to the app).
- Filters are defined by blocks passed to special class methods.
- Controller-level error handlers are defined by blocks passed to the 'error' class method.
Gin controller actions are any instance method defined on the controller class. The HTTP response body takes the value of the method's return value.
If the instance method takes arguments, matching params will be assigned to them. A required argument with a missing param triggers a Gin::BadRequest error, resulting in a 400 response.
class UserController < Gin::Controller
# Get params id and email
def show id, email=nil
...
end
end
Gin actions also support Ruby 2.0 keyed arguments, which are more flexible for assigning default values when dealing with missing params.
class UserController < Gin::Controller
def show(id, email: nil, full: false)
...
end
end
Views are rendered by calling the 'view' method from a controller instance. Views don't halt the action but return a String of the rendered view. If a layout was set, the rendered view will include the layout.
class UserController < Gin::Controller
def show id
# Renders <app.views_dir>/show_user.* with the app's default layout, if it exists.
view :show_user
end
end
Layouts are defined in the App, under its 'default_layout' class method, but can also be set at the controller level:
class UserController < Gin::Controller
layout :user
def show id
# Renders <app.views_dir>/show_user.* with the layout <app.layouts_dir>/user.*
view :show_user
end
end
Layouts can also be overridden from the view method:
class UserController < Gin::Controller
layout :user
def show id
# Renders <app.views_dir>/show_user.* with the layout <app.layouts_dir>/custom.*
view :show_user, :layout => :custom
end
def tos
# Renders <app.views_dir>/tos.* with no layout
view :tos, :layout => false
end
end
By default, the view is rendered with the controller instance as scope. A scope and locals may also be passed to the view method:
class UserController < Gin::Controller
def show(id, email: nil, full: false)
@user = User.find(id)
view :user, scope: @user, locals: {time: Time.now}
end
end
Generating paths that reference other controllers and actions is handled with the 'path_to' helper method.
path_to UserController, :index
path_to UserController, :show, :id => 123 # Required path params will throw an error if omitted
path_to :show_user, :id => 123 # Named route
# Controller name may be omitted if generating path to the same controller
path_to :show, :id => 123
# You can also use a preset path and append params
path_to '/articles', :page => 2
#=> '/articles?page=2'
Full URLs are generated in the same manner with the 'url_to' method, also aliased as 'to'.
url_to UserController, :show, :id => 123
#=> http://example.com/user/123
Rewriting a request from the controller effectively halts execution of the current controller action and creates a new request to another action and/or controller, or path.
The rewrite method acts just as if a request had been sent from the client, and will re-run any of the in-app middleware. If the app is itself running as middleware, you may use rewrite to pass a request down to the next item in the stack by specifying a path not supported by the app.
rewrite MyController, :action
#=> Calls app with route for MyController#action
rewrite MyController, :show, :id => 123
#=> Calls app with route for MyController#action with the given params
rewrite :show_foo
#=> Calls app with route for the current controller's :show_foo action,
#=> or if missing the controller and action for the :show_foo named route.
# Rewrite and execute the request with the given headers.
rewrite :show_foo, params, 'HTTP_X_CUSTOM_HEADER' => 'foo'
# Rewrite to an arbitrary path.
rewrite '/path/to/something/else', {}, 'REQUEST_METHOD' => 'POST'
Note that params are not forwarded with the rewrite call. The app considers this to be a completely different request, which means all params required must be passed explicitly.
Streamed and IO request content is also ignored unless it is explicitly assigned to the 'rack.input' (as a part of the headers argument).
Unlike Gin::Controller#rewrite, the reroute method forwards the current request and params to the provided controller and/or action, or named route, and halts further execution in the current action.
reroute MyController, :action
#=> Executes MyController#action
reroute MyController, :show, :id => 123
#=> Executes MyController#action with the given params merged to
#=> the current params.
reroute :show_foo
#=> Executes the current controller's :show_foo action, or if missing
#=> the controller and action for the :show_foo named route.
# Reroute with the given headers.
reroute :show_foo, {}, 'HTTP_X_CUSTOM_HEADER' => 'foo'
Asset paths and urls are built by the 'asset_url' helper method. If the file is in the public directory defined by the app class, appends a unique id to the path from the file's md5. If the file is in the assets directory, returns the path to that file. Returns nil if the file is not present.
The app's public directory defaults to root_dir + "/public"
. The app's assets directory defaults to public_dir + "/assets"
.
class MyApp < Gin::App
public_dir "/path/to/my/publicdir/"
end
class NewsController < Gin::Controller
def show id
asset_url "news-#{id}.jpg"
#=> "/news-123.jpg?lk241"
end
end
The app can also define a CDN or CDN rules which will be applied through the 'asset_url'.
class MyApp < Gin::App
asset_host "http://assets.example.com/"
end
class NewsController < Gin::Controller
def show id
asset_url "news-#{id}.jpg"
#=> "http://assets.example.com/news-123.jpg"
end
end
Asset host rules are defined by giving the app's 'asset_host' definition a block.
class MyApp < Gin::App
asset_host do |path|
case path
when /\.(jpe?g|gif|png|bmp|tiff)$/
"http://img.example.com"
when /\.js$/
"http://example.com/js"
end
end
end
class NewsController < Gin::Controller
def show id
asset_url "news-#{id}.jpg"
#=> "http://img.example.com/news-123.jpg"
end
end
As of release 1.2.0, Gin supports asset pipelining with the Sprockets gem. See the Asset Paths section above for generating paths to assets.
The asset pipeline requires no work to setup. At boot time, Gin will look for possible asset source directories (defined by App.asset_paths). If an asset source is found, the assets will be rendered automatically, otherwise the asset pipeline logic will not even be loaded. The asset pipeline behavior is easily controlled through the asset_pipeline App class method.
class MyApp < Gin::App
asset_pipeline true # Force ON
asset_pipeline false # Force OFF
end
Even if the asset_pipeline property is set to false, the Gin app will serve files in the directory assigned to App.assets_dir, which gives you the flexibility to render assets as you see fit. In other words: regardless of how assets were generated, Gin will generate paths to and serve any file under the App's assets_dir.
No doubt you will need to fine tune your pipeline. To this end, Gin yields a Sprockets::Environment instance to the asset_pipeline method.
asset_pipeline do |sprockets|
sprockets.append_path 'foo/assets'
end
The assets_dir where Gin looks for rendered assets defaults to public_dir + "/assets"
but is easily customized:
MyApp.assets_dir "/path/to/custom/dir"
Finally your app needs to know where to get the asset sources from. For this purpose, any number of directory globs may be given to the asset_paths method.
MyApp.asset_paths "/path/to/js_files/*/", "/path/to/css_files/*/"
Although the 'request' is available through an accessor method on the controller, params, sessions, and cookies also have convenient helper and accessor methods.
Params are accessible via the controller's 'params' instance method.
params # Returns indifferent access param Hash
Gin controllers will by default try to identify and cast params to their appropriate type based on their value. The supported types are String, Integer, Float, TrueClass, and FalseClass. However, there are times when exceptions are required and one or more params must remain in their original String type. The Gin::Controller class provides an inheritable attribute 'autocast_params' which allows turning this feature on and off for all or a select number of params.
autocast_params true # enable for all params
autocast_params false # disable for all params
# skip param type-casting for select params
autocast_params :except => [:zip, :phone, :fax]
# only type-cast select params
autocast_params :only => [:age, :timestamp]
These are accessed via the 'session' and 'cookies' helper methods, which return and set the Rack request values. The 'params' accessor includes path params as defined in the routes.
# Session is only available if enabled from the app.
session # Returns the session hash
session[:user] = @user.email # Sets and hashes the user session value
cookies # Returns the cookies hash
set_cookie "mycookie", "value", :expires => Time.now+300
delete_cookie "mycookie"
The session secret is generated automatically by the app class on boot, or can be set from the app class.
class MyApp < Gin::App
session_secret "THIS IS MY LONG SECRET STRING"
end
There are multiple ways to set the response from a controller instance, including accessing the 'response' attribute. The easiest way is to let the action return a String or Rack response Array, but status, headers, and body can be set individually with the helper methods of the same name.
def show id
@news = News.first(id)
if !@news
status 404
headers 'Content-Length' => '9'
body "Not found"
return
end
...
end
Correct uses of response methods:
body # Returns currently set body
body "Some string here"
status # Returns currently set status
status 404
headers # Returns currently set headers
headers 'Header-Name' => 'value'
Using these methods without arguments act as an accessor to see the currently set value.
Streaming is handled by a 'stream' block which yields an IO-like object to write to. The 'stream' method sets the body with a Gin::Stream instance which only starts reading when the method returns.
def download_many
stream do |io|
file = File.open "somefile", "rb"
io << file.read(1024) until file.eof?
file.close
end
end
A streaming connection can be kept open by passing true-ish as an argument to the 'stream' method. Useful for event-driven streaming.
There are a few more response helpers, mostly involving header setting.
content_type # Returns the currently set Content-Type header
content_type :html
content_type 'application/json', :default => :html
last_modified Time.now
cache_control :public, :must_revalidate, :max_age => 60
expires 300, :public, :must_revalidate
expire_cache_control # Assign browser cache-related headers to expire all cache
The controller class defines an inheritable 'content_type' class attribute which is used as a default.
Actions need sometimes be interrupted mid-flow. To handle these use-cases, Gin relies on the Sinatra-like halt, error, and redirect methods. These methods are available from Gin::Controller actions, filters, and error handlers.
class UserController < Gin::Controller
before_filter :logged_in do
redirect "/session/new" unless logged_in?
end
before_filter :admin, :except => [:show, :index] do
error 401, "You are not authorized to view this page" unless app_user.admin?
end
def destroy id
halt 404, "Could not find user" unless @user = User.first(id)
@user.destroy!
"User deleted!"
end
end
Correct uses of request flow methods:
halt 404
halt 404, "body content"
halt 404, {'Content-Type' => 'text/plain'}, "body content"
error 404
error 404, "body content"
redirect "/location/uri"
redirect "/location/uri", 301, "body content"
Gin::Controller defines two more flow-altering methods for specific uses. The 'etag' method which halts the request and returns a 304 for matched If-Modified and If-None-Modified headers, and the 'send_file' method which sends back the contents of the given file path.
class NewsController < AppController
def show id
@news = News.first(id)
etag "news-#{@news.id}-#{@news.revision}" # Halts and returns 304 if already viewed
@news.to_html
end
def download_pdf id
@path = News.pdf_path(id)
send_file @path
end
end
Correct uses of 'etag' and 'send_file' methods:
etag "value"
etag "value", :weak
etag "value", :type => :weak, :new_resource => true
send_file "/path/to/file.pdf"
send_file "/path/to/file", :type => "application/pdf",
:disposition => 'attachment',
:filename => "report.pdf",
:last_modified => Time.now
Gin filters are defined in the controller and behave much like the Rails filters. They can be defined by directly passing a block to 'before_filter' and 'after_filter' class methods, or detached from the filter chain with the 'filter' class method.
Filters are run in the context of the controller instance.
class AppController < Gin::Controller
before_filter :logged_in # more filters and options can be passed as arguments
filter :logged_in do
redirect "/session/new" unless session_user
end
end
# Equivalent, shorter version
class AppController < Gin::Controller
before_filter :logged_in do
redirect "/session/new" unless session_user
end
end
Filters are inherited and can be skipped. All filter chaining methods support the :only and :except keys.
class SessionController < AppController
skip_before_filter :logged_in, :only => :create
end
Error handlers, like filters, are defined as blocks at the controller class level and inherited. They handle both uncaught exceptions and special HTTP response status codes.
class SessionController < AppController
# Handle status 401 and BadLogin errors the same way.
error 401, BadLogin do |err|
status 401
body "You are not authorized to see this!"
end
end
Error handlers support exception inheritance so it's easy to catch a whole class (or all) exceptions.
error do |err|
# Handle all exceptions and status codes that haven't already been caught
end
error Exception do |err|
# Handle all exceptions that haven't already been caught
end
error SocketError do |err|
# SocketErrors get their own handler and won't trigger the Exception handler
end
Because of common cases where code needs to run on all exceptions (like Exception logging), Gin::Controller provides an 'all_errors' method as a callback when exceptions are raised. This method gets run after error handlers are run, and is not triggered by special status codes.
all_errors do |err|
log_exception err
end
Gin implements a per-environment config files loading mechanism. Files must be YAML and in the app's 'config_dir'. Config files are defined per-feature with definitions for defaults, and multiple environments. The 'default' config is used by Gin if there is no match for the current environment name.
# app/config/memcache.yml
default: &default
host: http://memcache.example.com
connections: 5
development: &dev
host: localhost:123321
connections: 1
test: *dev
staging:
host: http://stage-memcache.example.com
production: *default
# app/my_app.rb
MyApp.environment "staging"
MyApp.config['memcache']
#=> {'host' => 'http://stage-memcache.example.com', 'connections' => 5}
A specific value in the config can be accessed through and easy query String. If the query path is invalid, will return nil.
@app.config['memcache.host']
#=> "http://stage-memcache.example.com"
@app.config['memcache.invalid.path.to.key']
#=> nil
Configs can also be setup to auto-reload after a set amount of time. Reloading will only happen if the config attached to a given file is requested after the TTL period has expired.
MyApp.config_reload 300 # seconds
@app = MyApp.new
@app.config['memcache.connections']
#=> 5
# app/config/memcache.yml gets updated so connections are increased
sleep 300
@app.config['memcache.connections']
#=> 10
Testing Gin applications is easy. The gin/test file provides helper and assertion methods for testing anything from views rendered to specific data points returned by an API-type endpoint.
Requiring gin/test will create a TestHelper module for each Gin::App subclass. These modules should be included in a Test::Unit::TestCase class.
require 'gin/test'
class TestMyApp < Test::Unit::TestCase
include MyApp::TestHelper
def test_show_user
get "/user/123.json"
assert_response :success
assert_data "/user/firstname", "Bob"
end
end
Request helper methods can also be targeted to a specific controller, making segregating functional tests easy.
require 'gin/test'
class TestMyApp < Test::Unit::TestCase
include MyApp::TestHelper
controller MyApp::UserController
def test_show_user
get :show, id: 123, format: 'json'
assert_response :success
assert_data "/user/firstname", "Bob"
end
end
For sanity reasons, not everything is reloaded when autoreload is enabled. Gin will only know to reload files and constants if the originating require was made from the Gin::App definition. Gin::App defines its own 'require' method in development mode, which keeps track of which files and constants get loaded at require time.
# This uses Object#require and will not get reloaded
require 'json'
class MyApp < Gin::App
# This file and all its dependencies will get reloaded automatically
require 'my_app/controllers/user'
end
Auto-reload may be disabled from the class by passing false to the 'autoreload' method.
class MyApp < Gin::App
autoreload false
end