Update to Eventide's Schema Library Attribute Type Checking and a Breaking Change

Eventide’s Schema library introduces a new protocol for attribute type checking with version 2.5.0.0, which is available immediately.

Incompatibility with Prior Versions

Along with the new capabilities for type checking, the strict type checking mode for attributes has been removed, and the strict argument of the attribute macro is no longer supported. If you use the strict argument in any of your schema class attribute definitions, your schema class definitions will fail to load.

Previous Behavior

In schema class definitions using v2.4.0.0 (and earlier), attributes were made type-safe by declaring the attribute with an optional type:

class SomeClass
  include Schema

  attribute :animal, Animal
end

The animal attribute could be assigned an instance of the Animal class, or any subclass of the Animal class.

class Animal
end

class Dog < Animal
end

some_object = SomeClass.new

some_object.animal = Animal.new

some_object.animal = Dog.new

To restrict a typed attribute to only accept instances of the attribute’s type, and to reject subclasses, the optional strict attribute could be used as part of the attribute declaration:

class SomeClass
  include Schema

  attribute :animal, Animal, strict: true
end

some_object = SomeClass.new

some_object.animal = Animal.new

some_object.animal = Dog.new
# => Schema::Attribute::TypeError

With strict attribute typing, polymorphism was not supported. The default behavior was permissive of subclasses unless specifically restricted.

New Implementation

In the latest version of the Schema library, the type-checking remains permissive by default, but the type checking is entirely customizable, allowing any kind of type checking to be specified, including being impermissive of polymorphism.

The new protocol requires a module named TypeCheck to be implemented in the type’s namespace:

class PositiveNumber
  module TypeCheck
    def self.call(type, val)
      return true if val.nil?
      return false unless val.is_a?(Numeric)

      val > 0
    end
  end
end

When a class implements the TypeCheck protocol, it will be executed to prove that the value assigned to the attribute conforms to the type checking implementation:

class SomeClass
  include Schema

  attribute :amount, PositiveNumber
end

some_object = SomeClass.new

some_object.amount = 123
# => 123

some_object.amount = -1
# => Schema::Attribute::Error

It’s important to note that the only type checking that will be performed will be the logic implemented in the type’s TypeCheck module. When the protocol is implemented, the developer must take full control over all aspects of the type check. The Schema library is entirely “hands-off” with regards to type checking when a type implements the TypeCheck protocol. Therefore, as seen in the above example, checking the fundamentals, like whether the value is nil or whether it’s an instance of some type, is entirely the responsibility of the particular TypeCheck implementation.

To implement the strict type checking behavior of v2.4.0.0 (and earlier), the TypeCheck protocol implementation would assert that the type of the value is exactly the type expected:

class Animal
  module TypeCheck
    def self.call(type, val)
      return true if val.nil?

      val.instance_of?(Animal)
    end
  end
end

Optional Type Checking

Optional type checking for attributes remains unchanged with this latest release. If an attribute is declared without a type, an instance of any class can be assigned to the attribute:

class SomeClass
  include Schema

  attribute :name
end

some_object = SomeClass.new

some_object.name = 'Some Name'
# => "Some Name"

some_object.name = 123
# => 123

Available Immediately

The v2.5.0.0 release of the Schema library is available as of the publication of this article. And again, schema class definitions that use attributes with the strict attribute must be updated to use the TypeCheck protocol.