A common pattern seen across Rails applications is the following
update action in controllers
Convention over configuration is a very powerful way of getting developers up the curve quickly. However, it can lead to a certain amount of automatic programming which obscures the possibility of creating beautiful APIs with our standard RESTful actions. In the above case, years go by before Rails programmers even begin to notice that the tight coupling between the route, the controller action and the model is just an illusion. Infact, resource != controller + model!
A resource is a completely different layer of abstraction than the controller or the model. Controllers and models are elements of an MVC framework. Resources are the nouns in the language of the web. We use Models and Controllers to implement resources in our web application, but breaking the coupling between routes, models and controllers is one step in the direction to Rails nirvana.
Why is this important?
I’ve found it very helpful to adhere strictly to a RESTful architecture. This means thinking of everything as a resource that responds to the six default RESTful actions. This helps to keep your interface really clean, your controllers really lean and simple to test and pushes your application logic into the model layer where it rightfully belongs.
A “for example”
Let’s take a look at a simple case like a board game. Once a game is set up, the following things can be done to it
- a player can join
- a player can leave
- a player can make a move
- a player can resign
All of these constitute an “update” to the game resource. It might be tempting to start adding controller actions like
resign. Let’s see what happens if we do.
Interesting. Our controller is exploding and is not very DRY at all. Is there some way we can be more RESTful about this? Here’s technique number 1
Decouple your Controllers and your Models
Rails nested resources to the rescue! We declare Players and Moves as nested resource of Game
Please note, there is no model called Player. A Player is nothing but a User. Secondly
MovesController#create doesn’t call
@move.save. It calls
@game.add_move and all the corresponding logic that truly belongs in game is being called from the Moves controller. Thus, we’ve created a resource called Player out of the model user, and our Moves resource uses the Game model API to add moves to the game. There is no spoon!
Decouple your API from your actions
So let’s say even after thinking really hard you can’t find a resource that can give you the API you want. An example? Oh, let’s say - an article going through the various states of “moderated”, “published”, “unpublished” etc. Instead of adding various methods like
unpublish, we can decouple our API from our actions and drive all these state changes through the
update method. i.e.
Note, we’ve replaced the
update_attributes method with
Article#update which can contain all the convoluted logic to deal with the parameters sent. As you can see, we’ve decoupled our API from our controller and our model. The shape of the API as exposed to the outside world has very little to do with the internal implementation in terms of models.
Your API is the little jewel of your app. Users of your app will judge you based on the intuitiveness and consistency of your API. Therefore, it is not a good idea to shoehorn your API to fit current Rails conventions. Rather, you can and should try as far as possible to decouple your API from its implementation.
Another advantage - if your app is to work at any kind of serious scale, at some time you are going to have to consider a polyglot implementation. Maybe you hand over to a service written in Go or call an external web-service. At this point, a clean separation between your app’s API and its implementation will really stand you in good stead.