defi
Prepares a method call, and sends it to any object.
A challenge is a method name with its arguments, as you would write them after a dot. Sent to an object, it brings back what came out of the call: the value the method returned, or the exception it raised. The same challenge can be sent to as many objects as you like, to compare what each one does with it.
- Version
- 4.0.0
- Ruby
- 3.3 or later
- Dependencies
- None
- License
- MIT
Install
Add gem "defi" to your Gemfile, or run gem install defi.
Usage
A challenge takes positional arguments, keyword arguments and a block, and passes them as they are. to sends it to an object and returns a value; object is what came back, raised? says whether it was raised, and call returns the object or raises the exception again:
require "defi"
challenge = Defi::Challenge.new(:fetch, :b, 0)
challenge.to_s # => ".fetch(:b, 0)"
value = challenge.to({ b: 2 })
value.raised? # => false
value.object # => 2
value.call # => 2
value = Defi::Challenge.new(:fetch, :b).to({})
value.raised? # => true
value.object # => #<KeyError: key not found: :b>
value.call rescue $!.class # => KeyError
Defi::Challenge.new(:encode, "US-ASCII", undef: :replace).to("café").object # => "caf?"
Defi::Challenge.new(:map) { |n| n * 2 }.to([1, 2]).object # => [2, 4]
A challenge and a value are frozen; their arguments are the objects given, not copies.
What comes back
The message is sent with public_send: a private method, or a BasicObject, which has no public_send, gives a NoMethodError. A challenge keeps as its value the exceptions a method raises on its own: a StandardError, a ScriptError such as NotImplementedError, a SystemStackError, or the SystemExit of an exit. What is not the method’s doing goes through: an Interrupt, a signal, a NoMemoryError, the exception a Timeout uses to end its block, and any other subclass of Exception.
require "defi"
# What a method raises on its own comes back as the value.
Defi::Challenge.new(:+, 1).to("a").object.class # => TypeError
Defi::Challenge.new(:secret).to(Object.new).object.class # => NoMethodError
exiting = Object.new
def exiting.call = exit(3)
Defi::Challenge.new(:call).to(exiting).object.status # => 3
# What is not the method's doing goes through.
interrupting = Object.new
def interrupting.call = raise(Interrupt)
outcome = begin
Defi::Challenge.new(:call).to(interrupting)
rescue Interrupt
:went_through
end
outcome # => :went_through
Documentation
- README: usage, what comes back, and the changes in 4.0.
- API reference
- RubyGems
- Source code