In Ruby land, monkey patching is the act of modifying the methods on someone else’s class.
Ruby makes it easy to add, remove, and replace methods on any class – even core classes like
Array – but it is generally frowned upon.
It can be very difficult to debug problems caused by monkey patching, and it can easily cause bugs when the patched class changes.
If you’re considering monkey patching but you know it’s a bad idea, here are four alternatives in order from most preferable to least preferable.
The Monkey Patch
Here is the (somewhat contrived) example of monkey patching that we will be looking at.
ActionController::Base.class_eval do def current_user uid = session[:user_id] uid ? User.find_by(id: uid)) : nil end end class EmailController < ApplicationController def email render text: current_user.email end end
The idea is to make a
current_user method that is available for use in controllers.
class_eval method is being used to define
ActionController::Base, which is a class inside of Rails.
EmailController class is just there to show how the patched method is used.
On to the alternatives!
1. Inline It (Don’t Do It)
class EmailController < ApplicationController def email render User.find(session[:user_id]).email end end
Out of all the options in this article, including monkey patching, this requires the least code.
It requires no new classes, modules, or methods.
You never have to ask where
current_user is defined because it doesn’t exist.
The implementation of
current_user has been reduced to just a single line:
If the functionality is only used in a couple of places, then this is usually the best choice. Remember that no code is better than no code. If you suspect that the functionality will only be used in two or three places, then strongly consider inlining the code instead of extracting it into a new method.
However, regarding this particular example, we would likely want to access the current user in a lot of different places. If it’s going to be used everywhere, then inlining the code is probably a bad choice.
2. Make It A Standalone Function
module CurrentUser def self.get(session) uid = session[:user_id] uid ? User.find_by(id: uid)) : nil end end class EmailController < ApplicationController def email render text: CurrentUser.get(session).email end end
It’s preferable to implement new functionality in new code, instead of modifying existing code. It’s easier to test, and less likely to introduce bugs into existing code. Creating new classes/methods also helps to maintain separation of concerns, and it’s obvious where the functionality lives.
It doesn’t look very “rubyish,” but it should at least be easy to follow.
There is nothing clever or magical about it, which is exactly the point.
Looking at the usage code, you could correctly assume that there is a module/class called
CurrentUser, probably in a file named
current_user.rb, which contains the
It’s easy to trace your way through the code because it’s explicit.
This is a good solution if
current_user is used in just a few different controllers.
If it will be used in the majority of controllers, then it might make sense to choose convenience over simplicity with one of the next two alternatives.
3. Make It A Mixin Module
module CurrentUserMethods def current_user uid = session[:user_id] uid ? User.find_by(id: uid) : nil end end class EmailController < ApplicationController include CurrentUserMethods def email render text: current_user.email end end
If convenience is important, then consider implementing the functionality in a mixin module. This way the new method is limited to just the controllers that explicitly include it, instead of every controller in the whole app. This solution keeps some of the explicitness and separation from previous alternatives, without polluting every controller class.
This is a good solution if the functionality will be used in a lot of different places, but not everywhere.
If you are writing
include CurrentUserMethods on literally every controller, then you’re not getting any benefit from using a separate mixin, so you should consider the next option: putting it in a superclass.
4. Put It In A Superclass
class ApplicationController < ActionController::Base def current_user uid = session[:user_id] uid ? User.find_by(id: uid) : nil end end class EmailController < ApplicationController def email render text: current_user.email end end
If every single controller uses the functionality, then it’s probably best to put it in a class that all controllers inherit from. That way it’s available everywhere by default.
This is the least preferable alternative for a few reasons:
It affects literally every controller in the application. That’s a huge surface area for introducing bugs.
If you didn’t have an inheritance hierarchy before, you do now! Rails provides the controller superclass
ApplicationControllerby default, but you’re not going to have a preexisting superclass in all situations. If you’re using inheritance sparingly – and you should be – then most of the time you will need to create a new inheritance hierarchy. That’s not something you want to do lightly. If language features were a food pyramid, inheritance would be a “sometimes” food.
It’s harder to trace. The controller doesn’t explicitly include or define the method, the method just exists already. If you want to look up the definition, you have to go digging through all the superclasses to find it, plus any overriding methods. That’s more complicated than a normal, stand-alone method.
For our particular
current_user method, this might be the best solution.
The superclass already exists in Rails, so it’s easy to slap another method in there.
However, just because it’s easy doesn’t mean it’s a good idea.
Be aware of the trade-off you’re making between convenience and simplicity.
Do you already have a huge
Does everything tend to break whenever you change it?
Is it difficult to test the new functionality?
If so, then consider one of the other alternatives.