Last active
August 24, 2023 13:37
-
-
Save jordan-brough/15d1bfc2378a36a1a2a1 to your computer and use it in GitHub Desktop.
Ruby Examine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 [email protected] | |
# Jordan Brough [email protected] | |
# => nil | |
# | |
# User.all.random(5).examine(:name, :email, { subscription_count: -> { _1.subscriptions.count } }]) | |
# name email subscription_count | |
# Jordan Brough [email protected] 1 | |
# John Doe [email protected] 0 | |
# Tom Clifton [email protected] 0 | |
# Jordan Brough [email protected] 2 | |
# Jordan Brough [email protected] 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