By default, each class will serve the path built from its underscored name.
Ex.: Forum will serve "/forum", LatestNews will serve "/latest_news" etc.
This can be changed by setting base URL via map.
Example: - Book app should serve "/books"
class Book < E
map '/books'
# ...
end[ contents ↑ ]
Lets say you need your News app to serve both "/news" and "/headlines" base URLs.
It is easily done by using map with multiple params.
First param will be treated as base URL, any other consequent params - as canonical ones.
Example: - News should serve both "/news" and "/headlines" paths.
class News < E
map :news, :headlines
def index
# ...
end
endTo find out either current URL is a canonical URL use canonical?
It will return nil for base URLs and a string for canonial ones.
Example:
class App < E
map '/', '/cms'
def page
# on /page canonical? == nil
# on /cms/page canonical? == "/page"
end
# ...
end[ contents ↑ ]
Defining Espresso actions is as simple as defining Ruby methods,
cause Espresso actions actually are pure Ruby methods.
Example: - Defining 2 actions - :index and :edit
class App < E
map '/'
def index
# ...
end
def edit
# ...
end
end
# Now `App` will serve:
# - / # backed by `:index` action
# - /edit # backed by `:edit` action[ contents ↑ ]
Usually actions should also contain non-alphanumeric chars.
Most common - hyphens, dots and slashes.
To address this, Espresso uses a map to translate action names into HTTP paths.
The default map looks like this:
"__" => "/" "___" => "-" "____" => "."
Example:
def users__online # 2 underscores
# ...
end
# will serve users/online
def latest___news # 3 underscores
# ...
end
# will serve latest-news
def read____html # 4 underscores
# ...
end
# will serve read.htmlYou can define your own rules by using path_rule at class level.
Example: - Convert bang methods into .html suffixed paths
class App < E
map '/'
path_rule "!", ".html"
def news!
# ...
end
end
# :news! action will serve /news.html pathExample: - Convert methods ending in "_j" into .json suffixed paths
class App < E
map '/'
path_rule /_j$/, ".json"
def news_j
# ...
end
end
# :news_j action will serve /news.json path[ contents ↑ ]
Though path rules are useful enough, you can bypass them and set routes directly.
Example: make bar method to serve /bar, /some/url and /some/another/url
def bar
# ...
end
alias_action 'some/url', :bar
alias_action 'some/another/url', :barExample: make foo method to serve /foo and /some/url via any request method
def foo
# ...
end
alias_action 'some/url', :fooExample: get_foo method will serve /foo and /some/url only via GET request method
def get_foo
# ...
end
alias_action 'some/url', :get_fooAlso standard Ruby alias can be used:
class App < E
map '/'
def news
# ...
end
alias news____html news
alias headlines__recent____html news
endNow news action will serve any of:
- /news
- /news.html
- /headlines/recent.html
NOTE: Private and protected methods usually are not publicly available via HTTP.
However, if you add an action alias to such a method, it becomes public via its alias.
To alias a private/protected method and keep it private,
use standard ruby alias or alias_method rather than alias_action.
[ contents ↑ ]
Sometimes you need some actions to perform on multiple controllers.
To avoid repetitive operations, just put that actions into a module and import them.
Yes, import, not include, cause include are used to share helpers and included methods wont be treated as actions.
module MySharedActions
def foo
# ...
end
def bar :with, :some, args: 'etc.'
# ...
end
end
class App < E
map '/'
import MySharedActions
# ...
endApp will now respond to both /foo and /bar URL's
Please note that import will include all methods, however only public ones will be treated as actions. That's it, protected/private methods are not treated as actions.
Also, import wont import any setups, just actions.
See Remote Setup if you need to share setups between controllers.
[ contents ↑ ]
Espresso will split URL by slashes and feed obtained array to the Ruby method that backing current action.
Let's suppose we have an action like this:
class App < E
map '/'
def read type, status
# ...
end
endIf we do a request like this - "/read/news/latest", it will be decomposed as follow:
- action - read
- params - news/latest
Now Espresso will split params and call action:
read "news", "latest"Current example will work just well, cause read receives as many arguments as expected.
Now let's suppose we do an request like: "/read/news"
This wont work, cause read receives 1 argument instead of 2 expected.
read "news"And "/read/news/articles/latest" wont work either, cause read receives too many arguments.
read "news", "articles", "latest"However, as we know, Ruby is powerful enough.
And Espresso uses this power in full.
So, when we need read method to accept 1 or 2 args,
we simply give the last param a default value:
class App < E
map '/'
def read type, status = 'latest'
# ...
end
endNow read action will serve "/read/news" as well as
"/read/news/latest", "/read/news/archived", "/read/news/anything!"
Also we can make "/read/news/articles/latest" to work.
class App < E
map '/'
def read *types, status
# ...
end
endThat's it! Now when calling "/read/news/articles/latest",
types will be an array like ["news", "articles"] and status will be equal to "latest".
In a word, if Ruby method works with given params, HTTP action will work too.
Otherwise, HTTP action will return "404 NotFound" error.
[ contents ↑ ]
format allow to manipulate routing by instructing actions to respond to various extensions.
Also it is aimed to automatically set Content-Type header based on used extension.
Example:
class App < E
map '/'
format '.xml'
def article
# ...
end
endIn the example above, article action will respond to both "/article" and "/article.xml" URLs.
format accepts any number of extensions.
The second meaning of format is to automatically set Content-Type header.
Content type are extracted from Rack::Mime::MIME_TYPES map.
Ex: format '.txt' will return the content type extracted via Rack::Mime::MIME_TYPES.fetch('.txt')
Worth to note that format will act on all actions.
To set format(s) only for specific actions, use format_for.
Example: - only pages action will respond to URLs ending in .html and .xml
class App < E
map '/'
format_for :pages, '.xml', '.html'
def pages
# ...
end
def news
# ...
end
# ...
endNow App will respond to any of "/pages", "/pages.html", "/pages.xml" and "/news" but not "/news.html" nor "/news.xml", cause format was set for pages action only.
It is also possible to disable format for specific actions by using disable_format_for:
class App < E
map '/'
format '.xml' # this will enable .xml format for all actions
disable_format_for :news, :pages # disabling format for :pages and :news actions
# ...
endWorth to note that Espresso will get rid of extension passed with last param,
so you get clean params without manually remove format.
Meant that when "/news/100.html" requested, you get "100" param inside news action, rather than "100.html"
Example:
class App < E
format '.xml'
def read item = nil
# on /read item == nil
# on /read.xml item == nil
# on /read.xml/book 404 NotFound
# on /read/book item == "book"
# on /read/book.xml item == "book"
# on /read/100.xml item == "100"
# on /read/blah.xml item == "blah"
# on /read/blah.json item == "blah.json"
end
end[ contents ↑ ]
By default, verbless actions will respond to any request method.
Example: - index action responding to any request method
class App < E
def index
end
endTo make an action to respond only to a specific request method, simply prepend desired request method verb to action name.
Example:
class App < E
def post_news # will serve POST /news
# ...
end
def put_news # will serve PUT /news
# ...
end
# etc.
endIMPORTANT: verbified actions has priority over verbless ones, regardless definition order!
That's it, if you have news and post_news actions, on POST requests post_news will be executed:
class App < E
def post_news # will serve POST requests
# ...
end
def news # will serve any requests except POST ones
# ...
end
end[ contents ↑ ]
By default Espresso will respond only to requests originating on the host application is running on.
To make it listen on multiple hosts, pass originating hosts via controller's map method or via mount method when controllers are mounted.
Let's suppose your application are listening on site.com.
Example: make App controller to listen also on site.org:
class App < E
map '/', host: 'site.org'
# or just
map host: 'site.org'
# ...
endExample: make App controller to listen also on site.org and site.net:
class App < E
map '/', hosts: ['site.org', 'site.net']
# or just
map hosts: ['site.org', 'site.net']
# ...
endExample: make Forum slice to listen on site.org beside default site.com:
module Forum
class Posts < E
# ...
end
class Users < E
# ...
end
end
E.new do
mount Forum, host: 'site.org'
run
endExample: make Forum slice to listen on site.org and site.net beside default site.com:
E.new do
mount Forum, hosts: ['site.org', 'site.net']
run
endHosts can also be specified at app level and will apply to all controllers:
E.new do
map '/', host: 'site.org'
# or
map host: 'site.org'
# or
map hosts: ['site.org', 'site.net']
mount Forum
mount Blog
run
end[ contents ↑ ]
Espresso uses a really flexible rewrite engine that allow to redirect the browser to new address as well as pass control to next matching route or to an arbitrary controller(without redirect) or just send a custom response to browser(without redirect as well).
A rewrite rule consist of a regular expression and a block that receives matches via arguments.
redirect and permanent_redirect will redirect browser to new address with 302 and 301 codes respectively.
Example:
app = E.new do
rewrite /\A\/(.*)\.php\Z/ do |title|
redirect Cms.route(:index, title)
end
# ...
endpass will pass control to an arbitrary controller, without redirect.
Example:
class Articles < E
def read title
# ...
end
end
class Pages < E
def archive title
# ...
end
end
app = E.new do
# pass old pages to archive action
rewrite /\A\/(.*)\.php\Z/ do |title|
pass Pages, :archive, title
end
# pages ending in html are in fact articles, so passing control to Articles controller
rewrite /\A\/(.*)\.html\Z/ do |title|
pass Articles, :read, title
end
endImportant: if pass called without arguments it will pass control to next matching rule/route:
class Pages < E
rewrite /\A\/+(.*)/ do |path| # matching pretty anything
if matched = Redirects.where(source: path).first
redirect matched.target
end
pass # no redirects matched, moving to next matching route
end
# actions
endenv['espresso.gateways'] will display the list of tried routes before matched one.
halt will send response to browser and stop any code execution, without redirect.
It accepts from 0 to 3 arguments.
If argument is a hash, it is added to headers.
If argument is a Integer, it is treated as Status-Code.
Any other arguments are treated as body.
If a single argument given and it is an Array, it is treated as a bare Rack response and instantly sent to browser.
Example:
app = E.new do
rewrite /\A\/archived\/(.*)\.html\Z/ do |title|
unless page = Model::Page.first(:url => title)
halt 404, 'page not found'
end
halt page.content, 'Last-Modified' => page.last_modified.to_rfc2822
end
end[ contents ↑ ]