Skip to content

A library with neat little methods I find myself in need of in many Ruby projects.

License

Notifications You must be signed in to change notification settings

brunze/augmented

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Augmented

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.

Installation

In your Gemfile:

gem 'augmented'

Or:

$ gem install augmented

Usage

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.

Quick Examples

Augmented::Arrays

Array#tie

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]

Augmented::Enumerators

Enumerator#index_by

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"}

Augmented::Exceptions

Exception#chain

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"]
Exception#details, Exception#details=, Exception#detailed

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}
Exception#to_h

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
#   }
# }

Augmented::Hashes

Hash#map_values

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}
Hash#map_keys

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}
Hash#polymorph

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
Hash#transform, Hash#transform!

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}]}

Augmented::Modules

Module#refined

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

Augmented::Objects

Object#if, Object#unless, Object#else

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))
Object#in?

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
Object#pick

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"}
Object#tack

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
Object#tap_if, Object#tap_unless

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
Object#thru, Object#thru_if, Object#thru_unless

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
# ""

Augmented::Procs

Proc#|

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
Proc#rescues

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]

Augmented::Strings

String#blank?

Tests if a string is empty or made of whitespace.

using Augmented::Strings::Blank

''.blank?
# true
' '.blank?
# true
' hello '.blank?
# false
String#squish, String#squish!

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"
String#truncate, String#truncate!

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"]

Augmented::Symbols

Symbol#with

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
Symbol#eq, Symbol#neq, Symbol#lt, Symbol#lte, Symbol#gt, Symbol#gte

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'>

Contributing

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.

About

A library with neat little methods I find myself in need of in many Ruby projects.

Topics

Resources

License

Stars

Watchers

Forks

Languages