corefines

:gem: A collection of refinements for Ruby core classes with a compatibility mode for older Rubies and a convenient syntactic sugar.

27
0
Ruby

= Corefines
Jakub Jirutka https://github.com/jirutka[@jirutka]
:source-language: ruby
// custom
:gem-name: corefines
:gem-version: 1.11.1
:gh-name: jirutka/{gem-name}
:gh-branch: master
:badge-style: flat
:doc-base-url: http://www.rubydoc.info/github/{gh-name}/{gh-branch}/Corefines

ifdef::env-github[]
image:https://img.shields.io/travis/{gh-name}/{gh-branch}.svg?style={badge-style}[Build Status, link=“https://travis-ci.org/{gh-name}”]
image:https://img.shields.io/codeclimate/coverage/github/{gh-name}.svg?style={badge-style}[Test Coverage, link=“https://codeclimate.com/github/{gh-name}”]
image:https://img.shields.io/codeclimate/github/{gh-name}.svg?style={badge-style}[Code Climate, link=“https://codeclimate.com/github/{gh-name}”]
image:https://img.shields.io/gem/v/{gem-name}.svg?style={badge-style}[Gem Version, link=“https://rubygems.org/gems/{gem-name}”]
image:https://img.shields.io/badge/yard-docs-blue.svg?style={badge-style}[Yard Docs, link=“http://www.rubydoc.info/github/{gh-name}/{gh-branch}”]
endif::env-github[]

Corefines is a collection of general purpose refinements for extending the core capabilities of Ruby’s built-in classes.
It also provides a <> for older Ruby versions and alternative Ruby implementations that don’t support refinements (yet).

== 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:

[source, subs=“+attributes”]
gem ‘{gem-name}’, ‘~> {gem-version}’

or to your gemspec:

[source, subs=“+attributes”]
s.add_runtime_dependency ‘{gem-name}’, ‘~> {gem-version}’

and then execute:

[source, sh]
$ bundle install

== Using

First, you must require corefines prior using:

[source]
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 http://ruby-doc.org/core-2.2.0/Module.html#method-i-using[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).

[source, plain]
Corefines::::

A single extension can be imported into the current scope classically, e.g.:

[source]
using Corefines::Object::ThenIf

or preferably using its “alias”:

[source]
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.:

[source]
using Corefines::Object

But more often you want to include multiple extensions for the class, but not all of them, e.g.:

[source]
using Corefines::Object::then_if
using Corefines::Object::in?

this can be abbreviated to:

[source]
using Corefines::Object[:then_if, :in?]

If you feel that Corefines is too long, then you can also use abbreviation CF instead:

[source]
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, https://en.wikipedia.org/wiki/Ruby_MRI[MRI] (aka CRuby), supports refinements since version 2.1.0 (https://www.ruby-lang.org/en/news/2013/12/25/ruby-2-1-0-is-released/[released in 25 Dec 2013]).
footnote:[Actually, refinements has been introduced to MRI in 2.0.0, as an experimental feature. However, its design and implementation has been changed then, so refinements in 2.0.x and 2.1+ behaves quite differently.]
Version 2.0.0 (https://www.ruby-lang.org/en/news/2013/02/24/ruby-2-0-0-p0-is-released/[released in 24 Feb 2013]) is still supported though.
http://www.jruby.org/[JRuby] doesn’t support refinements yet, it’s planned in the upcoming version 9.0.0.0 (https://github.com/jruby/jruby/issues/1062[#1062]).
http://rubini.us/[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

  • {doc-base-url}/Array[Array]
    ** {doc-base-url}/Array/Second[#second]
    ** {doc-base-url}/Array/Third[#third]
    ** {doc-base-url}/Array/Wrap[.wrap]
  • {doc-base-url}/Class[Class]
    ** {doc-base-url}/Class/Descendants[#descendants]
  • {doc-base-url}/Enumerable[Enumerable]
    ** {doc-base-url}/Enumerable/IndexBy[#index_by]
    ** {doc-base-url}/Enumerable/Many[#many?]
    ** {doc-base-url}/Enumerable/MapBy[#map_by]
    ** {doc-base-url}/Enumerable/MapSend[#map_send]
    ** {doc-base-url}/Enumerable/MapTo[#map_to]
  • {doc-base-url}/Hash[Hash]
    ** {doc-base-url}/Hash/OpAdd[#+]
    ** {doc-base-url}/Hash/Compact[#compact]
    ** {doc-base-url}/Hash/Compact[#compact!]
    ** {doc-base-url}/Hash/Except[#except]
    ** {doc-base-url}/Hash/Except[#except!]
    ** {doc-base-url}/Hash/FlatMap[#flat_map]
    ** {doc-base-url}/Hash/Only[#only]
    ** {doc-base-url}/Hash/Only[#only!]
    ** {doc-base-url}/Hash/Recurse[#recurse]
    ** {doc-base-url}/Hash/Rekey[#rekey]
    ** {doc-base-url}/Hash/Rekey[#rekey!]
    ** {doc-base-url}/Hash/SymbolizeKeys[#symbolize_keys]
    ** {doc-base-url}/Hash/SymbolizeKeys[#symbolize_keys!]
  • {doc-base-url}/Module[Module]
    ** {doc-base-url}/Module/AliasClassMethod[#alias_class_method]
    ** {doc-base-url}/Module/AliasMethodChain[#alias_method_chain]
  • {doc-base-url}/Object[Object]
    ** {doc-base-url}/Object/Blank[#blank?]
    ** {doc-base-url}/Object/DeepDup[#deep_dup]
    ** {doc-base-url}/Object/Else[#else]
    ** {doc-base-url}/Object/In[#in?]
    ** {doc-base-url}/Object/InstanceValues[#instance_values]
    ** {doc-base-url}/Object/Blank[#presence]
    ** {doc-base-url}/Object/Then[#then]
    ** {doc-base-url}/Object/ThenIf[#then_if]
    ** {doc-base-url}/Object/Try[#try]
    ** {doc-base-url}/Object/Try[#try!]
  • {doc-base-url}/String[String]
    ** {doc-base-url}/String/Camelcase[#camelcase]
    ** {doc-base-url}/String/Color[#color]
    ** {doc-base-url}/String/Concat[#concat!]
    ** {doc-base-url}/String/Decolor[#decolor]
    ** {doc-base-url}/String/ForceUTF8[#force_utf8]
    ** {doc-base-url}/String/ForceUTF8[#force_utf8!]
    ** {doc-base-url}/String/Indent[#indent]
    ** {doc-base-url}/String/RelativePathFrom[#relative_path_from]
    ** {doc-base-url}/String/Remove[#remove]
    ** {doc-base-url}/String/SnakeCase[#snake_case]
    ** {doc-base-url}/String/ToB[#to_b]
    ** {doc-base-url}/String/ToRe[#to_re]
    ** {doc-base-url}/String/Unindent[#unindent] (alias #strip_heredoc)
  • {doc-base-url}/Symbol[Symbol]
    ** {doc-base-url}/Symbol/Call[#call]

== 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 http://opensource.org/licenses/MIT/[MIT License].
For the full text of the license, see the link:LICENSE[LICENSE] file.