monkey patching in ruby
TRANSCRIPT
Monkey Patchingin Ruby
by Anton Sakovich
@ Ottawa Ruby
Feb 24, 2015
What is monkey patching?Modification of an existing class at runtime with an intent tomodify or extend existing functionality.
Reopening classesclass Person attr_accessor :children def initialize(name) @name = name @children = [] endend
# some code omitted
class Person def <<(child) children << child endend
bob = Person.new('Bob')paul = Person.new('Paul')julie = Person.new('Julie')
bob << paulbob << julie
puts bob.children.count# 2
Example: merging nested hashesh1 = { first_name: "Bob", personal_data: { age: 30 }}h2 = { last_name: "Smith", personal_data: { weight: 160 }}
Age data is lost on simple mergeputs h1.merge(h2)# { first_name: "Bob",# personal_data: { weight: 160 },# last_name: "Smith"# }
ActiveSupport gem adds a method to merge nested hashesrequire 'active_support/core_ext/hash/deep_merge'puts h1.deep_merge(h2)# { first_name: "Bob",# personal_data: { age: 30, weight: 160 },# last_name: "Smith"# }
The new method is added by the Hash classreopeningclass Hash def deep_merge(other_hash, &block) # some code end def deep_merge!(other_hash, &block) # some code endend
Example: custom extensions - Rails# lib/core_extensions.rbclass DateTime def weekday? !sunday? && !saturday? endend
The patch can be wrapped in a module# lib/core_extensions/date_time/business_days.rbmodule CoreExtensions module DateTime module BusinessDays def weekday? !sunday? && !saturday? end end endend
and the module gets mixed inDateTime.include CoreExtensions::DateTime::BusinessDays
Example: overriding methodsclass Array alias_method :old_to_s, :to_s
def to_s stars = "\n" << '*' * 40 << "\n" "#{stars}#{old_to_s}#{stars}" endend
array = [1, 2, 3]puts array.to_s# ****************************************# [1, 2, 3]# ****************************************
Example: scoped patching in Ruby 2+module Starrable refine Array do alias_method :old_to_s, :to_s def to_s stars = "\n" << '*' * 40 << "\n" "#{stars}#{old_to_s}#{stars}" end endendclass Logger using Starrable def self.debug(x) puts x.to_s endend
Logger.debug [4, 5, 6]# ****************************************# [4, 5, 6]# ****************************************
and gems for Excelspreadsheets
Axlsx AxlsxStyler
With Axlsx + AxlsxStylerrequire 'axlsx_styler'axlsx = Axlsx::Package.newworkbook = axlsx.workbookworkbook.add_worksheet do |sheet| sheet.add_row sheet.add_row ["", "Product", "Category", "Price"] sheet.add_row ["", "Butter", "Dairy", 4.99 ] sheet.add_row ["", "Bread", "Baked Goods", 3.45 ] sheet.add_row ["", "Broccoli", "Produce", 2.99 ] sheet.column_widths 5, 20, 20, 20
# using AxlsxStyler DSL sheet["B2:D2"].add_style b: true sheet["B2:B5"].add_style b: true sheet["B2:D2"].add_style bg_color: "95AFBA" sheet["B3:D5"].add_style bg_color: "E2F89C" sheet["D3:D5"].add_style alignment: { horizontal: :left } sheet["B2:D5"].add_border sheet["B3:D3"].add_border [:top]endworkbook.apply_stylesaxlsx.serialize "grocery.xlsx"
Axlsx allows to retrieve an array of cells withsheet["B2:D5"]
This allows to for easy implementation of style applications,such as
sheet["B2:D2"].add_style b: truesheet["B2:D5"].add_bordersheet["B3:D3"].add_border [:top]
AxlsxStyler: a patch to the Array (for now)module AxlsxStyler module Array def add_style(style) validate_cells each do |cell| cell.add_style(style) end end def add_border(edges = :all) validate_cells selected_edges(edges).each do |edge| add_border_at(edge) end end # ... endend
require 'axlsx'require 'axlsx_styler/version'require 'axlsx_styler/array'require 'axlsx_styler/axlsx_extensions'
# adding the patchesArray.send :include, AxlsxStyler::ArrayAxlsx::Workbook.send :include, AxlsxStyler::Axlsx::WorkbookAxlsx::Cell.send :include, AxlsxStyler::Axlsx::Cell
Credits
Russ Olsen, Eloquent Ruby, Addison-Wesley 2011David A. Black, The Well-Grounded Rubyist, 2nd ed.,Manning 2014
http://www.justinweiss.com/blog/2015/01/20/3-ways-to-monkey-patch-without-making-a-mess