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
- Fork it
- Run tests with
rake - Start an IRB console that already has all your ducks in a row
bin/console - Make changes and submit a PR to https://github.com/ridiculous/duck_puncher