 go in and get started because it's already a couple minutes late. So it's great to be here at RubyConf. This talk is entitled, Rota, the Routing Tree Web Framework. My name is Jeremy Evans, and I'm the lead developer of the Rota Web Framework. And I'm also the lead developer of the SQL Ruby Database Library. So I started off explaining why I created Rota. After all, there are a lot of Ruby Web Frameworks. So there should be a good reason for creating another one, especially if you want to recommend that other people use it. To explain why I created Rota, I need to go back about 10 years when I first started using Ruby. So I first got into Ruby in 2004, late 2004, about six months after Rails was first released. I think 0.8.5 was the first Rails version I used. And I found that Rails made web development much easier than the frameworkless PHP that I was currently using for web development at the time. And I used Rails for pretty much all of my web development for a few years, but I gradually got disillusioned by the complexity of Rails. When I found out about Sinatra in late 2007, I was amazed with the simplicity. The fact that you've just specified the routes you wanted to handle, and they just yielded to a block to handle the action, just made developing web applications so much simpler. So in April 2008, I started using Sinatra for all of my new web applications. Now over the years, I found that as the applications I was working on got more complex, I ended up with a lot of duplication in my Sinatra routes. Since my only real basis of comparison was Rails, and the Sinatra code was still simpler than what I would have written in Rails, I didn't really consider this too much of an issue. Now in July of this year, I was looking at a comparison of a bunch of lesser known Ruby web frameworks, and I read about Cuba. Now Cuba has been around since 2010, but this was my first exposure to it. As I read about Cuba, I saw how it addressed the duplication issues that I was seeing in my more complex Sinatra applications. And from reviewing some benchmarks, I also saw that Cuba was significantly faster than Sinatra. After trying to convert one of my simpler Sinatra applications to Cuba, I found there were quite a few things I didn't like about Cuba that were easier in Sinatra. And I came to the conclusion that I should create a new web framework based on Cuba that borrowed many features from Sinatra, along with some other ideas I had about extensibility and non-pollution, trying to get the best web framework for the types of web applications that I developed. And that's the story of Rota's creation. Now when I originally decided to fork Cuba, I hadn't decided on a name, so the temporary name I used was Sonuba, since it borrowed features from Sinatra and Cuba. And as I was laying in bed one night thinking of a name, I thought about what made the framework special. And in my mind, the main difference between Rota and Sinatra is that in Sinatra, you are iterating over an array of possible routes. While in Rota, the routing process is broken down and generally takes the form of a tree. So Rota is named after the Rota trees, which appear in the ease video game series, and help the main characters accomplish their goals. And this picture is from Ease Origins, and it shows a Rota tree prominently. As I said, the main feature that separates Rota from most other Ruby web frameworks is that it is designed around the concept of a routing tree. Since most programmers are not familiar with the routing tree concept, I'm first going to talk about what a routing tree is and how it works. Now before I can talk about a routing tree, I need to talk about routing in general. And in general, routing is the process of taking the request and finding the code that should handle that request. And while routing can consider any aspect of the request, in most cases, only two parts of the request will be used during routing. And these two parts are both contained in the HTTP request line, which is the first data transmitted from the client to the server during an HTTP request. Now an HTTP request line looks like this. Here, get is the request method, and albums one tracks is the request path. And it is possible to use other parts of the request during routing, such as routing using information from the request headers or the request body. But in most cases, only the request method and the request path are used. Sinatra and other similar Ruby web frameworks look at the full path of the request when deciding how to route a request. So they iterate over their array of possible routes, checking each route to see if it matches the current request. Now a routing tree handles things differently, routing by segments of the path. So in routing request for albums one tracks, a routing tree is going to look at the first segment, albums. And if the album segment does not match, it's going to skip the entire albums branch so that other routes under albums are not considered. If the album segment matches, it's going to look inside the albums branch of the tree for a route for one tracks, ignoring other branches. So incidentally, this is more or less how a file system works. When you ask a file system to open a file, it doesn't compare every path on the file system to see if it matches the requested path. It looks at the first segment of the path, sees if it is a directory, and if so, it looks inside that directory for the rest of the path. Now if the use of a routing tree was purely a performance issue, it wouldn't be all that interesting. What makes routing trees interesting is that at any point during the routing, you can operate on the current request. So if any of you have used the Ragel state machine compiler, this is similar to Ragel's ability to execute arbitrary code during parsing. So now that I've briefly gone over what a routing tree is, let me go over how Rota implements the routing tree concept. So let's start with hello world, since that's the simplest case. So this Rota app will return hello world as the response body for every request. And while it does not show off the routing tree aspects of Rota, it does show that Rota gets the response body from the value that the block returns, which is similar to Sinatra. Now in almost all Rota apps, you're going to want to actually use Rota's routing tree methods. So the first routing tree method I'm going to discuss is called r.on, which creates branches in the routing tree. So here you call r.on with the string albums, which will match the current request path if the request path starts with albums. Let me break down what is going on here. So in the first line, we are calling the Rota.Route method, which is the start of the routing tree. All requests that come into the web application are yielded to the block that you passed to Rota.Route. This block is yielded a rack request instance with some additional methods, and by convention, the block argument is named r. The additional methods added to the rack request instance are related to routing the request. So as mentioned earlier, the requests.on method is used to create branches in the routing tree. Any arguments you pass to this method are called matchers and are used to match the current request. So in this case, a single matcher is given the string matcher. And in Rota, string matchers match the first segment in the request path. So if the request path starts with albums, this will match, and the request will be routed to the block that you passed to on. Now that block returns a hello album string, which Rota will use as the response body. If the request path is artists, this will not match, so r.on will return nil without yielding to the block. And execution will continue after the call. Now in this case, there is nothing after the call to r.on, so the return value of the Rota.route block will be nil. And since the block did not return a string, Rota will use a 404 response status code with an empty response body. So that provides the principle of least surprise, where if you do not specifically handle a request, an empty 404 response is used. And while it works, this code has issues, because it returns the same response for all paths under albums, including albums should not exist. And in general, that is not what you want. What you probably want to do is to return a 404 response for any path that you do not specifically handle. So if you only want to handle albums and not any paths below albums, you can use the r.is method. r.is is similar to r.on, but it does a terminal match. So it only matches if the request path is empty after processing the matchers. So this code will not match the albums should not exist path. So the reason for the method naming here is that r.on matches on the request prefix. And while r.is matches only if the match is complete. So routing trees in Rota are built using a combination of the r.on and r.is methods. r.on does prefix matching of the request path. And r.is does full matching of the request path. So if you were routing purely based on the request path, you could say r.on creates branches in the routing tree, and r.is creates leaf nodes. So here r.on albums creates a branch, which handles all paths under albums. Here you're calling r.isList, which will match only if the current request path is list. That may seem odd that this works, but the reason it does is that the request path is being modified as the request is being routed. So when a request for albums list comes in, the routing tree uses the initial request path. When the r.on albums method matches, it consumes albums from the front of the request path. So inside of the r.on block, the request path is empty. So r.is matches, or it's not empty, it's list. r.is matches only if all of its matchers match, and the request path is completely consumed by the matchers. Since the request path is list, and that matches the string that you give to r.is, this request matches and hello albums will be returned. If you get a request for albums list all, the r.on call will still match, but the request path inside the r.on block will be list all. And since r.is list does not completely consume the request path, this request will not be matched, and Rota will return an empty 404 response. So far, I've just been focusing on routing using the request path. As I mentioned earlier, routing usually takes into account the request method as well. So consider this routing tree, which will handle requests for albums new. In order to handle the get and post request methods, Rota has r.get and r.post routing methods. So if you call these methods with no arguments, they just do a simple match against the request method. So r.get matches get requests, and r.post matches post requests. Now here, a get request for albums new will return hello albums, and a post request for albums new will return album added. So the way routing tree is usually built in Rota is by combining these methods. You use the r.on method to branch based on the request path prefix. You use the r.is method to do a complete match on the path, and you use the r.get or r.post methods to handle different request methods for the same request path. As I mentioned, if you do not provide any matchers to the r.get or r.post methods, they just do a simple check against the request method. If you provide any matchers to these methods, they also do a terminal match on the request path, which allows for an API that is similar to Sinatra. So my get request for albums will be matched by r.get albums, and a post request for artists will be matched by r.post artists. But a post request for artists one will not be matched by either. It won't be matched by r.get because it is not a get request, and it won't be matched by r.post because the request path will not be completely consumed by the matchers. So when you are building a routing tree, if you only want to handle a get request for albums list and not other request methods, instead of calling r.is list and r.get inside of that, you can just use r.get list, which is a more succinct way of expressing the same routing tree. So now that we've covered the four basic routing methods, let's talk about the arguments you give to these methods, which are called matchers. So we've already covered one type of matcher, which is the string matcher. And this matches the verbatim string in the first segment of the request path. Strings can contain slashes if you want to match multiple segments in the request path. So this matches albums list, but not albums one. You can use embedded colons in your strings, which will match arbitrary segments in the request path. So this matches both albums one and albums two. Note that when you use an embedded colon in the string, the text that is matched by the colon is yielded to the block. And in Rota, this is the primary way the data from the request path is extracted for use inside the route handling code. Now another way of specifying the previous matcher is to use a separate symbol matcher. And just as with the embedded colon in the string, this yields the matched segment to the block. Yet another way of doing it is using a regular expression. And with a regular expression matcher, any regular expression captures are yielded to the block. So there are other types of matchers that allow for more advanced matching, but in the interest of time, I will not be going over them. I mentioned earlier that one of the main advantages of routing trees is that they have the ability to execute arbitrary code during the routing process. And that may not sound important. But it is the main reason that Rota allows for simpler and drier code compared to most Ruby web frameworks. If you want to make sure that someone is logged in before accessing these routes, you can just put that code that checks for login as the first line in your Rota.route block. And this provides a similar feature to the capabilities of a global before filter in Rails or Sinatra, so it doesn't sound like anything special. Because you can execute code at any point in the routing tree, you can put this check after the routes for logging in. I think this is more elegant than a global before filter that does a login check, which must check that the current path is not the login action or else no one would be able to log in. Now the principle of executing code during routing applies at all points in the routing tree. So if only certain users should have access to View or Update Albums, you can have that check near the top of the R.onAlbums block. And the most common place this is useful is with dealing with separate requests methods for the same request path. So assuming that the get request for Albums 1 will display a form for editing the Album with ID 1 and a post request for Albums 1 will process that form's input, the routes can share code for retrieving the Album, as shown here. Now this is not revolutionary, and you can accomplish pretty much the same thing using before filters in Rails or Sinatra. In both Rails and Sinatra, the before filters are going to be separated from the code being executed, which makes it harder to reason about. And if you use this pattern a lot, you have to specify a separate before filter for every set of get and post routes you have, which is cumbersome, and on Sinatra it negatively affects performance. Now speaking of performance, Rode is one of the best performing Ruby web frameworks. With Ruby's reputation for performance, you may think that's similar to saying Rode is one of the fastest turtles, but it is a much faster turtle than Rails and Sinatra. So for a simple hello world app with a single route, Rode is about two and a half times faster than Sinatra. And the reason for this is Rode has lower overhead than Sinatra. Now a hello world benchmark doesn't really tell you how well Rode will perform in real world applications. So my production applications were always faster using Rode than Rails or Sinatra. How much faster depends on the specific action, with simpler actions performing significantly faster and more complex actions performing about the same as most of the time for those who spent inside the action and not during routing. One pleasant surprise was that my integration tests sped up significantly. The exact same rack test based integration tests run 50% faster with Rode than Sinatra and twice as fast after converting from Rails to Rode. In terms of memory usage, Rode uses about 10 megabytes of memory less than Sinatra in terms of just requiring library, but in my real world apps I've only noticed a one or two megabyte decrease in the amount of memory used. When compared to Rails it's a different story. On the largest app I converted from Rails to Rode I saw memory decrease from 150 megabytes to 80 megabytes per unicorn worker process and the second largest I saw memory decrease from 100 megabytes to 60 megabytes per unicorn worker process. My largest app is probably only about the size of your average Rails app with about 200 routes. So this really does not tell you if Rode's approach will scale to large web applications. Since I thought this would be useful information to know if I was going to advocate that other people use Rode I decided to see how well Rode would scale to an application with a large number of routes. So you may be familiar with the C10K problem which was stated by Dan Kegel in 2001 and said, you know, it was time that web servers should be able to handle 10,000 clients simultaneously. So I'm gonna state the R10K problem which is that web frameworks should be able to handle 10,000 routes efficiently. So I wrote a code generator that generates web applications with 10, 100, 1,000 and 10,000 routes for Rode, Rails and Sinatra and I benchmarked them. So for 10 routes, R10K generates routes from A to J using a single segment per request path. For 100 routes, R10K generates routes from A to JJ using two segments per request path. So this extends to 1,000 routes with three segments per request path and 10,000 routes with four segments per request path. So let's first look at a comparison of Rode, Rails and Sinatra at 10, 100 and 1,000 routes. So here are the runtime results for 20,000 requests. Know that these are using the RAC API directly so this does not include the web server overhead. As you can see from the Rode and Rails lines, there is not a significant performance decrease for either as the number of routes increases. And this is because Rode uses a routing tree while Rails uses finite automata for routing requests. Since Sinatra just iterates over an array of possible routes, performance decreases linearly as the number of routes increases. So at 1,000 routes, Sinatra is almost as slow as Rails. Now regardless of the number of routes, Rode is much faster than either Rails or Sinatra. You can probably tell where this is going, but let's look at the results with 10,000 routes. So by the time you get to 10,000 routes, Sinatra's performance is much worse than Rails. And because of the scale of the graph, you can't tell how long Rode is taking 10,000 routes, but I can't tell you it is less than five seconds even with 10,000 routes. Note that these numbers do not include startup time. Adding startup time does not change the picture in general except when using 10,000 routes in Rails where it dramatically increases the time with Rails taking about as long to startup as it takes to serve 20,000 requests. And when trying to use 10,000 routes in Rails, almost all of the startup time is spent inside routes.draw. And if I had to guess, this is due to building the finite automata for the router. Now part of performance is also the amount of memory used. As mentioned earlier, and as you can see here, Rode uses less memory than Sinatra and significantly less memory than Rails regardless of the number of routes. The trend gets even more dramatic when you go from 1,000 routes to 10,000 routes. So with 10,000 routes, Rode uses less than half of the memory of Sinatra and about a fifth of the memory of Rails. Rode uses less memory with 10,000 routes than Rails uses with 10 routes. Now because benchmarks are worthless unless the source is available for reproduction and criticism, you can find the source code for these benchmarks in my R10K GitHub repository and I encourage anyone to double check my work to make sure I'm not doing anything stupid or unfair to Rails or Sinatra. I've gone over what I think are some design and performance advantages of using Rode. However, the benefits that Rode offers come at a cost and that cost is the loss of route introspection. Because all routing in Rode is done at the instance level, you cannot introspect your routes like you can in Rails or Sinatra. Now in most cases, this doesn't matter, but there are applications that rely on introspection of the routes and those would need to be handled differently with Rode. So now that I've talked about the advantages and disadvantages of Rode, I'm gonna talk a little bit about the history of routing tree web frameworks in Ruby. So the first routing tree web framework for Ruby was RUM, which was written by Christian Newkirchen, the author of RAC in January 2009. Now RUM was never released as a gem, it was basically just a proof of concept, showing how you could use a routing tree to route requests. CUBA was developed by Michael Martins, starting in April 2010, and it started off as a simple wrapper around RUM with support for Hamel templates and it's now expanded to support any template library supported by tilt. So CUBA was the first routing tree web framework for Ruby that was released as a gem and it has enjoyed some modest success with about 40,000 downloads. Now as I mentioned at the start of this presentation, Rode was forked from CUBA because while I liked the idea of using a routing tree, there were various aspects of CUBA's design and implementation that I did not like. I really think forking someone else's software should really be a last resort, as it's better to collaborate and work together to achieve a common goal. Some of the things I did not like about CUBA were fundamental to CUBA's design or philosophy, so it really would not have been possible to use CUBA and still accomplish the goals that I was looking to accomplish with Rode. And in the cases where the goals of the fork are different than the goals of the original software and you can't accomplish the goals of the fork without compromising the goals of the original software, I think forking is appropriate. So when I first started using CUBA in version 3.1.1, status code handling violated the principle of least surprise. So if you had this routing tree in CUBA, a request for albums with no ID would return an empty 200 response instead of an empty 404 response, because the status code was set to 200 as soon as the on albums method matched. Now this was a well-known issue with CUBA, with a whole section in the read me about how to work around the problem, and a pull request to fix this issue had been opening CUBA's issue tracker for over six months. Now this was eventually fixed in CUBA about a week after Rode's initial release using basically the same approach that Rode uses. So the second significant issue I had with CUBA was that it did not have built-in support for terminal routes. So CUBA has no built-in equivalent to Rode's R.is method, which makes sure that there is a terminal match on the request path. When you use CUBA's on method, it only makes sure that the prefix matches. So this code will match albums, but also albums one and albums should not exist. CUBA's recommendation, if you want a terminal match, is to use backslash z at the end of a regular expression. Let's just say I did not think this was very friendly to the user. Another thing I didn't like about CUBA was that it required explicit writing of response bodies instead of using the route block return value as the response body, which is how Snatcher operates. So every time you want to write a response body in CUBA, you have to call the res.write method explicitly. I think Snatcher's approach is simpler, and I don't think there is a net benefit by making the response body writing explicit. Now this is one of the philosophical differences between Rota and CUBA. Now I was originally thinking, I could just work around this issue by using a CUBA plugin that made the response body writing implicit. But I found that CUBA's plugin system was unable to override methods defined by CUBA. So I wanted to write a plugin that just overrode on and called super, and if the on block returned a string, write it to the response body before returning from the block. Now unfortunately in CUBA this does not work, at least not if you add the plugin directly to the CUBA class. And after speaking with the author, I found out this was by design. So I was used to SQL's plugin system where you can override any method and call super to get the default behavior. So it was at this point that I decided to fork and make sure that the fork used a more extensible plugin system that is based on SQL's plugin system. So after I decided to fork, I wanted to fix an issue that I've had with every Ruby web framework that I've used, which is that instance variables, methods, and constants pollute the namespace, which can cause problems for the user of the framework. So one of the things you can see in this picture is that rota trees naturally resist the pollution that is corrupting the rest of the land. Can anyone see the problem with this Sinatra code? The problem is that response instance variable is already used by Sinatra internally. So this clobbers the response variable and completely breaks Sinatra. Now it just so happens that one of the apps I work on deals with the domain object called response. So using response as the class name and response as the instance variable name was the most natural way to express this code. And because I wanted it to work easily in Sinatra, I had to rename the instance variable to agency response. I didn't like this because a name like agency response indicates to me that there are other types of responses that the system deals with, and that's just not true in this case. So in rota, all instance variables that are used internally in the scope of the rota.route block are prefixed with an underscore. So they will not conflict with the instance variables that the user wants to use. Name space pollution is not just an issue with instance variables, both constants as well. Now so can anyone see the problem with this Cuba code? So the problem is that even though you are requiring a top level response class in the first line, the reference to the response class inside the Cuba app references Cuba response, which is a subclass of rack response, not the top level response class that you required. So to avoid pollution of the constant name space, rota prefixes all constants in the rota name space with rota. So the internal classes are named rota, rota request, rota, rota response, and so on. This makes it unlikely that rota's constants will conflict with your application's constants. Finally, rota of woods, including the method name space. With Cuba, all of the routing methods, such as on get and root, are instance methods in the scope of the route block. So you cannot define view methods that conflict with them. I don't wanna imply this as a problem specific to Cuba since Cuba is actually much better than most other Ruby web frameworks in this regard, with only 23 additional methods over what is defined in object. With Sinatra, there are 68 additional methods. With rota, there are only six additional methods. And the reason that rota is able to only have six additional methods is by moving the routing methods to the request class, which is why the rota.route block yields the request instance to the block, and routing methods are methods called on this request instance. Incidentally, if you look at Cuba's routing methods, they all have feature envy, since pretty much all they do is call methods on the request instance. So even if this was not being done to avoid pollution, it's still a good idea from an object-oriented design perspective. So that brings us to the final section of this talk, where I'm gonna talk about rota's plugins. Rota's philosophy is to have a very small core with only the essentials and have all non-essential features added via plugins. That way you get to choose to load only the features that you need. So loading plugins in rota is similar to loading plugins in SQL and Cuba, just by calling the rota.plugin method. And like SQL, but unlike Cuba, rota ships with a set of official plugins, but external plugins can be loaded via the same method, so whether the plugin is external or not is transparent to the user. So in this case, this loads the render plugin, which supports rendering templates using the tilt library. So usage is similar to Sinatra, except that instead of having separate methods per template engine, it just has a single view method. Now in my experience with Sinatra, I rarely use more than one template engine in the same application. So with rota's render plugin, you set the default template engine when loading the plugin, and if you want to use a non-default template engine, you specify it as an option to override the default when you call view. So similar to Sinatra, the view method just returns a string. So if you call view as the last expression in your route block, the result of the view is used as the response body. A special note in the render plugin is the escape option, which switches the default template engine to an E-Ruby subclass that automatically escapes output, which prevents common cross-site scripting vulnerabilities. So when using the escape option, the usual percent equal tag will escape the output, and to get unescaped output, use the percent double equal tag. And the use of percent equal for escaping and percent double equal for not escaping provides most of the security benefits of the default escaping that Rails uses without the complexity of active support safe buffer. Sinatra users may think there's nothing special about this, since Sinatra supports the escape HTML ERB option, which will do roughly the same thing. This is what Sinatra recommends in its FAQ when asked about how to automatically escape HTML. What they do not tell you is that this is at least partially broken, in that it does not support post-fixed conditionals inside percent equal tags. And this is odd because post-fixed conditionals inside percent equal tags are supported when not using the escape HTML option. And the reason that percent equal doesn't work with post-fixed conditionals is because it generates this Ruby code, just using the percent equal tag content as the argument to escape XML. Unfortunately, this is not valid Ruby syntax as you cannot use a post-fixed conditional expression directly as a method argument. In order to make it valid Ruby syntax, you need to wrap the expression in parentheses, which is what Rode does. So Rode ships with another security-related plugin, CSRF, which adds protection against cross-site request forgery. So this loads rack CSRF as one of the middleware for the application. And it also adds some view helper methods, which allow you to easily create the necessary CSRF token tags in your views. So if you are doing server-side rendering using the render plugin and you want to improve performance, in addition to thinking about application performance, you also need to think about rendering performance on the client. And one of the best ways to improve rendering performance on the client is to flush the head tag for the response while still preparing the remainder of the response. And the chunk plugin lets you do that using transfer encoding chunked. So with the chunk plugin, you just call view or call chunked instead of view. And it will stream the response to the client in chunks. And by default, it will flush the top part of the layout before rendering the content template. So you can pass a block to the chunk method, which will be yielded to after the top part of the layout has been flushed to the client, but before rendering the content template. So this allows you to execute code necessary to render the content while the client is making requests to load the assets necessary to display the page. And this can significantly decrease the total time that the client takes to fully render the page. Additionally, at any point inside the content or layout templates, you can use flush to send the partially rendered response to the client. And this is mostly useful when loading large pages where you want the client to see visible content before all of the content is finished rendering. Now the chunk plugin also has a chunk by default option, which switches view to use chunk encoding if the client supports it, allowing you to speed up rendering performance for all the reviews. So one of the issues with Rota's approach of having a single routing block that all requests are yielded to is that you cannot split a block across multiple files. For large sites, it's not really practical to have all your routes in a single file, so Rota comes with a multi-route plugin. And with the multi-route plugin, in your main Rota application file, let's say you have a large routing sub-tree for routes under albums, which you want to move to a separate file. You replace the sub-tree with a call to r.route with the name of the route you want to use. And in most cases, this will be the same as the matched part of the request path. You take the routing sub-tree you removed from the main source file and you place it in a separate file. My convention, these are called routing files and they're stored in a routes sub-directory and you just require all files from the route sub-directory in your main application file. So inside these routing files you call Rota.route but you pass it the name for the route, which is the same as the name that you used inside the R.route call in the main Rota application file. So for large apps, it's common to move all of this routing code to routing files. So assuming that you named all of the routes with the request path prefix, you can dispatch to all of the named routes with a single R.route call. And in addition to drawing up your code, this is also faster as it matches against all route prefixes simultaneously using a single regular expression. Now if you are writing an app that uses the multi-route plugin, it's probably large enough that you do not want to use a single directory for all of your views. While you could just specify the sub-directory each time you wanted to render a review, that's not very dry. So Rota ships with a view sub-dears plugin that allows for setting a view sub-directory to use for a given branch. So with the view sub-dears plugin, you call set view sub-dear anywhere in your routing code and when you call view, it will automatically prefix the template name with that sub-directory. Now that's more dry, but some people only think that's not dry enough. So for the dryest code, you can use the symbol views plugin. With the symbol views plugin, you just have your route block return a symbol which is interpreted as a view template name and it will render the view and use the result as the response body. Now some people only consider this too dry, but the great thing about Rota is that this behavior is loaded via a plugin. So if you don't like it, you just don't have to load that plugin. Similar to the symbol views plugin, Rota ships with a JSON plugin. Now the JSON plugin makes it easy to create JSON API sites. You just have your route block return either an array or a hash and Rota will automatically convert it to JSON and use the JSON as response body. Now a common issue with most Ruby web frameworks is that by default they tend to be too liberal in terms of what they match. So the symbol matchers plugin exists to allow for more advanced matching. So take this simple route which matches albums followed by a segment for the albums ID and while this works in general, it is too liberal. It's kind of obvious it's too liberal since you are converting the matched album ID to an integer. So what you really want to do is only match segments consisting solely of decimal digits. Now you could switch to using a regular expression as your matcher, but then you have to specify the grouping manually and it takes more cognitive overhead to use a regular expression. So with the symbol matchers plugin, Rota automatically treats some symbols specially using specific regular expressions for them. One of these symbols is D which matches only decimal digits. So let's make sure that only albums followed by a segment with only decimal digits will be matched. Now the symbol matchers plugin also makes it easy to define your own symbol matchers. So let's say you have lots of routes in your application that include a user's username, but you don't allow arbitrary characters in your username. So you have a strict requirement of only lowercase ASCII letters and user names must be between six and 20 characters. So with the symbol matchers plugin, you can use the symbol matcher method, passing it the symbol you want to match and the regular expression to use for that symbol. And then everywhere in your app where you use that symbol, it will use that specific regular expression instead of the default of matching any sequence of characters. And as shown in the previous examples, this works both for symbols and for embedded colons and strings. So Rota ships with a flash plugin that adds support for flash handling similar to Rails flash handling or the flash handling you get from Snatcher flash. If you want to handle request methods other than get and post, Rota ships with an all verbs plugin that adds routing methods for all of the HTTP request methods. Rota ships with a not found plugin for Snatcher like support of specifying a block to call when no route matches. Rota ships with an air handler plugin for Snatcher like support of specifying a block to call when an air is raised while handling a request. Rota ships with a pass plugin for Snatcher like pass method. And the pass method jumps out of the current routing block as if it did not match allowing it to continue the routing process. Rota ships with an indifferent params plugin for support for a Snatcher like params hash that works both with symbols and strings. Now you could call this stealing from Snatcher if you did not know that I am the one that introduced indifferent params to Snatcher in the first place. To be fair to me, Snatcher originally symbolized all params which opened it up to denial of service attacks. So introducing indifferent access was done to keep it backwards compatibility not because different params are a good idea in general. If you want some actual stealing from Snatcher Rota ships with a streaming plugin with an implementation that is mostly borrowed from Snatcher though it does have some significant improvements that make it easier to use safely. And likewise, Rota ships with a caching plugin with an implementation that is mostly borrowed from Snatcher adding support for cache control, last modified, and E tags. Rota also ships with an assets plugin that allows for easily compiling and rendering your CSS and JavaScript files on fly in development and compiling them into a single compressed file in production for maximum performance. And Rota ships with a middleware plugin that turns your Rota app into rack middleware. So if you have a Rails or Snatcher application that you want to speed up but you do not have time to fully replace it with Rota, you can use the middleware plugin to turn the Rota app into middleware and use that middleware in your Rails or Snatcher application. Any route that the Rota app does not handle will be forwarded to your Rails or Snatcher applications which allows you to gradually upgrade. So Rota ships with over 10 additional plugins but as I'm getting low on time, I can't cover all of them. So let me review what I think are the main advantages of using Rota compared to most other Ruby web frameworks. First, I think for simple web applications, Rota is about as simple to use as Snatcher and much simpler than Rails. And Rota code base itself is significantly smaller and simpler than either Rails or Snatcher. As your application gets more complex, I think Rota does a better job than Snatcher of managing that complexity while always remaining a lot less complex than Rails. Rota performs significantly better than either Snatcher or Rails and scales much better than either Snatcher or Rails for truly large sites. Rota does not pollute your namespaces. So you do not need to worry about Rota conflicting with your application code. And finally, because of the SQL-based plugin system it uses, Rota is very extensible. So if you need functionality that is not included in one of the plugins that ship with Rota, it's easy to add yourself. So that's why I think you should consider Rota for your Ruby web application development. And that concludes this presentation. I want to thank you all for attending and listening to me present Rota. If you have any questions about Rota, I'll be happy to answer them now.