Ruby class deconstruction is quite intuitive and essentially reuses array and hash deconstruction via the deconstruct and deconstruct_keys methods.
class Result
attr_reader :success, :info
def initialize(success:, info:)
@success = success
@info = info
end
def deconstruct
[ @success, @info ]
end
def deconstruct_keys(*)
{ success:, info: }
end
end
You can think of this as automatic type conversion before deconstruction.
There are two common ways of deconstructing in Ruby. One is using the rightward assignment operator =>.
result = Result.new(success: false, info: 'Not enough potatoes' )
result => x, y
puts x # false
puts y # Not enough potatoes
result => { info:, success: }
puts info # Not enough potatoes
puts success # false
The other is using pattern matching:
case result
in true, info
puts "Success with info: #{info}"
in false, info
puts "Failure with info: #{info}" # Failure with info: Not enough potatoes
end
case result
in success: true, info:
puts "Success with info: #{info}"
in success: false, info:
puts "Failure with info: #{info}" # Failure with info: Not enough potatoes
end
You can also match specific classes:
SomethingElse = Struct.new(:success, :info)
record = SomethingElse.new(true, 'All good')
case record
in Result[info:]
in SomethingElse(info:) # [] and () are interchangeable
puts "info: #{info}" # info: All good
end
Ruby's built-in Struct supports deconstruction out of the box.
Since [] is just a regular Ruby method, you can design a clean and expressive interface with it:
class Container
def self.[](value)
new(value)
end
def initialize(value)
@value = value
end
def deconstruct
[ @value ]
end
end
case Container[42]
in Container[value]
puts "Value is: #{value}" # Value is: 42
end
This way, the initialization Container[42] and the deconstruction Container[value] share the same interface.