Corefines
Why refinements?
Extending core classes with so called monkey-paching pollutes the global scope, so it affects all files on the $LOAD_PATH
, i.e. whole application including used gems.
It’s not usually so big deal when you’re doing it in your application, but it’s very dangerous when used in a gem (library).
This can result in strange and hard to debug behaviour if another gem overrides a core class with the same method as your gem, but different implementation, and both gems are used together.
Refinements basically allows you to put monkey patches in an isolated namespace, so that your changes to core classes don’t affect other code.
TODO
Installation
Add this line to your application’s Gemfile:
gem 'corefines', '~> 1.11.1'
or to your gemspec:
s.add_runtime_dependency 'corefines', '~> 1.11.1'
and then execute:
$ bundle install
Using
First, you must require corefines
prior using:
require 'corefines'
This will not activate any extensions (just register them), even when running in compatibility mode.
Extensions (refinements) are activated selectively with the method using
.
Refinements are organized into modules by class which they refine, and further into submodules for individual methods. When an extension refines multiple classes, then it’s included in a module named after their nearest common ancestor (superclass).
Corefines::<CLASS>::<METHOD>
A single extension can be imported into the current scope classically, e.g.:
using Corefines::Object::ThenIf
or preferably using its “alias”:
using Corefines::Object::then_if
If you want to include all extensions for the class, then you can just import the parent module, e.g.:
using Corefines::Object
But more often you want to include multiple extensions for the class, but not all of them, e.g.:
using Corefines::Object::then_if
using Corefines::Object::in?
this can be abbreviated to:
using Corefines::Object[:then_if, :in?]
If you feel that Corefines is too long, then you can also use abbreviation CF instead:
using CF::Object::then_if
Refinements can be activated (with using
) at top-level (per file), inside a class, module or a method.
Compatibility mode
Refinements are still a young feature, so there’s a possibility that your gem or application will have to work on a Ruby platform that doesn’t fully support refinements yet.
The main Ruby implementation, MRI (aka CRuby), supports refinements since version 2.1.0 (released in 25 Dec 2013). [1] Version 2.0.0 (released in 24 Feb 2013) is still supported though. JRuby doesn’t support refinements yet, it’s planned in the upcoming version 9.0.0.0 (#1062). Rubinius also doesn’t support refinements yet.
This gem is a collection of pure refinements, and yet, it works even on older Rubies that don’t support refinements. Wait… how? Well, when you use the gem with an older Ruby, it’s actually cheating. Instead of locally scoped changes, it falls back to global monkey-patching.
The Corefines gem adds refine
and using
methods to the core classes, so you can define and use refinements just like in newer Rubies.
But internally it works very differently.
The refine
method adds a given block to a collection of pending “refinements” inside its module.
When using
is called first time for the module, it evaluates module’s “refinements” in context of the target classes (i.e. do a monkey-patch).
Not ideal indeed, but probably the best of what we can achieve.
List of refinements
-
-
#unindent (alias
#strip_heredoc
)
Acknowledgement
Most of the extension methods are based on, or highly inspired from:
Very useful articles about refinements and how to “trick” them:
Contributing
-
Fork it.
-
Create your feature branch (
git checkout -b my-new-feature
). -
Commit your changes (
git commit -am 'Add some feature'
). -
Push to the branch (
git push origin my-new-feature
). -
Create a new Pull Request.
License
This project is licensed under MIT License. For the full text of the license, see the LICENSE file.