Duck-typed interfaces for ruby.
It's a way to ensure that paramters respond to specific messages before heavy-lifting is done in a method.
2 reasons really:
- Some methods have a lot of heavy-lifting (e.g. database/network transactions). Once heavy, sometimes destructive, operations have occured the last thing a programmer wants is a
NoMethodError
in a later statement.duck_by_contract
doesn't even execute a programmer-defined-method unless the specified methods exist on calling parameters. - Timid code (i.e. code including a lot of
#is_a?
,#respond_to?
,#nil?
, etc...) is ugly and causes methods to become bloated. This gem provides a way to ensure parameters have expected behaviors without convolution, and it also provides a mechanism (duck_type_with_default
) to provide default behavior when interface parameters are not met.
To use it simply have your class extend DuckByContract
(when the gem is required/installed).
Then call duck_type
in the body of your class, giving it a hash where the key is the name of the method that you want to explicitly interface and the parameter is an array of arrays of method names (or just an array if the method only accepts parameter).
The duck_type_with_default
method can also be used to provide a block specifying how a method ought to behave if parameter's duck-types do not align
If you need more info, see the examples below.
require 'duck_by_contract'
class Persistor
extend DuckByContract
def persist(readable, db)
db.save(readable.read)
end
duck_type_with_default persist: [
[:read], [:save]
] do |maybe_readable, maybe_db|
raise "#{maybe_readable.inspect} or #{maybe_db.inspect} failed to meet its contract in #persist"
end
end
saver = Persistor.new
saver.persist("42", Object.new)
# => RuntimeError: 42 or #<Object:0x92d5ad8> failed to meet it's contract in #persist
require 'duck_by_contract'
class Horse
extend DuckByContract
def speak
puts 'Nay!'
end
def speak_with_other(vocal_thing)
puts 'I say:'
speak
puts 'Other says:'
vocal_thing.speak
end
duck_type speak_with_other: [:speak]
end
class Cup; end
class Duck
def speak
puts "Quack!"
end
end
ed = Horse.new
ugly = Duck.new
pint = Cup.new
ed.speak_with_other(ugly)
# =>
# I say:
# Nay!
# Other says:
# Quack!
ed.speak_with_other(pint)
# => Raises "DuckByContract::NotADuck"