= New Plugins * A csrf plugin has been added for CSRF prevention, using Rack::Csrf. It also adds helper methods for views such as csrf_tag. * A symbol_matchers plugin has been added, for customizing the regexps used per symbol. This also affects the use of embedded colons in strings. This supports the following symbol regexps by default: :d :: (\d+), a decimal segment :format :: (?:\.(\w+))?, an optional format/extension :opt :: (?:\/([^\/]+))?, an optional segment :optd :: (?:\/(\d+))?, an optional decimal segment :rest :: (.*), all remaining characters, if any :w :: (\w+), a alphanumeric segment This allows you to write code such as: plugin :symbol_matchers route do |r| r.is "track/:d" do end end And have it only match routes such as /track/123, not /track/abc. Note that :opt, :optd, and :format are only going to make sense when used as embedded colons in strings, due to how segment matching works. You can add your own symbol matchers using the symbol_matcher class method: plugin :symbol_matchers symbol_matcher :slug, /([\w-]+)/ route do |r| r.on :slug do end end * A symbol_views plugin has been added, which allows match blocks to return symbols, which are interpreted as template names: plugin :symbol_views route do |r| :template_name # same as view :template_name end * A json plugin has been added, which allows match blocks to return arrays or hashes, and uses a JSON version of them as the response body: plugin :json route do |r| {'a'=>[1,2,3]} # response: {"a":[1,2,3]} end This also sets the Content-Type of the response to application/json. To convert additional object types to JSON, you can modify json_response_classes: plugin :json json_response_classes << Sequel::Model * A view_subdirs plugin has been added for setting a default subdirectory to use for views: Roda.route do |r| r.on "admin" do set_view_subdir "admin" r.is do view "index" # uses admin/index view end end end * A render_each plugin has been added, for rendering the same template for multiple objects, and returning the concatenation of all of the output: <%= render_each([1,2,3], 'number') %> This renders the number template 3 times. Each time the template is rendered, a local variable named number will be present with the current entry in the enumerable. You can control the name of the local variable using the :local option: <%= render_each([1,2,3], 'number', :local=>:n) %> * A content_for plugin has been added, for storing content in one template and retrieving that content in a different template (such as the layout). To set content, you call content_for with a block: <% content_for :foo do %> content for foo <% end %> To retrieve content, you call content_for without a block: <%= content_for :foo %> This plugin probably only works when using erb templates. * A not_allowed plugin has been added, for automatically returning 405 Method Not Allowed responses when a route is handled for a different request method than the one used. For this routing tree: plugin :not_allowed route do |r| r.get "foo" do end end If you submit a POST /foo request, it will return a 405 error instead of a 404 error. This also handles cases when multiple methods are supported for a single path, so for this routing tree: route do |r| r.is "foo" do r.get do end r.post do end end end If you submit a DELETE /foo request, it will return a 405 error instead of a 404 error. * A head plugin has been added, automatically handling HEAD requests the same as GET requests, except returning an empty body. So for this routing tree: plugin :head route do |r| r.get "foo" do end end A request for HEAD /foo will return a 200 result instead of a 404 error. * A backtracking_array plugin has been added, which makes matching backtrack to the next entry in an array if a later matcher fails. For example, the following code does not match /foo/bar by default in Roda: r.is ['foo', 'foo/bar'] do end This is because the 'foo' entry in the array matches, so the array matches. However, after the array is matched, the terminal matcher added by r.is fails to match. That causes the routing method not to match the request, so the match block is not called. With the backtracking_array plugin, failures of later matchers after an array matcher backtrack so the next entry in the array is tried. * A per_thread_caching plugin has been added, allowing you to change from a thread-safe shared cache to a per-thread cache, which may be faster on alternative ruby implementations, at the cost of additional memory usage. = New Features * The hash_matcher class method has been added to make it easier to define custom hash matchers: hash_matcher(:foo) do |v| self['foo'] == v end route do |r| r.on :foo=>'bar' do # matches when param foo has value bar end end * An r.root routing method has been added for handling GET requests where the current path is /. This is basically a faster and simpler version of r.get "", except it does not consume the / from the path. * The r.halt method now works without an argument, in which case it uses the current response. * The r.redirect method now works without an argument for non-GET requests, redirecting to the current path. * An :all hash matcher has been added, which takes an array and matches only if all of the elements match. This is mainly designed for usage inside an array matcher, so: r.on ["foo", {:all=>["bar", :id]}] do end will match either /foo or /bar/123, but not /bar. * The render plugin's view method now accepts a :content option, in which case it uses the content directly without running it through the template engine. This is useful if you have arbitrary content you want rendered inside the layout. * The render plugin now accepts an :escape option, in which case it will automatically set the default :engine_class for erb templates to an Erubis::EscapedEruby subclass. This changes the behavior of erb templates such that: <%= '' %> # <escaped> <%== '' %> # This makes it easier to protect against XSS attacks in your templates, as long as you only use <%== %> for content that has already been escaped. Note that similar behavior is available in Erubis by default, using the :opts=>{:escape_html=>true} render option, but that doesn't handle postfix conditionals in <%= %> tags. * The multi_route plugin now has an r.multi_route method, which will attempt to dispatch to one of the named routes based on first segment in the path. So this routing tree: plugin :multi_route route "a" do |r| r.is "c" do "e" end end route "b" do |r| r.is "d" do "f" end end route do |r| r.multi_route end will return "e" for /a/c and "f" for /b/d. * Plugins can now override request and response class methods using RequestClassMethods and ResponseClassMethods modules. = Optimizations * String, hash, and symbol matchers are now much faster by caching the underlying regexp. * String, hash, and symbol matchers are now faster by using a regexp positive lookahead assertion instead of an additional capture. * Terminal matching in the r.is, r.get, and r.post routing methods is now faster, as it does not use a hash matcher internally. * The routing methods are now faster by reducing the number of Array objects created. * Calling routing methods without arguments is now faster. * The r.get method is now faster by reducing the number of string allocations. * Many request methods are faster by reducing the number of method calls used. * Template caching no longer uses a mutex on MRI, since one is not needed for thread safety there. = Other Improvements * The flash plugin now implements its own flash hash instead of using sinatra-flash. It is now slightly faster and handles nil keys in #keep and #discard. * Roda's version is now stored in roda/version.rb so that it can be required without requiring Roda itself. = Backwards Compatibility * The multi_route plugin's route instance method has been changed to a request method. So the new usage is: plugin :multi_route route "a" do |r| end route do |r| r.route "a" # instead of: route "a" end * The session key used for the flash hash in the flash plugin is now :_flash, not :flash. * The :extension matcher now longer forces a terminal match, use one of the routing methods that forces a terminal match if you want that behavior. * The :term hash matcher has been removed. * The r.consume private method now takes the exact regexp to use to search the current path, it no longer enforces a preceeding slash and that the match end on a segment boundary. * Dynamically constructing match patterns is now a potential memory leak due to them being cached. So you shouldn't do things like: r.on r['param'] do end * Many private routing methods were changed or removed, if you were using them, you'll probably need to update your code.