When designing a resourceful object hierarchy in Rails, often a single model has meaning in multiple different contexts. Take for example an application managing rental properties. This application has two main models, Properties and Tenants, which have obvious relationships with each other. These models also have value independent of this relationship.
At WhitePages.com we are often modeling entities that exist in many different contexts, somewhat like a graph. The question then becomes, how can I model a relationship such as a has_many/belongs_to using resourceful routes, while still allowing direct root access to a model, or potentially access through several different model connections? Can I access the same controller resource through different routes? The answer is yes. Lets take a look at a basic example to see how we can make this happen.
Let’s start with the following models:
[gist id=5573460 data-gist-hide-footer=”true”]
The default scaffolding generator gives us the following routes with both resources at root:
[gist id=5573423 data-gist-hide-footer=”true”]
These routes are workable, but do not correctly illustrate our designed model. Modeling the routes to match our belongs_to/has_many relationship, we would generate the following nested routes in config/routes.rb:
[gist id=5573426 data-gist-hide-footer=”true”]
Nesting our resources like this, we now have the following routes:
[gist id=5573428 data-gist-hide-footer=”true”]
Much better! This routing structure allows us to access our list of properties at /properties, a specific property at /properties/:id, a list of a properties Tenants at /properties/:property_id/tenants, and a specific tenant at /properties/:property_id/tenants/:id. This now models the relationship we’ve created between properties and Tenants. The only problem now is that our tenant controller does not know how to use the :property_id parameter correctly to set our scope. We need to make a few modifications to make use of the provided property_id. The majority of our changes are in ‘index’ and ‘new’.
[gist id=5573433 data-gist-hide-footer=”true”]
You can see from the code above that we are now using the property_id parameter provided by our route to inform ActiveRecord of the scope of our search as well as initializing new models. Hooray! But this isn’t the goal we are looking for. What we want is the ability to see Tenants in both the context of a property, but also, to view Tenants without any context. This will allow us to view all our Tenants without regard to what Property they are assigned to, and provide a Tenant details path without having to find through the Property relationship. Lets start with adding the rout to our routes file:
[gist id=5573438 data-gist-hide-footer=”true”]
This gives us the following routes:
[gist id=5573442 data-gist-hide-footer=”true”]
Now we have both root access to our Tenants as well as routes through our properties relationship! Success! Well, not yet. If we use any of the root routes to Tenants, our controller is going to throw an error, as property_id is not being sent along. Let’s fix that right now:
[gist id=5573445 data-gist-hide-footer=”true”]
Now we can declare success. We now have the ability to access all our Tenants through a root route, as well as through their defined relationship through Properties using a nested route.