Published in


refine and using Methods in Ruby

In this article, we’re going to explore the following topics:

  • monkey-patching in Ruby
  • refine and using methods
  • refine and using behind the scene

Monkey-patching in Ruby

Ruby eases the possibility to monkey-patch due to the fact that Ruby classes or modules can be reopened

This mechanism can be useful — even if the Liskov Substitution Principle is always preferable) — to TEMPORARILY avoid a server crash for example.

The problem of this solution is that the monkey-patch will be shared among all the instances of the monkey-patched class — in our case the Hash class.

Let’s have a look to a dummy Rails app as example:

Here, the MyLib#print_config method will not display the config anymore — keep in mind that this is a dummy app :-).

The problem is that a call to the GET /products.json route — which has nothing to do with our monkey-patch —also returns an empty body.

In effect, the side effect of our monkey-patch is that the render json: @products will implicitly call the @products.to_json method. So an empty String will be returned.

This example shows one of the limits of straightly monkey-patching a class by using the Ruby’s Class Opening mechanism.

In order to deal with this kind of issues, — and since the version 2.0.0 — Ruby introduced the concept of Refinement.

refine and using methods

The Module#refine method allows you to register a monkey-patch for a specific class that can be applied whenever we want by calling the Module#using method.

From now, we’ll prefer to talk about refinement instead of monkey-patching.

By calling the Module#refine method, we register and activate a refinement for the Hash class.

Note that this method only accepts a class (and not a module) as argument. This argument indicates to which class the patch is applied.

Our refinement is defined within the TemporaryPatch module. This module is used as a container for our refinements.

In effect, we could add another refinement within this module — to add a method to the Array class, for example — to group and apply our patches together.

Note that the module name is not important but it’s preferable to name it accordingly to the context of the refinement.

After our refinement declaration, we instantiate the my_ebook hash and call the my_ebook.to_s method.

At this particular moment, the refinement is not applied to the my_ebook hash.

Then, we call using TemporaryPatch.

This method applies all the refinements included in the module passed as argument — the TemporaryPatch module in the example.

So, from this moment and for the rest of the file, the Hash#to_s method is overridden by the one defined in our refinement.

So the lolcat.to_s method returns an empty String.

Finally, note that the refinement is also applied to the my_ebook hash even if this hash has been instantiated before the call to using.

Now that we’re more familiar with the Module#refine and Module#using methods, then let’s have a look to how the refinement concept works behind the scene.

refine and using behind the scene

When the Module#refine method is invoked it actually doesn’t modify the class passed as parameter.

Instead, it returns an anonymous module that keeps a reference to the refined class and to the module that includes the refinement

Here, we can see that the Module#refine method returns an anonymous module that directly inherits from the refined class — the Array class in our case.

Feel free to read the Ruby Object Model article if you are unfamiliar with the Module#ancestors method.

Before to return the anonymous module, the Module#refine sets a bunch of hidden attributes on:

  • the anonymous module
  • and the including module — the Patch module in the above example

Let’s detail these attributes.

Within the anonymous module:

The __refined_class__ attribute keeps a reference to the refined class — the Array class in the above example.

The __refined_at__ attribute keeps a reference to the module that includes the refinement — the Patch module in the above example.

Within the Patch module:

The __refinements__ attribute is a table of correspondance that contains all the refinements declared in the module.

This attribute is a hash with as key the refined class and as value the anonymous module that contains the refinement to apply.

In the above example, the __refinements__ hash is:

{ :Array => #<refinement:Array@Patch> }

These attributes are internally used by the Module#using method to apply the right patch for a given method.

The Module#using method loops through the internal __refinements__ hash of the module passed as argument.

Then for each refinement, the method sets a bunch of flags and apply the refinement to the execution context of the using message sender.

Feel free to read thePrivate & Protected in Ruby article if you’re unfamiliar with the Message, Receiver and Sender notions in Ruby.

When a call to a method included in the refinements is made, then the Method Lookup Path is bypassed and directly calls the method within the refinement instead — This is not very true, but let’s keep it simple for the moment. More details to come in the Part II.

Finally, let’s recap what’s a refinement.

A refinement is an anonymous module that keeps track of:

  • the refined class — the class where the patch is applied
  • the container module — the module where the refinement is declared
  • and the content of the refinement — the block passed to the refine method

Voilà !

If you found this helpful 👇 is now live!

RubyCademy is a learning center for developers who are in their first or second professional experience such as

  • self-taught Rubyist
  • post-bootcamp dev
  • looking for your first job
  • young graduate
  • junior or mid-level dev

and who want to consolidate their knowledge of Ruby and Ruby on Rails.

So at RubyCademy, our content is designed to help you learn important notions in record time that you can directly apply on your day-to-day work.

Feel free to visit our website and subscribe to start your journey to become a better Rubyist! 💯👇😉

Thank you for your time!

Thank you for taking the time to read this post :-)

Feel free to 👏 and share this article if it has been useful for you. 🚀

Here is a link to my last article: Symbol in Ruby.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store