-
Notifications
You must be signed in to change notification settings - Fork 7
Manual
Here is an industry-level, production-ready hello world:
#! /usr/bin/env ruby
require 'nyara'
configure do
set :port, 3000
set :workers, 4
set :env, 'production'
end
get '/' do
send_string 'hello world!'
endThen make it executable and run.
When you need to start with a bigger application, you can use the nyara command to initiate the project with a bit conventions.
For example, you want to create a project named fancyblog:
$ nyara new fancyblogThere are several options for nyara new
$ nyara new fancyblog -t slim # use slim as template engine
$ nyara new fancyblog -o activerecord # use AR instead of mongo
$ nyara new fancyblog -o none # don't add any orm yetTo see all options:
$ nyara help newThe generated project uses bundler to manage gem dependencies, you can edit Gemfile to make sure desired gems are included, then
$ cd fancyblog
$ bundle # install all gems, and generates Gemfile.lockIt is recommended that you also add Gemfile.lock into your version control system.
The project structure looks like rails very much:
app/
assets/
files/ # files to copy into `public` dir
scripts/ # js sources
styles/ # css sources
controllers/
models/
views/
config/
application.rb # generic environment and config
boot.rb # starts server
production.rb # environment for production
development.rb # environment for development
test.rb # environment for test
database.yml # database connection config
session.key # session signing key, you should not show it to public
spec/
spec_helper.rb
unit/
integration/
Rakefile # generates some default tasks
Gemfile # bundler
Linnerfile # config assets compiling, generates `public` dir
.gitignoreThe generated project follows an MVC convention, in which:
- M -- the model, usually are classes that maps into db tables or document groups
- C -- the controller, which defines routing and wires models and views up
- V -- the view (templates)
Before starting server, you should:
- make sure the database server is online.
- check database configs in
config/database.ymlthat it uses correct driver and connects to the right db. - if you are using activerecord,
rake db:createandrake db:migrate(there's also a convenientnyara g migration YOUR_MIGTATION_NAME).
$ nyara server
$ nyara server -e production # start with production environment
$ nyara server -p 8083 # listen to 8083 instead
$ nyara server -d # start daemon server in the background
$ nyara help server # see available optionsTo start an interactive shell (if you added pry in Gemfile, it will try use pry instead of irb):
$ nyara console
$ nyara help console # see availble optionsTo force an irb shell
$ nyara console -s irbAll commands can be abbreviated if there's no conflicts:
$ nyara s # start server
$ nyara c # start consoleFor instant refresh after editing a sass / coffee file, you can install this Chrome plugin:
https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei?hl=en
You may need to generate some keys to secure your cookies. See also Header, param, session and cookie.
$ nyara g session.key
$ nyara g session_cipher.key$ rake -T # show all tasks$ rake routes # display all routes$ rake assets:build # build assets into public
$ rake assets:clean # clean publictodo db tasks?
The global method configure evals the block inside Nyara::Config.
Options can be retrieved with get/self[], and can be changed with set/self[]=.
There are options that, are used specially for the app. Here's the list:
configure do
set :env, 'test' # set environment
# in "development" env reloader is enabled
# in "test" env autorun is disabled
# in "production" env workers are enabled
set :port n # set listening port number
set :workers n # set number of worker process
# if not set, will estimate best n by CPU count
set :host, 'some.com' # host name used in `url_to` helper
set :root, __dir__ # set app root dir, can affect other dir settings
# default is `Dir.pwd`
set :views, 'templates' # set view dir, relative to root
# default is `views`
set :public, 'static' # set public dir, relative to root
# if not set, nyara will not serve static files
set :x_send_file, "X-Accel-Redirect"
# for accelerating send_file with nginx/apache
# see also the "Response helpers" section
set :logger, true # equivalent to no setting
set :logger, false # do not log
set :logger, lambda{ MyLogger.new }
set :logger, MyLogger # logger will be created with:
# `MyLogger.new("production.log")`
# or `MyLogger.new(STDOUT)`
set :before_fork, lambda{ ... set some fd to close_on_exec etc ... }
set :after_fork, lambda{ ... re-connect db etc ... }
set :watch, true # watch app and view change, and auto reload
set :watch_assets, true # watch assets change, requires linner
set :timeout, 120 # request lives about 120 seconds if inactive
endSession options
# config (cookie) session name
set :session, :name, 'JSESSIONID'
# session private RSA key, used in DSA signing
set :session, :key, File.binread('some.pem')
# if set, ciphers the session content with AES
set :session, :cipher_key, 'a cipher key'
# always use secure, that requires your whole site run on https
set :session, :secure, true
# expires after 30 * 60 minutes
# usually no need to set this, default session expires at browser close
set :session, :expires, 30 * 60To retrieve options, you can visit Nyara.config. For example:
Nyara.config[:port]
Nyara.config[:root]There are also shortcuts to environment options:
Nyara.config.env
Nyara.config.development?
Nyara.config.test?
Nyara.config.production?To reset options
Nyara.config.resetFor convenience getting absolute file and directory paths
# strict mode, only files under project_path is returned, otherwise nil
Nyara.config.project_path relative_path
# second arg=false means non-strict mode
Nyara.config.project_path relative_path, false
Nyara.config.views_path relative_path
Nyara.config.public_path relative_path
Nyara.config.path_under 'public', relative_path, trueTo use logger
Nyara.config.loggerFor less typing, the following methods retrieving config information are delegated in Nyara
Nyara.env
Nyara.production?
Nyara.test?
Nyara.development?
Nyara.project_path
Nyara.views_path
Nyara.public_path
Nyara.path_under
Nyara.loggerYou can group actions into many controllers, for example
# requirement: the class name should end with "Controller"
class MyController < Nyara::Controller
get '/' do
send_string "hello my controller"
end
endA controller will not response without the mapping configure
configure do
# MyController may not be defined yet, so use a string identifier
map "/my", "MyController"
endYou can also map a controller under a module or class
configure do
map "/my", "FooBar::MyController"
end
module FooBar
class MyController < Nyara::Controller
...
end
endSetting controller name can effect Routing and path helpers.
class MyController < Nyara::Controller
set_controller_name 'your'
endSetting default layout can effect Render and layout.
class MyController < Nyara::Controller
set_default_layout 'layout'
endExample of nesting layouts by inheritance:
class OuterController < Nyara::Controller
set_default_layout 'outer_layout'
end
class InnerController < OuterController
set_default_layout ['inner_layout', OuterController.default_layout]
endIn Nyara the following http methods are supported:
get
post
put
delete
patch
optionsIf you want other http methods, you can use the http helper
# map a "POKE" method (I think it will be defined in HTTP 10.0)
http :poke, '/a/path' do
...
endDuring request handling, HTTP methods other than get and post are identified by:
- First word of the request, as stated in http protocol.
- For non-standard browsers,
postis used, and a_methodparam in query specifies the overridden method.
* NOTE that the _method param in request body won't take effect in routing. Because in Nyara routing is finished when header completes.
The path format is very similar to scanf/printf, for example:
get '/users/%u/edit' do |id|
@user = User.where(id: id).first
render "users/edit"
endThe %u in the above code specifies the id argument in the path. It can save you some work because paths like /users/foo/edit are rejected because "foo" is not an unsigned integer, and you get the argument id in type of Integer.
Available argument formats are:
-
%dsigned integer -
%uunsigned integer, useful for ids -
%xhex digits -
%ffloating point number -
%sstring containing not '/' -
%zstring to the end
Actions with an id (which starts with "#") can be associated with path helpers. For example, if you defined an action with
class FooController < Nyara::Controller
meta "#index"
get "/" do
...
end
endYou can get the path with
path_to "#index" # only available under FooControlleror
path_to "foo#index" # available in every controllersThe action identifier is in the form of CONTROLLER_NAME#ACTION_ID. By default, controller name is computed by snake cased class name minus the Controller suffix. It can be custom with set_controller_name
class FooController < Nyara::Controller
set_controller_name 'bar'
...
endThen visiting path helper for #index becomes:
path_to "bar#index"Path helper can receive and validate arguments. For example, you have an action demanding floating point number arguments:
meta "#coordinate"
get '/coordinate/%f,%f' do |x, y|
...
endTo generate path for the action
x = 1.1
y = 2.1
path_to "#coordinate", x, y #=> "/coordinate/1.1,2.1"If the number or type of arguments doesn't match, an error will be raised
path_to "#coordinate", 'foo', 2.1 # type mismatch, error
path_to "#coordinate", 1.1 # arity mismatch, errorThe url_to helper is similar to path_to, but adds scheme, domain and host. For example
url_to "#coordinate", 0, 0 #=> "//coordinate/0.0,0.0"* NOTE In the above example, starting with double slash makes the url flexible. The scheme of the page containing the url will be used.
More examples
url_to "#index", scheme: 'http', format: 'js'
#=> http://localhost:3000/.js
url_to '#index', host: '1.2.3.4:3000', 'q' => 'nyara'
#=> "//1.2.3.4:3000/?q=nyaraNyara can scan request and choose the most desirable content format. To limit content formats the action responds to, use meta before defining an action:
meta formats: %w[js jpg jpeg]
get '/data' do
case format
when 'js'
...
when 'jpg', 'jpeg'
...
end
endIn the above code, the format helper returns the matched content format, which is determined by either:
- Request path extension. For example, a path of
/data.jssets format to'js'. -
Acceptfield in request header. For example, a header ofAccept: image/jpegsets format to'jpg'.
* NOTE format helper returns String, not Symbol.
header, param, session and cookie are hashes with indifferent access, robust to String or Symbol keys.
* NOTE that only mutating session can affect response.
To change response header and cookie, use the following helpers
set_header field, value # set response header
add_header_line # add a response header line,
# useful when you need to repeat same header fields
set_cookie name, value=nil, opts={}
delete_cookie name
clear_cookieYou can use flash to pass some information to next action. For example
post '/' do
flash[:info] = 'Welcome'
redirect '/'
end
get '/' do
render erb: "<%= flash['info'] %>"
endYou may come into cases that, a template used in different actions visiting flash[some_key], but the information may come from current action or previous action, so there's flash.now is to specify information only retrievable in current action:
flash.now[:info] = 'Welcome'Flash is swept as soon as a request is handled, and +flash.next+ will be frozen after header is sent.
Methods
request.http_method # "GET" or "POST" or "PUT", ... etc
request.scheme # "http" or "https", ... etc
request.ssl? # https request
request.path # path without query
request.query # query params after path (Hash)
request.path_with_query # path+query (String)
request.scope # the mapped scope in configure
request.domain
request.port
request.host_with_port # domain with port
request.xhr? # whether is ajax
accept # computed `Accept` MIME types array
# with respect to order and q=...
accept_language # computed `Accept-Language` array
accept_encoding # computed `Accept-Encoding` array
form? # request content type is url-encoded or multipart
header
body # stream
param
cookie
session
flashstatus 404 # set response status
content_type 'html' # set response content type
send_header # send response header, and freeze it
send_data # send raw data, not wrapped in chunked encoding
send_chunk(string) # use this instead of `send_data` unless
# you know what you are doing
send_string(string) # same as `send_chunk`
send_file # send file, and terminate the action
halt # terminate action
redirect '/' # redirect to '/', terminating the action
redirect_to '#index'There are also render helpers (NOTE that render also terminates action, code after render is not executed)
# render a template
# template path relative to Nyara.config[:views]
# engine determined by file extension
render 'user/index', locals: {foo: 'bar'}
# render with inline template source
# content type is set to +text/html+ if not given
render erb: "<%= 1 + 1 %>"In the above example, if there are more than one templates matching "user/index", say, "user/index.slim" and "user/index.erb" for example, then an error is raised. You can add extension name to make it definitive:
render 'user/index.slim', locals: {foo: 'bar'}Render can accept a second argument: the layout(s). If controller has set the default layout, render will use it(them), or you can use other layout(s):
# render with layout
render 'user/index', 'layout'
# render with layouts
render 'user/index', layout: ['layouts/inner', 'layouts/outer']Current implementation is not very beautiful, they have some Caveats that may lead to unexpected behavior:
- All
<% yield %>,<%= yield %>are transformed into<%== yield >. - The first time a template is rendered, all the local variables are fixed.
You can also use partial helper, it doesn't terminate the action, neither sends data. It only renders the template into a string. Examples:
foo = partial 'user/_some_partial'
render erb: "<%= partial 'another_partial', locals: {foo: 'bar'} %>"You can use stream instead of render, which returns a resumable Nyara::View object.
Every call to resume executes the template to next Fiber.yield, and sends out the corresponding data.
view = stream erb: "<% 5.times do |i| %>i<% Fiber.yield %><% end %>"
view.resume # sends "0"
view.resume # sends "1"
view.resume # sends "2"
view.end # sends "34" and closes connectionsleep 3.4 # put current fiber into sleep for 3.4 seconds
# while not blocking other clients
handle_error # the default error handlerYou may need to add your own error handler. Here's an example
def handle_error e
case e
when ActiveRecord::RecordNotFound
# if we are lucky that header has not been sent yet
# we can manage to change response status
status 404
send_header rescue nil
else
super
end
endJust define helpers in a module, and include in your controller. For example
module MyHelpers
def foo
'bar'
end
end
class MyController < Nyara::Controller
include MyHelpers
endYou can use prepend if you want to overwrite some built-in helpers:
module MyRedirectOverWrite
def redirect path
# ... do my super awesome redirect!
end
end
class MyController < Nyara::Controller
prepend MyRedirectOverWrite
endBe careful with prepend: most helpers can be safely overwritten, but you should be careful not to re-define request or send_header.
You can use before to add filter / interceptor / decorater to actions. The call seq is
before <selector> do
...
endIt is very similar to CSS selectors, here are a few examples:
-
":get"(verb match with pseudo class) match all actions withgetmethod -
"#index"(id match) match the action defined withmeta "#index" -
".auth"(class match) match all actions defined withmeta ".auth" -
".auth:get"(class + verb match) match all actions defined withmeta ".auth"andgetmethod
You can put multiple selectors at once, and it is equivalent to calling before with each individual selectors (union):
before ".auth:post", ".auth:patch" do
...
endAssume you have an application like this:
require 'nyara/nyara'
configure do
map '/', 'App'
end
class App < Nyara::Controller
get '/' do
send_string 'hello world'
end
meta '#r'
get '/r' do
redirect '/'
end
end
if $0 == __FILE__
Nyara.setup
Nyara.start_server
endYou can write test with request / response helpers
require './app.rb'
require 'test/unit'
require 'nyara/test'
class MyTest < Test::Unit::TestCase
include Nyara::Test
def test_index
get '/'
assert response.success?
end
def test_r
get 'my#r'
follow_redirect
assert_equal 'hello world', response.body
end
endTest helper methods in Nyara::Test
-
get/post/put/delete/patch/options path, header={}, body_string_or_hash=''initiate a request -
http method, path, header, bodyinitiate a request, you can use any other methods -
path_to id, *argspath helper just like in a controller, but the controller name must be specified -
cookieobject representing client cookie, you can change it and effect the next request -
sessionyou can modify session and it will effect the next request -
requestlast request object -
responselast response object response.headerresponse.bodyresponse.statusresponse.success?-
redirect_locationlast redirect location -
follow_redirectfollow redirect and initiate another request -
envthe environment object -
env.controllerlast controller object which had processed the request, you can use it for inspecting path_helpers or instance variables... etc
It's OK to initiate any number of requests in one test.
- Getting started with a single file
- Getting started with a project structure
- Configure options
- Modulize actions with controller
- Routing and path helper
- Action metadata and content format
- Header, param, session and cookie
- Flash
- The request object
- Response helpers
- Render, layout and partial
- View streaming
- Other helpers
- Custom helpers
- Action lifecycle callbacks
- Test helper
Recipes(todo)
- [Web server]
- [Secure headers]
- [Mapping different paths to a same controller]
- [I18n]
- [Form helpers]
- [Oauth]
- [Test]
-- Digging deeper --