Skip to content

Instantly share code, notes, and snippets.

@jordan-brough
Last active August 24, 2023 13:37
Show Gist options
  • Save jordan-brough/15d1bfc2378a36a1a2a1 to your computer and use it in GitHub Desktop.
Save jordan-brough/15d1bfc2378a36a1a2a1 to your computer and use it in GitHub Desktop.
Ruby Examine
# to always include this in IRB save this to ~/.irbrc.examine and add this to your ~/.irbrc:
#
# load File.expand_path('../.irbrc.examine', __FILE__)
#
module JordanBrough
module Examine
module InstanceMethods
def examine(*attrs, printer: :default)
JordanBrough::Examine.examine(self, *attrs, printer: printer)
end
end
module_function
# for console usage
# outputs columnized + justified inspection of selected attributes.
# see Object#pluck for valid attrs
#
# examples:
#
# User.all.random(2).examine(:name, :email)
# name email
# Jordan Brough jordan+a4c@animoto.com
# Jordan Brough jordan+sep30_2@animoto.com
# => nil
#
# User.all.random(5).examine(:name, :email, { subscription_count: -> { _1.subscriptions.count } }])
# name email subscription_count
# Jordan Brough jordan+oct21@animoto.com 1
# John Doe john@john.com 0
# Tom Clifton tom@animoto.com 0
# Jordan Brough jordan+a4c@animoto.com 2
# Jordan Brough jordan+b2b@animoto.com 0
# => nil
# def examine(object, *attrs, &block)
# require 'date'
# array = object.respond_to?(:to_ary) ? object.to_ary : [object]
# printer = block || lambda do |keys, hashes, val_to_s|
# max_widths = {}
# keys.each do |key|
# max_widths[key] = ([key.to_s.size] + hashes.map { |hash| val_to_s.call(hash[key]).size }).max
# end
# keys_str = keys.map { |key| key.to_s.ljust(max_widths[key]) }.join(' ') + "\n"
# keys_str + hashes.map do |hash|
# hash.map { |key, val| val_to_s.call(val).ljust(max_widths[key]) }.join(' ')
# end.join("\n")
# end
# val_to_s = lambda do |val|
# case val
# when Time, DateTime then val.strftime('%Y-%m-%d %H:%M:%S')
# when Date then val.strftime('%Y-%m-%d')
# else val.to_s
# end
# end
# keys = attrs.map { |attr| attr.is_a?(Hash) ? attr.keys.first : attr }
# hashes = array.map { |item| pluck(item, attrs) } # note: pluck returns an ActiveSupport::OrderedHash
# printer.call(keys, hashes, val_to_s)
# end
# def pluck(object, attrs)
# key_vals = attrs.map do |item|
# key = item.is_a?(Hash) ? item.keys.first : item
# val = item.is_a?(Hash) ? item.values.first : item
# val = if val.respond_to?(:call)
# if val.respond_to?(:arity) && val.arity.abs > 0
# val.call(object)
# else
# val.call
# end
# else
# object.instance_eval(val.to_s)
# end
# [key, val]
# end
# Hash[key_vals]
# end
def examine(object, *attrs, printer:)
require 'date'
array = object.respond_to?(:to_ary) ? object.to_ary : [object]
keys = extract_keys(attrs)
hashes = array.map { |item| pluck(item, attrs) }
case printer
when :default
default_print(keys, hashes)
when :csv
csv_print(keys, hashes)
else
raise "unknown printer #{printer}"
end
end
def default_print(keys, hashes)
max_widths = calculate_max_widths(keys, hashes)
format_keys(keys, max_widths) + format_hashes(hashes, max_widths).join("\n")
end
def csv_print(keys, hashes)
require 'csv'
keys.to_csv + hashes.map do |hash|
hash.map { |key, val| value_to_string(val) }.to_csv
end.join
end
def calculate_max_widths(keys, hashes)
keys.each_with_object({}) { |key, max_widths| max_widths[key] = max_width_for_key(key, hashes) }
end
def max_width_for_key(key, hashes)
([key.to_s.size] + hashes.map { |hash| value_to_string(hash[key]).size }).max
end
def format_keys(keys, max_widths)
keys.map { |key| key.to_s.ljust(max_widths[key]) }.join(' ') + "\n"
end
def format_hashes(hashes, max_widths)
hashes.map { |hash| format_hash(hash, max_widths) }
end
def format_hash(hash, max_widths)
hash.map { |key, val| value_to_string(val).ljust(max_widths[key]) }.join(' ')
end
def value_to_string(val)
case val
when Time, DateTime then val.strftime('%Y-%m-%d %H:%M:%S')
when Date then val.strftime('%Y-%m-%d')
else val.to_s
end
end
def extract_keys(attrs)
attrs.flat_map { |attr| attr.is_a?(Hash) ? attr.keys : attr }
end
def pluck(object, attrs)
Hash[attrs.flat_map { |item| extract_key_values(item, object) }]
end
def extract_key_values(__item__, __object__)
__item__.is_a?(Hash) ? __item__.map { |key, val| [key, evaluate_value(val, __object__)] } : [[__item__, __object__.instance_eval(__item__.to_s)]]
end
def evaluate_value(__val__, __object__)
__val__.respond_to?(:call) ? (__val__.respond_to?(:arity) && __val__.arity.abs > 0 ? __val__.call(__object__) : __val__.call) : __object__.instance_eval(__val__.to_s)
end
end
end
Object.send(:include, JordanBrough::Examine::InstanceMethods) # using "send" for Ruby < 2.1 compatibility
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment