-
Couldn't load subscription status.
- Fork 6
Blog/refinements in ruby #236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 14 commits
6871132
e861335
b39640e
d50c071
6a751e1
6886260
d1fdd0a
45951be
795e581
1c35707
62f46bd
9a89637
3bbb82f
c569ba5
d3ada5d
4d8e168
8fc4cce
f0c621d
e30e2d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,180 @@ | ||||||||||||||||||||||||||||||
| +++ | ||||||||||||||||||||||||||||||
| title = "Refinements in Ruby: A Flexible Approach to Modifying Core Classes" | ||||||||||||||||||||||||||||||
| slug = "refinements-in-ruby" | ||||||||||||||||||||||||||||||
| date = 2024-09-13T11:48:18+05:30 | ||||||||||||||||||||||||||||||
| image = "/images/2024/refinements-in-ruby/header.jpeg" | ||||||||||||||||||||||||||||||
| draft = false | ||||||||||||||||||||||||||||||
| authors = ["Syed Mohd Mehndi"] | ||||||||||||||||||||||||||||||
| description = "Refinements in Ruby: A Flexible Approach to Modifying Core Classes" | ||||||||||||||||||||||||||||||
| tags = ["Ruby", "Clean Code"] | ||||||||||||||||||||||||||||||
| categories = ["Ruby", "Clean Code"] | ||||||||||||||||||||||||||||||
| type = "" | ||||||||||||||||||||||||||||||
| +++ | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Ruby is a highly flexible programming language known for its dynamic capabilities and metaprogramming power. One such powerful feature in Ruby is refinements. Introduced in Ruby 2.0, refinements allow you to modify existing classes (even core classes like String, Array, etc.) in a more controlled and localized way. This is particularly useful when you want to change class behavior in specific contexts without affecting the global environment, which is a common issue with traditional monkey-patching. | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Shouldn't we use Indian English instead of US English? |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| In this blog, we’ll dive into the basics of using refinements, explore common use cases, and explain why they can be preferable to monkey-patching. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ## What Are Refinements? | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Refinements offer a way to extend or override methods of existing classes, but only within a specific scope (like a module or class). This helps prevent unintended side effects that can occur when modifying core classes or third-party libraries globally. In other words, refinements give you a safer alternative to monkey-patching by limiting the scope of changes. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ### Key Characteristics of Refinements: | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| - **Scoped Modifications**: Refinements apply only in specific contexts (like within a module or class). | ||||||||||||||||||||||||||||||
| - **Opt-in Behavior**: You have to explicitly “activate” a refinement within the scope where you want it to apply. | ||||||||||||||||||||||||||||||
| - **No Global Impact**: Unlike monkey-patching, which modifies behavior globally, refinements are local to the scope they are activated in. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ## Refinements in Action | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Let’s walk through an example to demonstrate how refinements work in Ruby. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ### Step 1: Defining a Refinement | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Here’s how you define a refinement in Ruby. We will modify the String class to add a new method, #reverse_words, which reverses the words in a string, not the characters. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ```ruby | ||||||||||||||||||||||||||||||
| module StringExtensions | ||||||||||||||||||||||||||||||
| refine String do | ||||||||||||||||||||||||||||||
| def reverse_words | ||||||||||||||||||||||||||||||
| self.split(' ').reverse.join(' ') | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit:
Suggested change
Personally, I use 2 spaces as it reads nicely without having too much whitespace. But to each their own I guess. |
||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| In this example, we created a module `StringExtensions` that contains a refinement of the String class. This refinement adds a method reverse_words to reverse the order of words in a string. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ### Step 2: Activating the Refinement | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| To use the refinement, we need to activate it in a specific scope with the `using` keyword: | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ```ruby | ||||||||||||||||||||||||||||||
| class SentenceManipulator | ||||||||||||||||||||||||||||||
| using StringExtensions | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def initialize(sentence) | ||||||||||||||||||||||||||||||
| @sentence = sentence | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def reverse | ||||||||||||||||||||||||||||||
| @sentence.reverse_words | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| sentence = SentenceManipulator.new("Hello World from Ruby") | ||||||||||||||||||||||||||||||
| puts sentence.reverse | ||||||||||||||||||||||||||||||
| # Output: "Ruby from World Hello" | ||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Here, the refinement applies only inside the `SentenceManipulator` class. If you try to use reverse_words outside of this class, it will not be available. | ||||||||||||||||||||||||||||||
hulksyed07 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ### Step 3: Trying to Use the Refinement Outside the Scope | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ```ruby | ||||||||||||||||||||||||||||||
| puts "Hello World from Ruby".reverse_words | ||||||||||||||||||||||||||||||
| # Output: NoMethodError: undefined method `reverse_words' for "Hello World from Ruby":String | ||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| This code will raise an error because the refinement was not activated globally, and it’s only available within the SentenceManipulator class. | ||||||||||||||||||||||||||||||
hulksyed07 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ### What Happens When You Use Refinements Inside Their Defining Module? | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Refinements in Ruby are designed to only take effect when explicitly activated with the `using` keyword. This means that even if you try to use a refinement inside the module where it is defined, it won’t work unless the refinement is explicitly activated in the scope where it is used. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Here’s an example to demonstrate this behavior: | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ```ruby | ||||||||||||||||||||||||||||||
| module StringExtensions | ||||||||||||||||||||||||||||||
| refine String do | ||||||||||||||||||||||||||||||
| def reverse_words | ||||||||||||||||||||||||||||||
| self.split(' ').reverse.join(' ') | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| class Foo | ||||||||||||||||||||||||||||||
| def bar | ||||||||||||||||||||||||||||||
| 'Hello World'.reverse_words | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| StringExtensions::Foo.new.bar | ||||||||||||||||||||||||||||||
| # Output: NoMethodError: undefined method `reverse_words' for "Hello World":String | ||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| #### Why Does This Happen? | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| In Ruby, refinements are not automatically activated within the module where they are defined. Instead, you must explicitly activate them with the `using` keyword in the scope where they are intended to be used. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| #### Fixing the Issue | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| To fix this, you can activate the refinement within the `Foo` class with the `using` keyword: | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ```ruby | ||||||||||||||||||||||||||||||
| module StringExtensions | ||||||||||||||||||||||||||||||
| refine String do | ||||||||||||||||||||||||||||||
| def reverse_words | ||||||||||||||||||||||||||||||
| self.split(' ').reverse.join(' ') | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| class Foo | ||||||||||||||||||||||||||||||
| using StringExtensions | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def bar | ||||||||||||||||||||||||||||||
| 'Hello World'.reverse_words | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| puts StringExtensions::Foo.new.bar | ||||||||||||||||||||||||||||||
| # Output: "World Hello" | ||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| By adding using `StringExtensions` inside the `Foo` class, we explicitly enable the refinement for that scope, and the reverse_words method becomes available. | ||||||||||||||||||||||||||||||
hulksyed07 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| #### Key Takeaway | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Even though a refinement is defined within a module, it is not automatically available within the module’s inner classes or methods. This is intentional to ensure that refinements remain opt-in and their effects are explicitly scoped. Always use the using keyword in the desired scope to activate the refinement. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ## Why Use Refinements Instead of Monkey-Patching? | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 1. **Limited Scope**: Unlike monkey-patching, which alters a class’s behavior globally, refinements allow changes to be confined to specific contexts. This reduces the risk of breaking code in other parts of your application. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 1. **Better Code Isolation**: Refinements ensure that method modifications do not leak out and affect other classes or libraries unintentionally. With refinements, you can safely modify the behavior of third-party libraries without worrying about conflicts. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 1. **Safer Library Usage**: Libraries can define refinements to tweak core class behaviors for internal use without impacting their users’ code. This leads to more reliable and maintainable libraries. | ||||||||||||||||||||||||||||||
|
Comment on lines
+143
to
+147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not have them in a single list? |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ## Common Use Cases for Refinements | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 1. **Overriding Third-Party Library Behavior**: If you need to modify the behavior of a third-party library without affecting the rest of your application, refinements allow you to do so safely. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 1. **Enhancing Core Classes for Specific Purposes**: When working with Ruby's core classes like String or Array, you may want to add methods that are useful for your domain without affecting how those classes work globally. | ||||||||||||||||||||||||||||||
hulksyed07 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 1. **Testing and Prototyping**: Refinements are great for temporarily modifying behavior during testing or prototyping, as the changes can be localized to the test suite or experimental code. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ## Caveats of Using Refinements | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| While refinements offer great flexibility, they come with certain caveats: | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 1. **Performance Overhead**: Refinements introduce a small overhead, especially when resolving method calls. While this is generally minimal, it may impact performance in hot paths. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 1. **Limited Usage in Some Libraries**: Some libraries and frameworks (such as Rails) may not fully support refinements or may have conflicts with them due to their reliance on method lookups. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 1. **Limited Method Visibility**: Refinements only affect methods directly called on objects. They do not apply to methods called indirectly via send, method, or define_method. | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ## Conclusion | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Refinements in Ruby provide a powerful and flexible alternative to monkey-patching by allowing developers to extend or modify classes in a controlled and localized manner. They help maintain code modularity, prevent unintended side effects, and promote better practices when working with core classes and third-party libraries. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| However, keep in mind the caveats and use refinements judiciously. If your goal is to alter class behavior without the risk of affecting the global state of your application, refinements are an excellent tool to have in your Ruby toolkit. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ## Further Reading: | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Ruby Documentation on Refinements | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| - [Ruby Documentation on Refinements](https://docs.ruby-lang.org/en/2.4.0/syntax/refinements_rdoc.html 'Ruby Documentation on Refinements') | ||||||||||||||||||||||||||||||
| - [Understanding Monkey-Patching in Ruby](https://blog.incubyte.co/blog/monkey-patch 'Understanding Monkey-Patching in Ruby') | ||||||||||||||||||||||||||||||
hulksyed07 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Happy coding! | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's update the date to today, it will show up chronologically on the list