Organizing Routes In Rails
Tags: rails, ruby, architecture
Published: 2019-06-04 15:17:09

I love Ruby on Rails, but one thing that bothered me a lot was how quickly the `routes.rb` file can get unwieldy. When I began creating version 2 of an API I was making, this issue presented itself. Thankfully, there's an easy way to handle this with a little knowledge of how ruby can reference extended objects.

Consider the following example routes file:

```ruby
Rails.application.routes.draw do
  root to: redirect("https://www.example.com/show")

  get "/ready", to: proc { [200, {}, [""]] }


  namespace :api do
    namespace :v1 do
      # about 31 routes
    end
    namespace :v2 do
      # about 36 routes
    end
    # v3, v4, v5 and so on
  end
end
```

That can get extremely long and drawn out, especially if you can't drop older versions of the API for an extended period of time. We can break this up easily by utilizing some events and methods of ruby's `extend` class and break these versioned routes into their own files.

## Folder and File Structure

The `routes.rb` file is located in the `config` folder of a Rails app. Create a new folder in the same place: `config/routes`. We'll store the new route extensions here.  

Inside that folder, I want to name the file the same thing as the namespace that will be in the file. The first route collection I want to siphon off is `:api :v1`.  Name the file `api_v1_routes.rb` to match our module and namespace structure.

Open the new file and create a module called `ApiV1Routes`.

```ruby
module ApiV1Routes
end
```

## Extended

When you extend an object, ruby has a helpful event called [extended](https://ruby-doc.org/core-2.6.2/Module.html#method-i-extended) that allows you to execute some code after the object is... extended.

```ruby
module ApiV1Routes
  def self.extended(router)
  end
end
```

In the above method, when we create a class that extends this particular module, the extended event will fire with a reference to the object that is being extended. In our case, the extended method is going to receive the `router` as a parameter.

## Execution In Context

Now we have a module that can get a reference to the rails router when it's extended, how do we add routes to the router from here? 

All objects in ruby have a method called [instance_exec](https://ruby-doc.org/core-2.6.3/BasicObject.html#method-i-instance_exec). This takes a block and executes the block within the context of the caller.  In our `ApiV1Routes` module, this looks like the following.

```ruby
module ApiV1Routes
  def self.extended(router)
    router.instance_exec do

      # add routes
      namespace :api do
        namespace :v1 do
          # 31 v1 routes
        end
      end

    end
  end
end
```

According to the documentation for `instance_exec`, the `self` is set to the calling object so we have access to all the object's instance variables and methods.

>In order to set the context, the variable self is set to obj while the code is executing, giving the code access to obj’s instance variables.

The routes are being added as if we were within the `routes.rb` file itself!

## Tying it all together.

Now that we've created the module we need rails to load these route modules, otherwise we won't be able to reference them in our code.

Open the `application.rb` file, which should also be located in the `config` folder. Add the following line of code:

```ruby
# Load split router files
config.autoload_paths += %W(#{config.root}/config/routes)
```

This tells rails to load the files in the routes folder, since `config/routes` isn't a default that rails will autoload for you.

Back in the `routes.rb` file, at the top add `extend ApiV1Routes` before everything else in the `do` block.

```ruby
Rails.application.routes.draw do
  extend ApiV1Routes
  extend ApiV2Routes

  # all our other routes
end
```

That's it!  Any routes you've added this way should now be able to be used!  Repeat this for any clearly defined route structures you'd like to separate, then add the `extend ModuleName` to the `routes.rb` file.