Ruby on Rails is known as a framework which allows the developer to get to results very fast.
It does that using a combination of two strong principles:
- Convention over configuration – Rails makes a lot of assumptions on how our code is built and by doing that it saves us time in two aspects:
- If we fulfill those assumptions a lot of code will be spared for us.
- The developer does not need to decide how to design the code (at least at bootstrap stage). The decisions are already made by Rails and we only have to fill the gaps.
- A great community which supplies almost any imaginable functionality a developer would like to add to the web app.
These two points are some of the major reasons we all love Rails, but it does come with a price tag:
In many Rails applications a decent amount of the code does not exist in the context of the project. If we’re lucky we can find the missing code on GitHub and if we’re less lucky we may have to actually dive into the local gem source files and look for it.
I don’t know about you, but when I find a bug in my app, I prefer that as much as possible amount of the code will be immediately accessible to me for debugging.
Spread code can make debugging our app very hard since code flow is really hard to follow, and a lot of the time the code is dynamically generated, which makes it non-existent for debugging purposes.
There are some cases when I’m willing to make this trade-off pretty easily. One of these is Devise, a great gem for authentication. I have two reasons that made my decision easier:
- The domain: I’m not a security expert, and I have no doubt that a component that is maintained and improved for years is better than anything I can come up with.
- Black-Box attitude: In most cases, Devise is used as it is and code overriding is done only in rare cases. I never found myself debugging Devise’s code.
There are, of course, some trade-offs which I don’t think worth the while and can even do more harm than good in future retrospective. I’ll cover two of those:
While SimpleDelegator is not a gem, I consider it as a huge code-messer with clear, more adequate, solutions.
For those who aren’t familiar with SimpleDelegator I’ll show the simplest usage scenario:
Since A inherits from
SimpleDelegator it allows it to get another object in its initializer, so that missing methods of A will be delegated to it.
On a first look there seem to have no problem with this, but on runtime things could get really ugly.
The fact that you can’t look at the class you’ve instantiated (A) and tell where the method
do_something runs is nothing less than awful.
Imagine yourself that we have 5 classes, on separate files, which implement the method
do_something. Now we have two options to know where
obj.do_something is running:
1. Find the class which was passed to the A constructor while instantiating
obj at runtime.
2. Put a
debugger statement in each of the classes implementing the method
I certainly prefer the following option:
What we have done here is to mix the module B into class A. This means that B’s methods are available for A instances.
The large difference is that now, when we look at
obj.do_something, at least we have a search point to start from:
We know that
obj is of class A, and when we open class A’s definition a direction is given – one of the included modules or somewhere up the inheritance tree (although in this example A does not inherit a class).
It makes our code a lot clearer, and following it bearable.
SimpleDelegator does not give you this direction and leaves the developer to shoot in the dark trying to understand the code flow of the app.
Unless you really have to dynamically bind those methods to the object you’d better stay with defining it statically.
InheritedResources is a gem which helps us make our controllers DRY’er. It can come instead of the default code generated for a controller using the scaffolding mechanism.
InheritedResources::Base which dynamically populates it with some variation of the
rails generate scaffold_controller Porjects
It spares us the boring and repetitive definition of basic resourceful controllers with one magic inheritance. Having said that, I do think that sometimes we forget what our goal is and what are our means to get there. Our goal is a clearer, easy to maintain code. DRY is just one of the means to get there, it is definitely not the goal itself.
In the case of InheritedResources I don’t think the goal is achieved. The code is definitely not clearer since it does not exist, and it is not easier to maintain since we have to get familiar with another mini-framework to get things done. Also, debugging controllers which inherit
InheritedResources::Base is, with no doubt, a harder task than debugging plain written controllers.
If we examine InheritedResources against the two considerations which made me decide to use Devise it fails on both:
- The domain: Rails controllers are a domain that every Rails developer must have full control over. You cannot be a Rails developer without having full knowledge of how controllers work. InheritedResources does not spare us domain knowledge we could have lived without.
- Black-Box: Most of the controllers do not stay in the simple form of resourceful actions. They are often changed to fit our application and carry app-specific tasks. When we use InheritedResources we will probably find ourselves trying to alter its behavior which is sometimes harder than writing it from scratch in our own.
In this case I would suggest using Rails scaffold mechanism to produce the default code and work around that. Sure, it will produce more code in your application but in the long-term this code will be more easy to maintain and debug.
Ruby on Rails is one of the more dynamic and convention driven web frameworks out there. A lot of the code is given to us and hard decisions are strongly pointed to a certain solution.
We should always keep our code in a way that other developers (or even us in a year after writing the code) will have a strong notion of how it works by just looking at it.
I’m not saying using InheritedResources or SimpleDelegator are not to be considered in certain scenarios, but I would try to get an advice from someone who used them before to get the whole picture as to what are the consequences in the future.
From my experience, I found myself regretting for using them in the first place, and in some cases I even refactored the whole code so it will use the other alternatives.
Take care, and make your decisions wisely🙂