DuckPuncher

Ruby objects walk and talk like ducks, therefore they are ducks. But ducks don't always behave, and some times they need tough love. You know, lil love punches! :punch: :heart:

These are the ducks I love the most:

Array#m             => `[].m(:to_s)` => `[].map(&:to_s)` 
Array#mm            => `[].mm(:sub, /[aeiou]/, '*')` => `[].map { |x| x.sub(/[aeiou]/, '*') }` 
Array#get           => `[].methods.get('ty?')` => [:empty?] 
Hash#dig            => `{a: 1, b: {c: 2}}.dig(:b, :c)` => 2 (Now build into Ruby 2.3)
Numeric#to_currency => `25.245.to_currency` => 25.25 
Numeric#to_duration => `10_000.to_duration` => '2 h 46 min'
Numeric#to_time_ago => `10_000.to_time_ago` => '2 hours ago'
Numeric#to_rad      => `10.15.to_rad` => 0.17715091907742445
String#pluralize    => `'hour'.pluralize(2)` => "hours"
String#underscore   => `'DuckPuncher::JSONStorage'.underscore` => 'duck_puncher/json_storage'
Object#clone!       => `Object.new.clone!` => a deep clone of the object (using Marshal.dump)
Method#to_instruct  => `Benchmark.method(:measure).to_instruct` returns the Ruby VM instruction sequence for the method
Method#to_source    => `Benchmark.method(:measure).to_source` returns the method definition as a string

I also provide an experimental punch that tries to download the required gem if it doesn't exist on your computer. The method is called require! and works like this:

Downloads and activates a gem for the current and subsequent consoles. For example:

>> `require 'pry'` 
LoadError: cannot load such file -- pry
 from (irb):1:in `require'
 from (irb):1
 from bin/console:10:in `<main>'
>> require! 'pry'
Fetching: method_source-0.8.2.gem (100%)
Fetching: slop-3.6.0.gem (100%)
Fetching: coderay-1.1.0.gem (100%)
Fetching: pry-0.10.3.gem (100%)
=> true
>> Pry.start
[1] pry(main)>

Pretty cool, right? Although, it doesn't work well with bigger gems or those with native extensions.

Install

gem 'duck_puncher'

Usage

Ducks need to be loaded before they can be punched! Maybe put this in an initializer:

# config/initializers/duck_puncher.rb
DuckPuncher.punch_all!                  #=> punches all the ducks forever
DuckPuncher.punch! :Hash, :Object       #=> only punches the Hash and Object ducks

Create a new class of your favorite duck pre-punched:

DuckPuncher.punch :String               #=> returns an anonymous punched duck that inherits from String
DuckString = DuckPuncher.punch :String  #=> give the anonymous duck a name, so that you can use it!
DuckString.new.respond_to? :underscore  #=> true

That works, but it's pretty verbose and not real pactical to be managing potentially a bunch of custom classes. That's +why DuckPuncher defines a global punch method. This makes it easy to decorate your ducks on demand:

punch(:String, "yes").to_boolean

The new method isn't persisted on the String class. Instead it creates (and caches) a String delegation class. This class is pre-punched with the available string extensions! :punch:

Using a combination of Array#mm and Hash#dig:

params = { users: [{ id: 1, profile: { name: 'ryan' }}, { id: 2, profile: { name: 'kawika' }}, { id: 3 }] }
list = punch :Array, params[:users].map { |u| punch(:Hash, u) }
list.mm :dig, :profile, :name
#=> ["ryan", "kawika", nil]

Contributing