Skip to content

v1.1.0

Compare
Choose a tag to compare
@kylekthompson kylekthompson released this 14 Oct 16:06
· 38 commits to main since this release
e24dda6

Strictly Define Interfaces (and more!)

Strict::Interface

With this release, we've introduced Strict::Interface- a new way to strictly validate a cohesive set of functionality that might have different implementations. Consider, for example, a codebase that offers a Storage class:

class Storage
  def write(key:, contents:)
    # write to file
  end

  def read(key:)
    # read from file
  end
end

Within your tests, rather than writing to the filesystem you might want to write to an in-memory store. Where you typically might rely on duck typing, you can now define a Strict::Interface and multiple implementations which conform to that interface. See below for an example:

class Storage
  extend Strict::Interface

  expose(:write) do
    key String
    contents String
    returns Boolean()
  end

  expose(:read) do
    key String
    returns AnyOf(String, nil)
  end
end

module Storages
  class Memory
    def initialize
      @storage = {}
    end

    def write(key:, contents:)
      storage[key] = contents
      true
    end

    def read(key:)
      storage[key]
    end

    private

    attr_reader :storage
  end
end

storage = Storage.new(Storages::Memory.new)
# => #<Storage implementation=#<Storages::Memory>>

storage.write(key: "some/path/to/file.rb", contents: "Hello")
# => true

storage.write(key: "some/path/to/file.rb", contents: {})
# => Strict::MethodCallError

storage.read(key: "some/path/to/file.rb")
# => "Hello"

storage.read(key: "some/path/to/other.rb")
# => nil

module Storages
  class Wat
    def write(key:)
    end
  end
end

storage = Storage.new(Storages::Wat.new)
# => Strict::ImplementationDoesNotConformError

Strict Attributes Coercers

You can now easily coerce into objects or values defined with strict attributes. ValueOrObjectClass.coercer will provide a coercer that will turn a hash-like object into an instance of the class.

class Money
  include Strict::Value

  attributes do
    amount_in_cents Integer
    currency AnyOf("USD", "CAD"), default: "USD"
  end
end

class Transaction
  include Strict::Value

  attributes do
    from_account String
    to_account String
    amount Money, coerce: Money.coercer
  end
end

Transaction.new(from_account: "1", to_account: "2", amount: { amount_in_cents: 100_00 })
# => #<Transaction from_account="1" to_account="2" amount=#<Money amount_in_cents=100_00 currency="USD">>

What's Changed

Full Changelog: v1.0.0...v1.1.0