Delivered: Simple runtime type checking for Ruby method signatures
Signed, Sealed, Delivered 🎹
Delivered gives you the ability to define method signatures in Ruby, and have them checked at runtime. This is useful for ensuring that your methods are being called with the correct arguments, and for providing better error messages when they are not. It also serves as a nice way of documenting your methods using actual code instead of comments.
Usage
Simply define a method signature using the sig method directly before the method to be checked,
and Delivered will check that the method is being called with the correct arguments and types.
class User
extend Delivered::Signature
sig String, age: Integer
def create(name, age:)
"User #{name} created with age #{age}"
end
endIf an invalid argument is given to User#create, for example, if age is a String instead of
the required Integer, a Delivered::ArgumentError exception will be raised.
Delivered also provides a handy verify! method to verify the type of any value.
Delivered.verify! 1, Integer
Delivered.verify! :yes, T.Any(:yes, :no)Single and Double Splat Arguments
You can use single and double splats in your method signatures, and Delivered will pass them through without checking, while still checking the other named positional and keyword arguments.
sig String
def create(name, *args, foo:, **kwargs); endReturn Types
You can also check the return value of the method by passing a Hash with an Array as the key, and the value as the return type to check.
sig [String, age: Integer] => String
def create(name, age:)
"User #{name} created with age #{age}"
endOr by placing the return type in a block to sig.
sig(String, age: Integer) { String }
def create(name, age:)
"User #{name} created with age #{age}"
endDelivered Types
As well as Ruby's native types (ie. String, Integer, etc.), Delivered provides a couple of
extra types in Delivered::Types.
You can call these directly with Delivered::Types.Boolean, or for brevity, assign
Delivered::Types to T in your classes:
class User
extend Delivered::Signature
T = Delivered::Types
endThe following examples all use the T alias, and assumes the above.
Boolean
Value MUST be true or false. Does not support "truthy" or "falsy" values.
sig validate: T.Boolean
def create(validate:); endRespondTo
Value MUST be respond to the given method(s).
sig name: T.RespondTo(:to_s)
def create(name:); endRangeOf
Value MUST be a Range of the given type
sig name: T.RangeOf(Integer)
def create(name: 1...2); endArrayOf
Value MUST be an Array of the given type
sig names: T.ArrayOf(String)
def create(names: ['Joel', 'Ash']); endEnumerable
Value MUST be an Enumerable of the optional given type
sig users: T.Enumerable
def create(users: [1, 2]); endsig users: T.Enumerable(User)
def create(users: User.all); endAny
Value MUST be any of the given list of values, that is, the value must be one of the given list.
sig T.Any(:male, :female)
def create(gender); endIf no type is given, the value CAN be any type or value.
sig save: T.Any
def create(save: nil); endYou can also pass nil to allow a nil value alongside any other types you provide.
sig T.Any(String, nil)
def create(save = nil); endNilable
When a type is given, the value MUST be nil OR of the given type.
sig save: T.Nilable(String)
def create(save: nil); end
sig T.Nilable(String)
def update(name = nil); endIf no type is given, the value CAN be nil. This essentially allows any value, including nil.
sig save: T.Nilable
def create(save: nil); endYou may notice that Nilable is interchangeable with Any. The following are equivilent:
sig save: T.Nilable
def create(save: nil); endsig save: T.Any
def create(save: nil); endAs are these:
sig T.Nilable(String)
def update(name = nil); endsig T.Any(String, nil)
def update(name = nil); end