Skip to content

Instantly share code, notes, and snippets.

@serradura
Last active November 14, 2020 20:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save serradura/9df18569d6e8eb7d9b293d28f977dd7b to your computer and use it in GitHub Desktop.
Save serradura/9df18569d6e8eb7d9b293d28f977dd7b to your computer and use it in GitHub Desktop.
u-thenable - A backport/polyfill of `yield self` and` then` methods for old Ruby versions. (https://rubygems.org/gems/u-thenable)
.tool-versions

µ-thenable

A backport/polyfill of yield_self and then methods for old Ruby versions (< 2.6.0).

Prerequisites

Ruby >= 2.2.2, < 2.6.0

Installation

Add this line to your application's Gemfile:

gem 'u-thenable'

# If you desire to make the methods available in any object. use:
gem 'u-thenable', require: 'u-thenable-ext'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install u-thenable

Usage

###################################
# Extending an object (via mixin) #
###################################

class Numbers
  include Thenable

  def one; 1; end
  def two; 2; end
  def three; 3; end

  def all; [one, two, three]; end
end

numbers = Numbers.new

puts numbers.then { |n| n.two * n.three  }      # => 6
puts numbers.yield_self { |n| n.two * n.three } # => 6

TwoPlusThree = -> (n) { n.two * n.three }

puts numbers.then(&TwoPlusThree)       # => 6
puts numbers.yield_self(&TwoPlusThree) # => 6

########################################
# Using in any object (extends Object) #
########################################

Add3 = -> (value) { value + 3 }

puts numbers.one.then(&Add3) # => 4
# => NoMethodError: undefined method `then' for 1:Fixnum

puts numbers.all.then { |all| all.map(&Add3) }
# => NoMethodError: undefined method `then' for [1, 2, 3]:Array

require 'thenable-ext'

puts numbers.one.then(&Add3) # => 4

puts numbers.all.then { |all| all.map(&Add3) } # => [4, 5 ,6]

How to run the test suite

ruby test_runner.rb
# frozen_string_literal: true
puts "\n== Extends classes/modules applying the Thenable mixin ==\n"
system('ruby test_thenable.rb')
puts "\n== Extends all Ruby objects ==\n"
system('ruby test_thenable_ext.rb')
# frozen_string_literal: true
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'u-test', '0.9.0'
end
require_relative 'thenable'
class TestThenable < Microtest::Test
def number_class(value)
Class.new.tap do |base|
base.class_eval <<-RUBY
def self.number; #{value}; end
def number; self.class.number; end
RUBY
end
end
def test_methods_in_instances
#
# .yield_self
#
one = number_class(1)
one.send(:include, Thenable)
##
## assert the instances
##
assert 1 == one.new.yield_self(&:number)
assert 1 == one.new.yield_self { |i| i.number }
assert 0 == one.new.yield_self { |i| 0 }
assert 0 == one.new.yield_self { 0 }
assert 1 == one.new.yield_self.count
assert one.new.yield_self.instance_of?(Enumerator)
##
## assert the class
##
assert 1 == one.yield_self(&:number)
assert 1 == one.yield_self { |i| i.number }
assert 0 == one.yield_self { |i| 0 }
assert 0 == one.yield_self { 0 }
assert 1 == one.yield_self.count
assert one.yield_self.instance_of?(Enumerator)
#
# .then
#
two = number_class(2)
two.send(:include, Thenable)
##
## assert the instances
##
assert 2 == two.new.then(&:number)
assert 2 == two.new.then { |i| i.number }
assert 0 == two.new.then { |i| 0 }
assert 0 == two.new.then { 0 }
assert 1 == two.new.yield_self.count
assert two.new.yield_self.instance_of?(Enumerator)
##
## assert the class
##
assert 2 == two.then(&:number)
assert 2 == two.then { |i| i.number }
assert 0 == two.then { |i| 0 }
assert 0 == two.then { 0 }
assert 1 == two.then.count
assert two.then.instance_of?(Enumerator)
end
def test_methods_in_classes
#
# .yield_self
#
one = number_class(1).extend(Thenable)
assert 1 == one.yield_self(&:number)
assert 1 == one.yield_self { |i| i.number }
assert 0 == one.yield_self { |i| 0 }
assert 0 == one.yield_self { 0 }
assert 1 == one.yield_self.count
assert one.yield_self.instance_of?(Enumerator)
#
# .then
#
two = number_class(2).extend(Thenable)
assert 2 == two.then(&:number)
assert 2 == two.then { |i| i.number }
assert 0 == two.then { |i| 0 }
assert 0 == two.then { 0 }
assert 1 == two.then.count
assert two.then.instance_of?(Enumerator)
end
def test_methods_in_modules
one = Module.new do
extend Thenable
def self.number; 1 ;end
end
#
# .yield_self
#
assert 1 == one.yield_self(&:number)
assert 1 == one.yield_self { |i| i.number }
assert 0 == one.yield_self { |i| 0 }
assert 0 == one.yield_self { 0 }
assert 1 == one.yield_self.count
assert one.yield_self.instance_of?(Enumerator)
#
# .then
#
assert 1 == one.then(&:number)
assert 1 == one.then { |i| i.number }
assert 0 == one.then { |i| 0 }
assert 0 == one.then { 0 }
assert 1 == one.then.count
assert one.then.instance_of?(Enumerator)
refute Module.new.respond_to?(:yield_self) if RUBY_VERSION < '2.5.0'
refute Module.new.respond_to?(:then) if RUBY_VERSION < '2.6.0'
end
end
Microtest.call
# frozen_string_literal: true
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'u-test', '0.9.0'
end
require_relative 'thenable-ext'
class TestThenableExt < Microtest::Test
def test_methods_in_instances
#
# .yield_self
#
assert '1' == 1.yield_self(&:to_s)
assert '1' == 1.yield_self { |i| i.to_s }
assert 0 == 1.yield_self { |i| 0 }
assert 0 == 1.yield_self { 0 }
assert 1 == 1.yield_self.count
assert 1.yield_self.instance_of?(Enumerator)
#
# .then
#
assert '2' == 2.then(&:to_s)
assert '2' == 2.then { |i| i.to_s }
assert 0 == 2.then { |i| 0 }
assert 0 == 2.then { 0 }
assert 1 == 2.then.count
assert 2.then.instance_of?(Enumerator)
end
def test_methods_in_classes
#
# .yield_self
#
assert 'String' == String.yield_self(&:name)
assert 'String' == String.yield_self { |i| i.name }
assert 0 == String.yield_self { |i| 0 }
assert 0 == String.yield_self { 0 }
assert 1 == String.yield_self.count
assert String.yield_self.instance_of?(Enumerator)
#
# .then
#
assert 'Hash' == Hash.then(&:name)
assert 'Hash' == Hash.then { |i| i.name }
assert 0 == Hash.then { |i| 0 }
assert 0 == Hash.then { 0 }
assert 1 == Hash.yield_self.count
assert Hash.yield_self.instance_of?(Enumerator)
end
end
Microtest.call
require_relative 'thenable'
Object.include(Thenable::Mixin)
# frozen_string_literal: true
module Thenable
VERSION = '1.0.1'
def self.included(base)
return unless RUBY_VERSION < '2.6.0'
base.send(:include, Mixin)
base.extend(Mixin)
end
def self.extended(base)
base.extend(Mixin) if RUBY_VERSION < '2.6.0'
end
module Mixin
if RUBY_VERSION < '2.5.0'
def yield_self
return yield(self) if block_given?
Enumerator.new { |yielder| yielder.yield(self) }
end
end
alias_method(:then, :yield_self) if RUBY_VERSION < '2.6.0'
end
end
require_relative 'thenable-ext'
require_relative 'thenable'
Gem::Specification.new do |s|
s.name = 'u-thenable'
s.summary = 'A backport/polyfill of `yield_self` and `then` methods.'
s.description = 'A backport/polyfill of `yield self` and` then` methods for old Ruby versions.'
s.version = Thenable::VERSION
s.licenses = ['MIT']
s.platform = Gem::Platform::RUBY
s.required_ruby_version = ['>= 2.2.2', '< 2.6.0']
s.files = ['thenable.rb', 'thenable-ext.rb'].flat_map { |f| [f, "u-#{f}"] }
s.require_path = '.'
s.author = 'Rodrigo Serradura'
s.email = 'rodrigo.serradura@gmail.com'
s.homepage = 'https://gist.github.com/serradura/9df18569d6e8eb7d9b293d28f977dd7b'
end
require_relative 'thenable'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment