Augmented
is a library with some core-type utility methods that I frequently find myself copying across projects. It uses refinements instead of class modification for maximum control and an easy sleep at night.
Many of the methods in Augmented
facilitate a more functional style of programming and cover a few tiny gaps in Ruby's solid functional support.
In your Gemfile:
gem 'augmented'
Or:
$ gem install augmented
You have 3 ways of loading the refinements. You can load all of them at once:
using Augmented
You can load all refinements for just one type:
using Augmented::Arrays
using Augmented::Enumerators
using Augmented::Exceptions
using Augmented::Hashes
using Augmented::Modules
using Augmented::Objects
using Augmented::Procs
using Augmented::Strings
using Augmented::Symbols
# etc.
Or you can load just the methods you need:
using Augmented::Objects::Pickable
using Augmented::Procs::Chainable
using Augmented::Symbols::Arguable
# etc.
Weaves an object between the elements of an array. Like join
but without flattening the array into a string.
using Augmented::Arrays::Tieable
[1, 2, 3].tie(:hello)
# [1, :hello, 2, :hello, 3]
[1, 5, 12].tie{ |a, b| a + b }
# [1, 6, 5, 17, 12]
Builds an index of all elements of an enumerator according to the given criterion. Last element wins.
using Augmented::Enumerators::Indexing
['a', 'bb', 'c', 'ddddd'].to_enum.index_by(&:length)
# {1=>"c", 2=>"bb", 5=>"ddddd"}
Returns an enumerator over the exception's causal chain, starting with the exception itself.
using Augmented::Exceptions::Chain
begin
begin
begin
raise 'first'
rescue
raise 'second'
end
rescue
raise 'third'
end
rescue => error
error.chain.map(&:message)
end
# ["third", "second", "first"]
Attach a hash of details to any exception.
using Augmented::Exceptions::Detailed
exception = RuntimeError.new('oops!').detailed(foo: 10, bar: { baz: 30 })
exception.details
# {:foo=>10, :bar=>{:baz=>30}}
exception.details = { bam: 40 }
exception.details
# {:bam=>40}
Serializes an exception into a Hash including its backtrace, details and causal chain.
using Augmented::Exceptions::Serializable
using Augmented::Exceptions::Detailed
begin
begin
raise RuntimeError.new('first').detailed(foo: 10)
rescue
raise RuntimeError.new('second').detailed(bar: 20)
end
rescue => error
error.to_h
end
# {
# :class => "RuntimeError",
# :message => "second",
# :details => { :bar => 20 },
# :backtrace => [ ... ],
# :cause => {
# :class => "RuntimeError",
# :message => "first",
# :details => { :foo => 10 },
# :backtrace => [ ... ],
# :cause => nil
# }
# }
Returns a new hash with the same keys but transformed values.
using Augmented::Hashes::Mappable
{ aa: 11, bb: 22 }.map_values{ |i| i * 3 }
# {:aa=>33, :bb=>66}
Returns a new hash with the same values but transformed keys.
using Augmented::Hashes::Mappable
{ aa: 11, bb: 22 }.map_keys{ |k| k.to_s[0] }
# {"a"=>11, "b"=>22}
Creates an object from a Hash.
using Augmented::Hashes::Polymorphable
class Sheep
def initialize attributes
@speak = attributes[:speak]
end
def speak
puts @speak
end
end
{ type: 'Sheep', speak: 'baaaah' }.polymorph.speak
# baaaah
Recursively applies functions to a tree of hashes.
using Augmented::Hashes::Transformable
tree = { lorem: 'ipsum', dolor: [ { sit: 10}, { sit: 20 } ] }
triple = -> i { i * 3 }
tree.transform({ lorem: :upcase, dolor: { sit: triple } })
# {:lorem=>"IPSUM", :dolor=>[{:sit=>30}, {:sit=>60}]}
Makes it less verbose to create small refinements.
using Augmented::Modules::Refined
class TextPage
using refined String,
to_phrase: -> { self.strip.capitalize.gsub(/\.?\z/, '.') }
# ...
def text
@lines.map(&:to_phrase).join(' ')
end
end
Allows you to conditionally return an object, allowing you to be more concise in some situations.
using Augmented::Objects::Iffy
Person.new.eat(toast.if(toast.buttered?).else(muffin))
Person.new.eat(toast.if(&:buttered?).else(muffin))
Person.new.eat(toast.unless(toast.soggy?).else(muffin))
Person.new.eat(toast.unless(&:soggy?).else(muffin))
Tests if the object is included in a collection (collection must respond to included?
).
using Augmented::Objects::In
2.in?([1, 2, 3])
# true
5.in?(0..2)
# false
'B'.in?('ABC')
# true
Calls a bunch of methods on an object and collects the results.
using Augmented::Objects::Pickable
class MyThing
def foo; 'lorem'; end
def bar; 'ipsum'; end
def baz; 'dolor'; end
end
MyThing.new.pick(:foo, :baz)
# {:foo=>"lorem", :baz=>"dolor"}
Appends a bunch of singleton methods to an object.
using Augmented::Objects::Tackable
Object.new.tack(name: 'Alice', greet: -> { puts "hello I'm #{name}" }).greet
# hello I'm Alice
Like tap
but only executes the block according to the condition.
using Augmented::Objects::Tappable
toast.tap_if(toast.warm?){ |toast| toast.butter }.eat
toast.tap_if(:warm?.to_proc){ |toast| toast.butter }.eat
Applies a function to an object and returns the result. Object#thru_if
and Object#thru_unless
do so depending on the condition supplied (if the condition fails, the object is returned untouched).
using Augmented::Objects::Thru
filter_words = -> s { s.gsub(/bad/, '').squeeze(' ').strip }
'BAD WORDS, BAD WORDS'.downcase.thru(&filter_words).capitalize
# "Words, words"
config.censor = true
'BAD WORDS, BAD WORDS'.downcase.thru_if(config.censor?, &filter_words).capitalize
# "Words, words"
''.downcase.thru_unless(:empty?.to_proc, &filter_words).capitalize
# ""
Chains several procs together so they execute from left to right.
using Augmented::Procs::Chainable
sub_two = -> i { i - 2 }
triple = -> i { i * 3 }
add_twenty = -> i { i + 20 }
(sub_two | triple | add_twenty)[5]
# 29
Wraps a Proc
to rescue it from certain exceptions while returning a given value.
using Augmented::Procs::Rescuable
integerify = proc{ |x| Integer(x) }.rescues(ArgumentError, 42)
['1', '2', 'oops!', '4'].map(&integerify)
# [1, 2, 42, 4]
Tests if a string is empty or made of whitespace.
using Augmented::Strings::Blank
''.blank?
# true
' '.blank?
# true
' hello '.blank?
# false
Replaces runs of whitespace with a single space except at the edges of the string. Can be given a custom pattern and replacement.
using Augmented::Strings::Squish
' hello world '.squish!
# "hello world"
'---what-a-nice--kebab-'.squish(/\W+/, '_')
# "what_a_nice_kebab"
Returns a prefix of a string up to a given number of characters.
using Augmented::Strings::Truncatable
'hello world'.truncate(5)
# "hello"
[(string = 'hello world'), string.truncate!(5)]
# ["hello", "hello"]
Like Symbol#to_proc
but allows you to pass some arguments along.
using Augmented::Symbols::Arguable
class Eleven
def add_many *others
11 + others.reduce(0, :+)
end
end
:add_many.with(1, 2, 3).call(Eleven.new)
# 17
Creates functions that compare an object's attribute.
using Augmented::Symbols::Comparing
class User
def initialize name
@name = name
end
attr_reader :name
end
users = [ User.new('Marianne'), User.new('Jeremy') ]
users.find(&:name.eq('Marianne'))
# <User:0x... @name='Marianne'>
Do you have a method you would like to see added to this library? Perhaps something you keep copying from project to project but always found too small to bother with a gem? Feel free to submit a ticket/pull request with your idea.