Refinements, Hooks and Advanced Modules
26.1 Refinements (Scoped Monkey Patching)
module StringExtensions
refine String do
def palindrome?
clean = downcase.gsub(/[^a-z0-9]/, "")
clean == clean.reverse
end
end
end
# "racecar".palindrome? # NoMethodError here
using StringExtensions # active from here to the end of the file/module
"Racecar".palindrome? # true26.2 Hook Methods
self.included(base) / self.extended(base) / self.prepended(base) A module is mixed in
A class is subclassed (plugin registries) method_added / singleton_method_added / method_removed Methods are defined/removed
An undefined constant is referenced (autoloading) method_missing / respond_to_missing? An undefined method is called
initialize_copy / initialize_dup / initialize_clone An object is copied Numeric operations with your type
at_exit, ObjectSpace.define_finalizer Program exit / object GC
class Plugin
@registry = []
class << self
attr_reader :registry
def inherited(subclass)
super
Plugin.registry << subclass
end
end
end
class CsvExporter < Plugin; end
class JsonExporter < Plugin; end
Plugin.registry # [CsvExporter, JsonExporter]26.3 ActiveSupport::Concern Style (Plain Ruby)
module Auditable
def self.included(base)
base.extend(ClassMethods)
base.instance_variable_set(:@audited, [])
end
module ClassMethods
def audit(*fields) = @audited.concat(fields)
def audited_fields = @audited
end
def audit_log = self.class.audited_fields.to_h { [it, public_send(it)] }
end26.4 Autoloading and Code Loading
Zeitwerk (the gem used by Rails) autoloads constants from file names based on conventions.
require "json" # load a library/gem once
require_relative "lib/user" # relative to the current file
load "script.rb" # load every time
autoload :Parser, "parser" # lazy load on first constant reference
$LOAD_PATH.unshift File.expand_path("lib", __dir__)