Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
/spec/reports/
/tmp/
spec/examples.txt

.ruby-version
.ruby-gemset
51 changes: 51 additions & 0 deletions lib/dry/core/inspect.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

module Dry
module Core
class Inspect < ::Module
def initialize(*attributes, **value_methods_map)
super()

define_inspect_methods(attributes.flatten(1), **value_methods_map)
end

private

def define_inspect_methods(attributes, **value_methods_map)
define_inspect_method(attributes, **value_methods_map)
define_pretty_print(attributes, **value_methods_map)
end

def define_inspect_method(attributes, **value_methods_map)
define_method(:inspect) do
instance_inspect = if self.class.name
"#<#{self.class.name}"
else
Kernel.instance_method(:to_s).bind(self).call.chomp!(">")
end

inspect = attributes.map { |name|
"#{name}=#{__send__(value_methods_map[name] || name).inspect}"
}.join(", ")
"#{instance_inspect} #{inspect}>"
end
end

def define_pretty_print(attributes, **value_methods_map)
define_method(:pretty_print) do |pp|
object_group_method = self.class.name ? :object_group : :object_address_group
pp.public_send(object_group_method, self) do
pp.seplist(attributes, -> { pp.text "," }) do |name|
pp.breakable " "
pp.group(1) do
pp.text name.to_s
pp.text "="
pp.pp __send__(value_methods_map[name] || name)
end
end
end
end
end
end
end
end
65 changes: 65 additions & 0 deletions spec/dry/core/inspect_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

require "pp"

RSpec.describe Dry::Core::Inspect do
subject(:inspect) { Dry::Core::Inspect.new(:fullname, :secret_question_answer, **value_methods_map) }

let(:value_methods_map) { {} }
let(:klass) do
Class.new do
attr_reader :fullname, :secret_question_answer
private :fullname, :secret_question_answer

def initialize(fullname, secret_question_answer)
@fullname = fullname
@secret_question_answer = secret_question_answer
end

private

def secret_question_answer_encrypted
@secret_question_answer * 42
end
end
end
let(:instance) { klass.new("John Doe", 42) }

before { klass.include(inspect) }

it "defines a pretty_print method" do
expect(instance.pretty_inspect)
.to match(/\A#<#<Class:0x[a-f0-9]{16}>:0x[a-f0-9]{16}\n fullname="John Doe",\n secret_question_answer=42>\n\z/)
end

it "defines an inspect method" do
expect(instance.inspect)
.to match(/\A#<#<Class:0x[a-f0-9]{16}>:0x[a-f0-9]{16} fullname="John Doe", secret_question_answer=42>\z/)
end

context "with a non-anonymous class" do
before { stub_const("User", klass) }

it "uses the class name in the pretty_inspect output" do
expect(instance.pretty_inspect).to eq("#<User fullname=\"John Doe\", secret_question_answer=42>\n")
end

it "uses the class name in the inspect output" do
expect(instance.inspect).to eq("#<User fullname=\"John Doe\", secret_question_answer=42>")
end
end

context "with a value method given" do
let(:value_methods_map) { {secret_question_answer: :secret_question_answer_encrypted} }

it "uses the value method in the pretty_inspect output" do
expect(instance.pretty_inspect)
.to match(/\A#<#<Class:0x[a-f0-9]{16}>:0x[a-f0-9]{16}\n fullname="John Doe",\n secret_question_answer=1764>\n\z/)
end

it "uses the value method in the inspect output" do
expect(instance.inspect)
.to match(/\A#<#<Class:0x[a-f0-9]{16}>:0x[a-f0-9]{16} fullname="John Doe", secret_question_answer=1764>\z/)
end
end
end