Understanding the Rails Router: Why, What, and How

Ask questions Research chat →

https://www.writesoftwarewell.com/understanding-rails-router-why-what-how/ · scraped

rails

Attachments

Scraped Content

— 5804 words · 2026-02-14 17:45:08 UTC ·

Excerpt

![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/3aa46790-a23b-4384-a153-5bcbb01fa861/router-3.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466VUZRSXRA%2F20260214%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260214T174504Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEEaCXVzLXdlc3QtMiJHMEUCIDivo%2FEFqednkfb7LN%2BHDVKqHG%2Bq8FLChk0G%2BKihCk8lAiEA3cScUo%2BDFH2rrV7ODEHVIeeglTI4lPvNPRtrqGrq8gUq%2FwMIChAAGgw2Mzc0MjMxODM4MDUiDFtkPUq33EeMJ%2BtFDSrcA%2FdRZhhZhejXSHxGcQZE0m%2BkJRJ89nMPLkxHDucmelP1QejRbbRCHETgN2CbSsy2HnNTwyGocryNEmplPe31LSw1%2F66nauZqeUblMv73r7g9EA7hkAR%2BClOOApvyc78SN9ExyRpwpVvK45imTHia2miyp9kGiHLY1nVPXg4atNUj3quJr98A8lanjcUEACz4QZPuIRUT27jQ7wAAQt4BG%2BqVvjFSgVxPxZ88lg8BivoYh55k8%2BqLXr6NZNeD1lEFHTbw9oocJh7MdTowxmV5mBF0LjBPqt9O1vY321QN74BdPOqsFP4%2Bs7DsgeWRGRLbs9IjCYVJEqr%2FizGlPeJ4uXiun3ezKHycgR%2Bsnp3chmI%2BSHeqQ%2BgmY2EvGyjp9FQSXfgzgcg3c08%2BBbCurV2bpOzYqh4BExkhP
![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/3aa46790-a23b-4384-a153-5bcbb01fa861/router-3.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466VUZRSXRA%2F20260214%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260214T174504Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEEaCXVzLXdlc3QtMiJHMEUCIDivo%2FEFqednkfb7LN%2BHDVKqHG%2Bq8FLChk0G%2BKihCk8lAiEA3cScUo%2BDFH2rrV7ODEHVIeeglTI4lPvNPRtrqGrq8gUq%2FwMIChAAGgw2Mzc0MjMxODM4MDUiDFtkPUq33EeMJ%2BtFDSrcA%2FdRZhhZhejXSHxGcQZE0m%2BkJRJ89nMPLkxHDucmelP1QejRbbRCHETgN2CbSsy2HnNTwyGocryNEmplPe31LSw1%2F66nauZqeUblMv73r7g9EA7hkAR%2BClOOApvyc78SN9ExyRpwpVvK45imTHia2miyp9kGiHLY1nVPXg4atNUj3quJr98A8lanjcUEACz4QZPuIRUT27jQ7wAAQt4BG%2BqVvjFSgVxPxZ88lg8BivoYh55k8%2BqLXr6NZNeD1lEFHTbw9oocJh7MdTowxmV5mBF0LjBPqt9O1vY321QN74BdPOqsFP4%2Bs7DsgeWRGRLbs9IjCYVJEqr%2FizGlPeJ4uXiun3ezKHycgR%2Bsnp3chmI%2BSHeqQ%2BgmY2EvGyjp9FQSXfgzgcg3c08%2BBbCurV2bpOzYqh4BExkhP2ZIHWP3lhFUkz1wxlSBXNTJBjKUWtiGA8ZOzTrlzUEipWS62tqvt6X92l7kWPz7z8O55keBcIoN4w%2FGlF64BOC9v9RSFGTvaoLxDLUtrVx6DmSzP0TTJW5VDRsWnVO7k%2BDQHp0DpE0L%2Bvfnzcl9qcEbbuPnC7JiMnu5dzQWuImkkT%2Fa4wgo8vHhr4jPP6rQjPxN6NBSv5ulNUhyHQDJX1jwNvuQMK%2FRwswGOqUBbOwrfNpZkoguc6bv5cUw9vziEvnHH6dvYVdCQ%2F7sPux2lvBvbxvmJxHkXrq8szmGhXxwri19JZ8uYpdJyVDT9mUYtcaTYoU%2FSyhnStsYMoNDFMNcBE%2Bkm54YpM6j27YEUIE1CV8Zake402wBwW9VU0H4SsS5%2B17TgclsfvJrRnKCFK8kinS5PmohBhzELS5CUygy8xapBcDp3xjLJm2l2tUNh6%2F0&X-Amz-Signature=6448fba9f243e98e288db83a7e652654ca303902d1f58f9bcf9e9878366db28c&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject) Crossroad in the woods to hint Rails Router I originally wrote this post earlier this year, but since then I've learned a few more things about the Rails router, so re-publishing the revised and polished tutorial. Do you ever get the feeling that you know a topic is important and that you should know well, but you only understand it superficially? Ever since I got into Rails, I've had that feeling with the Rails router. Transitioning from ASP.NET, I knew the concept of routing and got comfortable with the router after reading the docs. However, at the back of my mind, I've always had that nagging feeling that I don't REALLY understand the complete picture. I could make the basic routes and resources to get by for the simple CRUD tasks, but whenever I wanted to do something complicated, I found myself fumbling in dark. Well, after over a year of fumbling and trying to skim the docs and StackOverflow to just learn enough to get by, I decided to dig deeper and REALLY understand how Routing works in Rails. I've tried to summarize everything I learned in this one giant article. It's over 5,000 words, so don't expect to finish it in one sitting. However, if you can stick through it and read it till the end, I can pretty much guarantee that you'll have a much better understanding of how routing works in Rails. You'll also learn a few nice tips and tricks to clean up and simplify your existing routes right away. Sounds interesting? Let's get started. Here's what we'll learn: ### What You'll Learn: 1. What is Routing? 2. Routing in Rails 3. Digging Deeper: Router Internals 4. A Short Detour: the instance_exec Method 5. The match Method and its Options 6. HTTP Method Shorthands 7. Understanding the Segment Keys 8. The Magic of Named Routes 9. Resourceful Routes 10. Non-Resourceful Custom Routes We'll start with the basics, by understanding the purpose of routing in web applications and the role of the Rails router. Then we'll open the hood (i.e. the Rails codebase) and take a peek behind the scenes to understand how Rails implements the routing mechanism. Then, we'll learn about the match method, which forms the core of most of the routing shorthand methods in Rails, such as get, post, etc. It leads us into covering a few fundamental routing topics such as segment keys and named routes. Once we've learned the fundamentals, we'll be ready to tackle the practical routing topics, which you'll use day-to-day in your Rails apps. They include the concept of resources and RESTful routes, which drastically reduce the amount of code you write by following convention-over-configuration. We'll wrap up by learning the syntactic sugar provided by router to make it easy to create more expressive routes. As it goes with all the posts on this blog, I'm writing to clarify my understanding and act as a reference for the future me, but I really hope that you, the reader, find my writing useful in gaining a deeper understanding of the router. There's a little metaprogramming involved along the way, but don't worry, it's actually very simple to understand. If you need a quick primer on metaprogramming, check out my notes on the Metaprogramming Ruby book. ![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/d1e9437f-5e0d-43ad-9af6-ca0726a6b6db/Akshay-3.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466VUZRSXRA%2F20260214%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260214T174504Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEEaCXVzLXdlc3QtMiJHMEUCIDivo%2FEFqednkfb7LN%2BHDVKqHG%2Bq8FLChk0G%2BKihCk8lAiEA3cScUo%2BDFH2rrV7ODEHVIeeglTI4lPvNPRtrqGrq8gUq%2FwMIChAAGgw2Mzc0MjMxODM4MDUiDFtkPUq33EeMJ%2BtFDSrcA%2FdRZhhZhejXSHxGcQZE0m%2BkJRJ89nMPLkxHDucmelP1QejRbbRCHETgN2CbSsy2HnNTwyGocryNEmplPe31LSw1%2F66nauZqeUblMv73r7g9EA7hkAR%2BClOOApvyc78SN9ExyRpwpVvK45imTHia2miyp9kGiHLY1nVPXg4atNUj3quJr98A8lanjcUEACz4QZPuIRUT27jQ7wAAQt4BG%2BqVvjFSgVxPxZ88lg8BivoYh55k8%2BqLXr6NZNeD1lEFHTbw9oocJh7MdTowxmV5mBF0LjBPqt9O1vY321QN74BdPOqsFP4%2Bs7DsgeWRGRLbs9IjCYVJEqr%2FizGlPeJ4uXiun3ezKHycgR%2Bsnp3chmI%2BSHeqQ%2BgmY2EvGyjp9FQSXfgzgcg3c08%2BBbCurV2bpOzYqh4BExkhP2ZIHWP3lhFUkz1wxlSBXNTJBjKUWtiGA8ZOzTrlzUEipWS62tqvt6X92l7kWPz7z8O55keBcIoN4w%2FGlF64BOC9v9RSFGTvaoLxDLUtrVx6DmSzP0TTJW5VDRsWnVO7k%2BDQHp0DpE0L%2Bvfnzcl9qcEbbuPnC7JiMnu5dzQWuImkkT%2Fa4wgo8vHhr4jPP6rQjPxN6NBSv5ulNUhyHQDJX1jwNvuQMK%2FRwswGOqUBbOwrfNpZkoguc6bv5cUw9vziEvnHH6dvYVdCQ%2F7sPux2lvBvbxvmJxHkXrq8szmGhXxwri19JZ8uYpdJyVDT9mUYtcaTYoU%2FSyhnStsYMoNDFMNcBE%2Bkm54YpM6j27YEUIE1CV8Zake402wBwW9VU0H4SsS5%2B17TgclsfvJrRnKCFK8kinS5PmohBhzELS5CUygy8xapBcDp3xjLJm2l2tUNh6%2F0&X-Amz-Signature=b1eb9c199ebc525161df01b8084a33314861d2c5b9b6b58b60c7d9f66eb6abd6&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject) ![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/a3fd44aa-bb0c-45e6-9a50-3f22c600b3ec/metaprogramming_ruby-4.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466VUZRSXRA%2F20260214%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260214T174504Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEEaCXVzLXdlc3QtMiJHMEUCIDivo%2FEFqednkfb7LN%2BHDVKqHG%2Bq8FLChk0G%2BKihCk8lAiEA3cScUo%2BDFH2rrV7ODEHVIeeglTI4lPvNPRtrqGrq8gUq%2FwMIChAAGgw2Mzc0MjMxODM4MDUiDFtkPUq33EeMJ%2BtFDSrcA%2FdRZhhZhejXSHxGcQZE0m%2BkJRJ89nMPLkxHDucmelP1QejRbbRCHETgN2CbSsy2HnNTwyGocryNEmplPe31LSw1%2F66nauZqeUblMv73r7g9EA7hkAR%2BClOOApvyc78SN9ExyRpwpVvK45imTHia2miyp9kGiHLY1nVPXg4atNUj3quJr98A8lanjcUEACz4QZPuIRUT27jQ7wAAQt4BG%2BqVvjFSgVxPxZ88lg8BivoYh55k8%2BqLXr6NZNeD1lEFHTbw9oocJh7MdTowxmV5mBF0LjBPqt9O1vY321QN74BdPOqsFP4%2Bs7DsgeWRGRLbs9IjCYVJEqr%2FizGlPeJ4uXiun3ezKHycgR%2Bsnp3chmI%2BSHeqQ%2BgmY2EvGyjp9FQSXfgzgcg3c08%2BBbCurV2bpOzYqh4BExkhP2ZIHWP3lhFUkz1wxlSBXNTJBjKUWtiGA8ZOzTrlzUEipWS62tqvt6X92l7kWPz7z8O55keBcIoN4w%2FGlF64BOC9v9RSFGTvaoLxDLUtrVx6DmSzP0TTJW5VDRsWnVO7k%2BDQHp0DpE0L%2Bvfnzcl9qcEbbuPnC7JiMnu5dzQWuImkkT%2Fa4wgo8vHhr4jPP6rQjPxN6NBSv5ulNUhyHQDJX1jwNvuQMK%2FRwswGOqUBbOwrfNpZkoguc6bv5cUw9vziEvnHH6dvYVdCQ%2F7sPux2lvBvbxvmJxHkXrq8szmGhXxwri19JZ8uYpdJyVDT9mUYtcaTYoU%2FSyhnStsYMoNDFMNcBE%2Bkm54YpM6j27YEUIE1CV8Zake402wBwW9VU0H4SsS5%2B17TgclsfvJrRnKCFK8kinS5PmohBhzELS5CUygy8xapBcDp3xjLJm2l2tUNh6%2F0&X-Amz-Signature=c312a123bd51f1ffd7b73160863851a91f933cdfa1e0b6a29135ddf5a9062b5d&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject) Alright, enough talk. Let's get started. We've got a lot of ground to cover. ## What is Routing? Before answering that, let me ask another question: What happens when someone visits your application? To keep things simple, let's ignore the load balancers, web servers, proxy servers, etc. and just assume the request hits your Rails application server. What happens when that request reaches your application? The very first thing that needs to happen is Rails needs to figure out which part of your codebase it should send (or route) that request to, so you can return a meaningful response to the browser. And here lies the answer to the question: What is Routing? In simple terms, > Routing is the process that determines what code in your application needs to run based on the URL of the incoming request. Router is one of the first components to run when an HTTP request hits your application. It inspects the incoming URL and directs it to the appropriate Ruby code. Typically, this code will be an action method inside a Rails controller. ![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/46f39609-fed8-4830-9723-04387868d0ea/router-1.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466VUZRSXRA%2F20260214%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260214T174504Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEEaCXVzLXdlc3QtMiJHMEUCIDivo%2FEFqednkfb7LN%2BHDVKqHG%2Bq8FLChk0G%2BKihCk8lAiEA3cScUo%2BDFH2rrV7ODEHVIeeglTI4lPvNPRtrqGrq8gUq%2FwMIChAAGgw2Mzc0MjMxODM4MDUiDFtkPUq33EeMJ%2BtFDSrcA%2FdRZhhZhejXSHxGcQZE0m%2BkJRJ89nMPLkxHDucmelP1QejRbbRCHETgN2CbSsy2HnNTwyGocryNEmplPe31LSw1%2F66nauZqeUblMv73r7g9EA7hkAR%2BClOOApvyc78SN9ExyRpwpVvK45imTHia2miyp9kGiHLY1nVPXg4atNUj3quJr98A8lanjcUEACz4QZPuIRUT27jQ7wAAQt4BG%2BqVvjFSgVxPxZ88lg8BivoYh55k8%2BqLXr6NZNeD1lEFHTbw9oocJh7MdTowxmV5mBF0LjBPqt9O1vY321QN74BdPOqsFP4%2Bs7DsgeWRGRLbs9IjCYVJEqr%2FizGlPeJ4uXiun3ezKHycgR%2Bsnp3chmI%2BSHeqQ%2BgmY2EvGyjp9FQSXfgzgcg3c08%2BBbCurV2bpOzYqh4BExkhP2ZIHWP3lhFUkz1wxlSBXNTJBjKUWtiGA8ZOzTrlzUEipWS62tqvt6X92l7kWPz7z8O55keBcIoN4w%2FGlF64BOC9v9RSFGTvaoLxDLUtrVx6DmSzP0TTJW5VDRsWnVO7k%2BDQHp0DpE0L%2Bvfnzcl9qcEbbuPnC7JiMnu5dzQWuImkkT%2Fa4wgo8vHhr4jPP6rQjPxN6NBSv5ulNUhyHQDJX1jwNvuQMK%2FRwswGOqUBbOwrfNpZkoguc6bv5cUw9vziEvnHH6dvYVdCQ%2F7sPux2lvBvbxvmJxHkXrq8szmGhXxwri19JZ8uYpdJyVDT9mUYtcaTYoU%2FSyhnStsYMoNDFMNcBE%2Bkm54YpM6j27YEUIE1CV8Zake402wBwW9VU0H4SsS5%2B17TgclsfvJrRnKCFK8kinS5PmohBhzELS5CUygy8xapBcDp3xjLJm2l2tUNh6%2F0&X-Amz-Signature=46d16535a72919979baa9d33713d386605866b9fe7ffc7fc67f70bcb27a29626&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject) Rails Router As you can see, the router is the first point of contact for a request inside your Rails application. After inspecting the URL, the router decides to either: 1. forward the request to a controller action 2. redirect the request to another URL inside your application, or even to an external website or application 3. filter and reject the request based on pre-defined constraints or conditions, e.g. if the user is not logged in, then do not show the /admin page. If the router can't find a route that matches the incoming request, it throws an error. You can think of the router as a black box with a clear input and an output. Upon receiving the incoming HTTP request URL as an input, the router outputs the controller action where that request must be dispatched. The Rails Router However, the router serves a dual purpose in Rails. In addition to routing the incoming request to a controller action, it can also dynamically generate the URLs for you, so you don't need to hard-code string URLs throughout your app. Not only this approach is more maintainable, it also reads well and makes your codebase more expressive. We'll explore the named routes in a later section of this post. Now that you know what routing is, let's try to understand how routing works in the Rails framework. ## Routing in Rails When you create a new Rails application, the application generator creates a config/routes.rb file which contains all the routes for your application. This gives a central place to look for all the paths and pages supported by your application. To configure the router, you add specific rules (called routes) in the routes.rb file. These routes tell the router how to map incoming URLs to specific controller actions. When a request on a specific URL endpoint arrives (e.g. app.com/pages), the router first finds the route which matches the URL pattern (pages). It does this by going through the routes in the order they're defined in the routes.rb file. As soon as it finds a matching route, the search ends. Each route has a corresponding controller#action method associated with it which gets called to generate the response HTML for that request. The most basic way to define routes in Rails is to use the match method. For example, the following route instructs Rails to direct any POST request with the URL posts/publish to the publish action method on the PostsController class. ```plain text match "posts/publish/:id", controller: "posts", action: "publish", as: "publish", via: :post ``` > Note: If you were expecting to see a shorthand route like get posts/publish => posts#publish, I'm intentionally taking the difficult and longer route (no pun intended!) with the match method, as it will give you a deeper and better understanding of routing. This single route contains multiple components that we'll examine in this post. - the incoming URL path to match: posts/publish - which controller and action this request should be routed to, i.e. the publish action on PostsController class - the name for this route: publish, it generates URL helpers named publish_url and publish_path that you can use in your code. - HTTP verb this route corresponds to :post - segment key :id which acts as a variable placeholder for the ID of the post. As you can see, the router offers a rich domain-specific language (DSL) to express a lot of information with a concise syntax. And this is still a long-form version that you'll rarely use!! As you'll soon learn, Rails uses convention-over-configuration to even shorten the above route without affecting its expressiveness. For example, the above route could be simplified to: ```plain text post "posts/publish/:id" => "posts#publish", as: "publish" ``` And it keeps getting better. So that was a brief introduction to the routing process and the Rails router. Now let's inspect the internals of the router to understand what's going on behind the scenes. Trust me, this will be really useful as you learn more advanced features of the router later in the article. ## Digging Deeper: Router Internals > In this section, we'll explore how Rails implements the beautiful routing DSL behind the scenes. This is going to be a bit technical, but nothing too complex. Let's revisit the config/routes.rb file we saw earlier. When you create a new Rails app, it automatically creates a /routes.rb file in the config directory. All it contains is a single call to the draw method on the object returned by the Rails.application.routes method. Additionally, it takes a block containing the routes for your application. ```plain text Rails.application.routes.draw do # application routes go here... end ``` To understand how the above method works, we need to take a short detour and understand Ruby's instance_exec method defined on the BasicObject class. ### Understanding How instance_exec Works The instance_exec method takes a block and runs that block in the context of the object on which it's called. Inside the block, it sets self to that object, so you can access its instance variables and methods. ```plain text class Company def initialize(name, product) @name = name @product = product end def info "#{@name}: #{@product}" end end microsoft = Company.new 'Microsoft', 'Excel' microsoft.instance_exec do puts info # access instance method puts @name # access instance variable end # Output: # Microsoft: Excel # Microsoft ``` You might wonder what's the purpose of using instance_exec when you can just call the method directly on microsoft. You are, of course, right in this example, but, as we'll soon see, the most important benefit of instance_exec method is that it allows you to pass a block and run it later in the context of an object. This is really useful technique for creating special-purpose DSLs. Not sure what I mean? let's revisit the routes.rb file. ```plain text Rails.application.routes.draw do end ``` The Rails.application is an instance of the Application class which inherits from the Engine class. The Engine class has a routes method which returns an instance of the RouteSet class. ```plain text # railties/lib/rails/engine.rb def routes(&block) @routes ||= ActionDispatch::Routing::RouteSet.new_with_config(config) # ... @routes end ``` It means that the block passed to the draw method in the routes.rb file is ultimately received by the draw method on the RouteSet class. This method passes that block to the eval_block method, as seen below. ```plain text # actionpack/lib/action_dispatch/routing/route_set.rb def draw(&block) # ... eval_block(block) # ... end def eval_block(block) mapper = Mapper.new(self) mapper.instance_exec(&block) end ``` As you can see, the eval_block method first creates an instance of the ActionDispatch::Routing::Mapper class and executes the block within the context of that instance. What it means, is that any code we write inside the block passed to the Rails.application.routes.draw method will be evaluated as if it was written inside the Mapper class. 💡 If this is already started to get overwhelming and you feel like giving up, DON'T. Clone the Rails repo, open it in the editor, and trace the call stack with me. It's a fun exercise and you'll learn a lot along the way. For example, the two pieces of code below are similar. However, the first version just reads better. It feels like a programming language specifically designed for the routing domain. ```plain text Rails.application.routes.draw do root 'application#index' get 'posts/publish', to: 'posts#publish' end # is similar to routes = ActionDispatch::Routing::RouteSet.new mapper = Mapper.new(routes) mapper.root 'application#index' mapper.get 'posts/publish', to: 'posts#publish' ``` This also means that whenever you see a method in your routes file, you know where to look for its definition. It's the ActionDispatch::Routing::Mapper class and its included modules. For example, the commonly used get method is defined in the Mapper::HttpHelpers module. ```plain text def get(*args, &block) map_method(:get, args, &block) end ``` This gives us a solid base to explore the Rails Routing API. We'll start our investigation with the match method. ## The match Method > In this section, we will explore how the ```plain text match ``` Once you really understand the match method and its options, the rest of the routing methods and shorthands become very easy to understand. To understand the Rails Router API, it's essential to learn the match method, which forms the core of the Router DSL. All helper methods like get and post use match under the hood. Additionally, most of the shorthand syntax such as scopes and constraints use the options provided by match behind the scenes. Here's the basic API of the match method, which is defined inside the Mapper class. ```plain text match(path, options) ``` The first parameter path tells the router what URL pattern you wish to match. It can be either a String or a Hash. - The string version specifies the URL pattern, e.g. /posts/publish/:id - The hash matches the URL pattern to a controller#action pair, like '/posts/1' => 'posts#show'. The second parameter options is a hash containing additional data required by the router to decide where it should redirect this request. You can also pass any custom data, which gets passed to the params hash accessible in the controllers. In its simplest and most explicit form, you supply a string URL pattern along with the name of the controller and the action via options. ```plain text match 'photos/:id', controller: 'photos', action: 'show', via: :get ``` If you don't want to pass them separately, a string in the form of controller#action is also allowed with the :to option. ```plain text match 'photos/:id', to: 'photos#show', via: :get ``` Finally, the hash version even simplifies this: ```plain text match 'photos/:id' => 'photos#show', via: :get ``` As we'll learn later, you'll often use shortcuts like get and post instead of directly using the match method. However, match comes in handy when you want to match a route for multiple HTTP verbs. ```plain text match 'photos/:id', to: 'photos#handle', via: [:get, :post] ``` Note: the :via option is mandatory for security-related reasons. If you don't pass it, the router raises an ArgumentError. ### Options Available to Match The options hash helps the router identify the action to which it should pass the incoming request. Here are some of the important options that you can provide to the match method. 🤔 Keep in mind: Any custom data that doesn't match the existing option gets passed as-is to the params hash, which you can access in the controllers, using the params hash. :controller and :action The controller and action keys are used together. They specify the name of the controller and the action you want to route this request to. ```plain text match 'home', controller: 'application', action: 'home', via: :get ``` :to If you don't want to pass the controller and action separately, the :to option lets you pass the controller#action as a string. ```plain text match 'home', to: 'application#home', via: :get ``` Note: You're not restricted to using only controller actions for incoming requests. The :to option also allows you to point to a Rack endpoint, i.e. any object that responds to a call method. This is really useful for quickly testing routes without creating new controllers, actions, and views. ```plain text match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get match 'path', to: RackApp, via: :get ``` If you're curious to learn more about Rack, check out this article: ![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/b91e0174-f77e-4988-b4d7-e432bedff1bf/Rack-2.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4663FTAEH5Y%2F20260214%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260214T174506Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEEaCXVzLXdlc3QtMiJGMEQCICamgZfjEd8vcTpWmLe1yAV1wB4vFMC7uq8M98yAyl04AiBQZNMuCaR4ho1IjBmK7GijvJxe%2FazUKzvKqK03gy87zCr%2FAwgKEAAaDDYzNzQyMzE4MzgwNSIMitJgbS5edvIJR1zIKtwDA2qEc8edrGFr461riwwqAF%2BbQiN4KjwaBGPK8AgtTD2KLJ96hLV9GSSuyiIADI3SsV%2BDOU1jMu3EbkG8eTRRxdpJZj9t9psjLINVO0eY0iypgcMEZsAmue71ec7NtLyGLF6z8ZkEuxclKiGxidmFY%2FTGWU6Gz0LH6NgWLToSkM8hOuX2ZlL48HMptQ1Zwh0gphcmBGPpfLar%2FVxuSjMr2jwouF1x65FRDDWfxNAzfUNtlm2i9DhsgJiSxdnqT3wEEy7oWLIKSH%2Bx7FVptKCxnLwfe%2FnxFUSwU%2FsRZ7NYnp3Fx%2B19lD47MH63JjjV%2BRTj%2BrgKLoMWP%2BlLjWSUiwFbzUn9Qqz%2FkAL6KHmbBClybWRZYMN0P4kmHBb16cwZH5%2FVsQTpIlimaSDrmtS%2BNbsO5%2FNfbvWtCU20pOARe22DQ2yFmUbATzdP8MtoOYCmr0ZYUj2XnsT8oyx2Uj%2BrEEP3282ehIfuMPBChRj4tdRvT%2FM60mjqqohk3diEWuP3R4VKgQBjpcUt4Es5azLhfFrl%2F6p59xb93hrHEbvGcvqj8Bm5gHwug%2Fu%2BH5XV26yS8kV8cFwyNwWUY0lFEcx58JU7ewU5YAbzx1mC7Jv0VU9hCfLJpRiWGEvo%2BeKccV0w7NHCzAY6pgHwFi4LVEvI%2Bbzxf68dFQDPrLqLSQCvN1Pp5kJMRThcLDFAuWaGt4igbqsvAIEctzRnXWtUbi26IPd6RFhNrmDnPxeQFEQGxbwGau12qvu4B79zRyZpat7zln%2BQnfFzirX0NmEbJsVQxtTqNzz%2BLEMzvb6mqNunQmYCMA9OdegCo5Coav0j8RYqgv7QzchCixcZe3ANmY89nc9%2B5vIG6gpAvsoAVEDP&X-Amz-Signature=06657212c1121493196d96b77dbd1bc34f47ead80fd7ed3833ac3abbc88f1699&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject) via Lists the HTTP verb for this route. You have to pass at least one HTTP verb, for security-related reasons. ```plain text match 'path', to: 'controller#action', via: :get match 'path', to: 'controller#action', via: [:get, :post] match 'path', to: 'controller#action', via: :all ``` module Use this if you want namespaced controllers. The following route directs the request with /posts URL to Blog::PostsController#index action. ```plain text match '/posts', module: 'blog', controller: 'posts', action: 'index', via: :get ``` as This option provides the name for the generated URL helpers. The following route creates the helpers named blog_path and blog_url. ```plain text match 'posts', to: 'posts#index', as: 'blog', via: :get ``` constraints If you want to put more restrictions on what URL patterns a route should match, provide this option with a hash of regular expressions. It can also take an object responding to matches?. ```plain text match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get ``` defaults Sets the default value for a parameter. The following route will set the value of params[:author] to 'Akshay'. ```plain text match '/courses/publish(/:author)', to: 'courses#publish', defaults: { author: 'Akshay' }, via: :get ``` For a complete list of options, check out the API docs for the match method. Most of the routing methods you'll use are just shorthands using various combinations of the match method's options. Next, we'll learn about the shortcuts Rails provides, so you don't need to use the common options such as :via, :module, etc. all the time. ## HTTP Method Shorthands So far, we've been using the match method, passing the HTTP verbs like :get and :post with the :via option. Can we do better? 🧐 YES! As you may already know, Rails provides simplified shorthand methods like get and post which implicitly assume the HTTP verb, inferred from the method name. For example, the following three routes mean the same thing. ```plain text match 'posts', to: 'posts#index', via: :get # == get 'posts', to: 'posts#index' # OR get 'posts' => 'posts#index' ``` There is a method for each HTTP verb. - get - post - patch and put - delete - options All of them accept the exact same options that we saw for the match method. For the remaining post, I'll use the above shorthands instead of the more explicit match method, unless needed. Next, we'll learn another fundamental topic in routing, segment keys. ## Segment Keys So far, we've seen route paths such as posts/:id. If you're new to routing, you might be wondering why there are symbols inside the URL. These symbol-like terms are called segment keys, which make your URLs dynamic. Making a URL dynamic? What does that even mean? 🤔 It means that they allow the router to match both posts/1, posts/2 and posts/n, where n can be anything. 💡 A segment key is just a variable prefixed with a colon, just like a symbol, inside a URL pattern. It lets you capture the value inside the URL. Instead of hard-coding a URL which matches exactly one URL, segment keys allow you to match more than one URL. You can access the values of those keys in the params hash, e.g. params[:id]. Here's a segment key named :slug which lets you refer posts by their logical, dasherized name, i.e. ruby-on-rails. ```plain text get '/posts/:slug', to: 'posts#show' ``` No, not this slug 💡 A Slug is the unique identifying part of a web address. When you make a GET request to the URL matching the above route, e.g. /posts/understanding-router, Rails sets params[:slug] to understanding-router. You can access the slug value inside the controller. ```plain text class PostsController < ApplicationController def show @post = Post.find_by(slug: params[:slug]) end end ``` Note: If you use segment keys in a route, you have to provide their values when generating the URLs with the Rails helpers. ```plain text <%= link_to 'Understanding Rails', controller: 'posts', action: 'show', slug: 'understanding-rails' %> ``` But what if some of my URLs don't need a segment key? You can mark a segment key as optional by wrapping it in parentheses. The following route matches both /posts/understanding-rails and posts. ```plain text get '/posts(/:slug)', to: 'posts#show' ``` That's nice, but how can I restrict the slugs to only be text and not numbers? This is where the constraints option that we saw earlier, comes into the picture. You can add restrictions on the segment keys by providing a constraint. The following route matches only those URLs where slug is a word consisting of letters between a to z. ```plain text get '/posts/:slug', to: 'posts#show', constraints: { slug: /[a-z]+/ } # or you can also use a shorthand get '/posts/:slug', to: 'posts#show', slug: /[a-z]+/ ``` But I also want to access the request object... For more powerful constraints, you can pass a lambda to :constraints which receives the request object. ```plain text get '/posts/:slug', to: 'posts#show', constraints: ->(req) { req.params[:slug].length < 10 } ``` But, my constraint logic is too complicated, and I hate putting it right there inside the route... Rails allows you to extract the complex logic to a class that has a matches(request) method on it. Put that class inside the app/constraints directory, and Rails will pick it up without a hitch. ```plain text get '/posts/:slug', to: 'posts#show', constraints: SlugLength.new # app/constraints/slug_length.rb class SlugLength def matches?(request) request.params[:slug].length < 5 end end ``` Don't forget to return a boolean result from the method, depending on whether the URL matches the pattern. Alright, that's enough about the segment keys. Next, we'll tackle the named routes, which give you sweet helper methods to generate the URLs on the fly. ## Named Routes So far, we have seen only one responsibility of the router: take an incoming HTTP request and route it to the piece of code (controller#action) that handles it. - Input: HTTP Request URL - Output: Controller and Action However, the Rails router works in the reverse way, as well. Given the route name (along with the corresponding data or model), the router can generate the resulting URL. Consider the following route. ```plain text get 'post/:id', to: 'posts#show' ``` When you make a request to post/10, the router invokes the show action on the PostsController class. What if you want to link to a post? How would you go about creating the URL? Well, the simplest solution is to interpolate it, passing the post id. ```plain text link_to post.title, "/post/#{post.id}" ``` But hard-coding URLs into your application is not always a good idea. When your URL changes, you'll have to search the whole codebase to find and replace the old URLs. If you miss one, your code won't complain until the users click the link, only to be shown an error page. To fix this, you could provide the controller and action name along with the post id, so Rails could infer the URL. ```plain text link_to post.title, controller: 'posts', action: 'show', id: post.id ``` However, there's an even better way with the named routes, where you can name a route by providing the :as option. When you name a route, Rails will automatically generate helper methods to generate URLs for that route. These methods are called name_url or name_path, where name is the value you gave to the :as option. Let's rewrite the above route and give it a name. ```plain text match '/post/:id', to: 'posts#show', as: 'article' ``` The router creates two methods named: article_path and article_url. The difference between the two methods is that article_path returns only the path portion of the URL, whereas article_url returns the complete URL, which includes the protocol and domain. ```plain text article_path(4) -> `/posts/4` article_url(4) -> `https://blog.com/posts/4` or `https://localhost:3000/posts/4` ``` Once you have named a route, you can use it as follows: ```plain text link_to post.title, article_path(id: post.id) ``` It's less code to write, and it also avoids the problem of hard-coding the URL. In fact, we can skip the id key, and just pass its value. ```plain text link_to post.title, article_path(post.id) ``` We can go even further. Rails allows you to pass any object to the named helper methods as long as those objects have a to_param method on it. ```plain text class Question def to_param '100' end end article_path(Question.new) # '/post/100' ``` What's more, all the active record objects have a to_param method out of box, which returns the object's id (you can customize this). ```plain text link_to post.title, article_path(post) ``` This version is more readable, concise, and also avoids the problem of hard-coded URLs. Pretty cool, right? Now we're going to tackle one of the most practical concepts in routing that you'll use every day on your Rails apps: resources. ## Resourceful Routes 💡 Resourceful routing allows you to quickly declare all of the common routes for a given resource. A single call to resources declares seven routes for index, show, new, create, edit, update, and destroy actions. In addition to having multiple resources, there's also a singular form of resourceful routes. It represents a resource that only has one, single form. For example, a logged-in user's profile. You can use the The concept of resources is very powerful in Rails. With a single call to a method named resources, Rails will generate seven different routes for you, saving you a lot of typing. But saving a few keystrokes is just the cherry on top. The biggest benefit of using resourceful routing is that it provides a nice organizational structure for your application. Let's learn how. ### What's a Resource? A resource, like an object, is one of those concepts that's difficult to put into words. You intuitively understand them, but you can't express them. I'll stick to how Roy Fielding, the inventor of REST, defined them in his dissertation. > A resource R M R (t) t Is your head spinning? 🤯 Mine too. Let's try the other definition he gives in the same section. > A resource is the key abstraction of information. Any information that can be named can be a resource. Much better, right? 💡 I think that definition captures the essence of resources really well. Any concept in your application domain can be a resource: an article, a course, or an image. It doesn't have to be a physical entity, but can be an abstract concept such as a weather forecast or an SSH connection to connect to a remote server. The main point is: you have total freedom in choosing whatever idea you are using in your application as a resource. But Why do I need a Resource? Good question. You might be wondering what this discussion of resources has to do with routing. Well, resources are fundamental to routing. They allow you to reduce seven different routes into a single method call. If you've used any software application, you must have noticed that most apps have a few common patterns. Most of the time, you're: 1. Creating some data (publishing a post, uploading an image) 2. Reading that data (viewing the tweets, listening to podcasts) 3. Updating the data (changing source code on GitHub, editing the post) 4. Deleting the data (removing a folder from Dropbox, deleting your profile picture) No matter how fancy your application is, I guarantee it will have some form of these four actions that your users can take. Collectively, they're called CRUD which stands for Create, Read, Update, and Delete. 💡 A resource is an object that you want users to be able to access via URI and perform CRUD operations. For each of these actions, your application should have a route. Assuming you're building a course platform, your routes file might have the following routes. ```plain text post 'courses' => 'courses#create' get 'course' => 'course#show' patch 'courses/:id' => 'courses#update' delete 'courses/:id' => 'courses#destroy' ``` In addition to these four routes, you'll typically have three more: - one to see all the courses - two to fetch the pages that let the users create and modify the courses. ```plain text get 'courses' => 'courses#index' get 'courses/new' => 'courses#new' get 'courses/:id/edit' => 'courses#edit' ``` So we have a total of seven routes that are common to most resources in most web applications. If you are building a blog, you'll have these seven routes for posts. If you're building Instagram, you'll have the same routes for images. No matter the topic, you'll most likely have some subset of these seven routes for each resource. Since this pattern is so common, Rails introduced the concept of resourceful routing, whereby calling one method named resources and passing the name of the resource, i.e. :post, :course, :image, etc. you get these seven routes for free. Rails will automatically generate them for you. ```plain text Rails.application.routes.draw do resources :courses end ``` In addition, resourceful routing also generates sensible names for these routes. For example, you'll have names like course_path, edit_course_path at your disposal without providing the :as option. The following table summarizes what you get with a single call to resources. The values under the Prefix column represent the prefix of the route's name, e.g. new_course gives you new_course_path and new_course_url helpers, and so on. ```plain text ➜ bin/rails routes -g course Prefix Verb URI Pattern Controller#Action courses GET /courses(.:format) courses#index POST /courses(.:format) courses#create new_course GET /courses/new(.:format) courses#new edit_course GET /courses/:id/edit(.:format) courses#edit course GET /courses/:id(.:format) courses#show PATCH /courses/:id(.:format) courses#update PUT /courses/:id(.:format) courses#update DELETE /courses/:id(.:format) courses#destroy ``` > Resourceful routing allows you to quickly declare all of the common routes for a given resource. A single call to ```plain text resources ``` ```plain text index ``` ```plain text show ``` ```plain text new ``` ```plain text create ``` ```plain text edit ``` ```plain text update ``` ```plain text destroy ``` In addition to having multiple resources, there's also a singular form of resourceful routes. It represents a resource that only has one, single form. For example, a logged-in user's profile. You can use the resource method for this. ```plain text resource :profile ``` It creates the following routes: ```plain text new_profile GET /profile/new(.:format) profiles#new edit_profile GET /profile/edit(.:format) profiles#edit profile GET /profile(.:format) profiles#show PATCH /profile(.:format) profiles#update PUT /profile(.:format) profiles#update DELETE /profile(.:format) profiles#destroy POST /profile(.:format) profiles#create ``` Note two important differences from the plural version: 1. The route to show all profiles is missing. Since we only have a single profile, there's no point in displaying all. 2. None of the routes contain an id segment key. We don't need it to identify a profile as there's only one profile. What if I don't have all seven routes? Sometimes, you don't want all seven routes that resources method creates for you. In such cases, you can pass the only or except options to filter the ones you need or don't need. ```plain text resources :courses, only: [:index, :show] resources :courses, except: [:delete] ``` You can even nest multiple resources, but I'll leave that topic for a different post, since this one is already getting bigger than I anticipated. ## Non-Resourceful Custom Routes A single call to resources in the routes.rb file declares the seven standard routes for your resource. What if you need additional routes? Don't worry, Rails will let you create custom routes that don't fit one of the above seven routes with the member and collection routes. The member route only applies to a single resource, whereas collection will apply to a group of resources. ### Member Routes Let's assume that your users need to see a preview of the course before purchasing it, and you need to expose that route on the courses/:id/preview URL. How should you go about it? Here's how you define the preview route on the CoursesController using the member block. ```plain text resources :courses do member do get 'preview' end end ``` The router adds a new route that directs the request to the preview action on the CoursesController class. The remaining routes remain unchanged. It also passes the course id in params[:id] and creates the preview_course_path and preview_course_url helpers. ```plain text preview_course GET /courses/:id/preview(.:format) courses#preview ``` If you have a single member route, use the short-hand version by passing the :on option to the route, eliminating the block. ```plain text resources :courses do get 'preview', on: :member end ``` Although this approach works fine, a better way is to create a new controller dedicated to previewing images. Typically, you should strive to restrict your controllers with the seven default actions. > What I’ve come to embrace is that being almost fundamentalistic about when I create a new controller to stay adherent to REST has served me better every single time. Every single time I’ve regretted the state of my controllers, it’s been because I’ve had too few of them. I’ve been trying to overload things too heavily. Full Stack Radio ### Collection Routes Let's say you want to search all courses and you want to expose it on the courses/search endpoint. You can add a new route for the collection of courses with the collection block. ```plain text resources :courses do collection do get 'search' end end ``` This adds the following new route. It will also add a search_courses_path and search_courses_url helpers. ```plain text search_courses GET /courses/search(.:format) courses#search ``` If you don't need multiple collection routes, just pass :on option to the route. ```plain text resources :courses do get 'search', on: :collection end ``` This will add the same route as above. To learn more about custom routes, check out this article: ![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/9a301027-05af-428b-addd-914345c22645/member_collection-1.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4665J6L6HRK%2F20260214%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260214T174507Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEEaCXVzLXdlc3QtMiJHMEUCIQCyp3PK95jgwz7A%2BzGNheyPx0FyBbZtn8%2FfqR5LDQ6jPAIgD%2BC%2BMLNFEDkmKcByJJ613gOiqI7gfHB46O5dsL%2FCZJQq%2FwMIChAAGgw2Mzc0MjMxODM4MDUiDLfouAaoi8z3ch0pWyrcAwJghDW%2BY0t0Xa21Wn2wDfjnyVxMxNbwXyr0kjq2ol2UlZ5MRdzvaBExwUp8j0RjM5J3RxcZEKBbzHu%2FVQmIF7nUDBuYEpVxK3oZbd%2BZx1OJRtAh4MCVp8lpRzc8%2BE2uu91Gaihm4%2FPa9twJfPDBmsi%2BPfeey8sdU7YNWQtMGqwAUyR1v6pfd6OvaxbIK6juCywJwBvdYNDVOW3Vj%2F6A1SiKMggIm45wO1nYwZ6sJ9nzj54QcVeuao%2BA3MISaBLqiwgPyf4iw6wSw8LiDqpLAvVr2HzvzKc5YKzfN7SjS%2F7mzfOL6mpl%2F7AuqlHV2RBoJcmUkQn26IvYhsDHc12UiQ2rLINgd112e%2BvyZ6TTxkTbzTc3vS89ztIptySbZ7Xe9QmZGUmzMaUswmaMEo0L8nHCHX3Z0qFvey4vNirps5EYy12q8BuJWtBcV2JFksO5UUEKlv3ANWIgCMk6pFuxX4ZVB10m2g8R2xxKaNi98%2BiuLIX4EPirqCLiG94ln4HlasgkrNivHKjgJ1Oic7LVlp6bvN%2BBzSMMkcAluNXTF25rcMqLkBHXzq2aaso1Zqu%2F6Sjecwkm8wNzotGqfLZAUaHEGiUigzfFl40K1lGsoPX57fSJ%2FAUe7BUL1prhMPzRwswGOqUB75IWnS8kfxJcb04iypb%2BOa9ienxcHLJT4yGxAwQgXBWpz5jwHJowprrlQfuC%2FKKZHwoZiK1nNHmWMqm13UZCo3nqxJXZFsKgQDqZ%2FtXYW38AQK04mUwP%2Bpap6mcRn1HTUaCHLYJxmZzecVVFeNWEg%2FymjQVKoFxdzjWtu0uKLj%2B0gLAUj%2BxVaiDEn3rc9Fj9X8rt2QmDEjU2u7ANDkv%2Bh2xGgTsH&X-Amz-Signature=febd72bfdb7164e5ec83079267751d622851f072a1fe799d8fd3b3abc049b38f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject) As we saw, the Rails router is very powerful and highly flexible. Routing is also a two-way street: match URLs to controller actions, and generate URLs dynamically. Additionally, the router uses powerful conventions such as resourceful routing to automatically generate sensible routes for you. Alright, I will stop here. There are a few other shortcuts and helper methods that we didn't cover in this article, but I've already written more than 5000 words non-stop, and this article is way, wayy, wayyy bigger than what I first thought it would be. If you're still reading, I sincerely hope that you learned at least one new thing about the Rails router that you didn't know before. Expect a few posts in the upcoming weeks that will explain the remaining topics. But what we've covered so far should be more than enough for you to get started and be productive with using various routing features, both basic and advanced, in your applications. Here're some additional resources, if you want to learn more about routing. ### Further Resources - Rails Routing from the Outside In - Rails API: The Mapper Class - Routing in Laravel (to learn routing from a different framework's perspective) - Roy Fielding's Dissertation That's a wrap. I hope you found this article helpful and you learned something new. As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you. If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you. ### Posted by Akshay Khot I'm a software developer living in beautiful Victoria, BC. This blog is my attempt to share my learnings and to spread the joy of programming in Ruby with the world.

Visibility

Visible to everyone

Reading Status

Related Bookmarks

My Note


Saved!

Annotations

Export as Markdown
+ Annotate selection

Add Annotation