Skip to content

abstract struct Athena::ORM::Types::Type
inherits Struct #

Converts a mapped field's values between their Crystal representation and their database representation.

Every mapped field has a type, identified by its name, such as "string" or "datetime". It's inferred from the property's Crystal type, or set explicitly with the type argument of AORMA::Column. When an entity is loaded, its type converts each column into the value assigned to the property. When an entity is written, its type converts the property's value into something the database driver can bind.

@[AORMA::Entity]
class Product < AORM::Entity
  # Mapped as `bigint`, inferred from `Int64`.
  @[AORMA::Column]
  @[AORMA::ID]
  @[AORMA::GeneratedValue]
  property! id : Int64

  # Mapped as `decimal`, set explicitly.
  @[AORMA::Column(type: "decimal")]
  property! price : String
end

Built-in Types#

Name Type Crystal value Inferred from
string, text AORM::Types::String String String
smallint AORM::Types::SmallInt Int16 Int16
integer AORM::Types::Integer Int32 Int32, and enums whose base type fits in an Int32
bigint AORM::Types::BigInt Int64 Int64, and enums based on Int64, UInt32 or UInt64
smallfloat AORM::Types::SmallFloat Float32 Float32
float AORM::Types::Float Float64 Float64
decimal AORM::Types::Decimal String
number AORM::Types::Number BigDecimal BigDecimal
boolean AORM::Types::Boolean Bool Bool
datetime AORM::Types::Datetime Time Time
guid AORM::Types::Guid UUID UUID
binary AORM::Types::Binary Bytes
blob AORM::Types::Blob Bytes Bytes

A field whose Crystal type isn't inferred, and that has no explicit type, raises when its entity's metadata is built. Enum fields are stored as their member's integer value; see AORMA::Column for details.

Note

The number type only exists when the program defines BigDecimal, e.g. via require "big", since BigDecimal requires linking libgmp.

Warning

MySQL and MariaDB read DECIMAL columns as Float64, so on those databases decimal and number values are limited to Float64 precision and lose their scale, e.g. "12.3400" reads back as "12.34". SQLite stores them with REAL affinity, with the same limitation. Only Postgres round-trips them exactly.

Custom Types#

A custom type can store any Crystal value, such as a value object, in a column. Define a struct inheriting from this type, implementing the conversions in both directions, then register it under a name with .add_type.

# A value object holding an email address.
record Email, address : String

# Stores an `Email` as its address.
struct EmailType < AORM::Types::Type
  NAME = "email"

  def sql_declaration(column : AORM::Schema::Column, platform : AORM::Platforms::Platform) : String
    platform.string_type_declaration_sql column
  end

  def to_db(value : _, platform : AORM::Platforms::Platform)
    value.is_a?(Email) ? value.address : value
  end

  def to_crystal_value(value : _, platform : AORM::Platforms::Platform) : Email?
    case value
    when Nil, Email then value
    when String     then Email.new value
    else                 raise "Cannot convert #{value.class} to an Email."
    end
  end

  def to_crystal_value(value : DB::ResultSet, platform : AORM::Platforms::Platform) : Email?
    value.read(String?).try { |address| Email.new address }
  end
end

AORM::Types::Type.add_type EmailType::NAME, EmailType.new

The type can then be used by passing its name to AORMA::Column:

@[AORMA::Entity]
class User < AORM::Entity
  @[AORMA::Column]
  @[AORMA::ID]
  @[AORMA::GeneratedValue]
  property! id : Int64

  @[AORMA::Column(type: "email")]
  property! email : Email
end
  • #to_db converts a property's value into the value bound to the query. It must return something the driver can bind (DB::Any), otherwise the query raises.
  • #to_crystal_value(value : DB::ResultSet, platform) reads the next column from the result set while an entity is hydrated.
  • #to_crystal_value(value, platform) converts a value that has already been read, such as a database-generated identifier.
  • #sql_declaration returns the SQL type used to declare a column of this type.

Warning

A type that defines the to_crystal_value(value : _, platform) overload must also define the DB::ResultSet overload. Otherwise the value : _ overload would also receive the result set itself, since a subclass's overload takes precedence over the more specific one defined on this type.

The ORM makes a few assumptions about the values a type produces:

  • Changes are detected by comparing a field's value with == to the value it was loaded with. Value objects should therefore be immutable and compare by value, as a record does. Modifying a mutable object in place isn't detected; assign a new instance instead.
  • #to_db is only called for values being written or compared: the fields an INSERT writes, the changed fields an UPDATE writes, and identifiers and criteria values in WHERE clauses.
  • Entities are kept in the identity map under the string form of their identifier, so a value object used as an identifier needs a #to_s that differs for distinct values.

Todo

Value-object identifiers aren't accepted everywhere yet. AORM::EntityManager#find ids, repository criteria and AORM::NativeQuery#set_parameter take driver-bindable values (DB::Any) rather than value objects.

Direct known subclasses

Athena::ORM::Types::BigInt Athena::ORM::Types::Binary Athena::ORM::Types::Blob Athena::ORM::Types::Boolean Athena::ORM::Types::Datetime Athena::ORM::Types::Decimal Athena::ORM::Types::Float Athena::ORM::Types::Guid Athena::ORM::Types::Integer Athena::ORM::Types::SmallFloat Athena::ORM::Types::SmallInt Athena::ORM::Types::String

Constructors#

.get_type(name : ::String) : self#

Returns the type registered as name.

Raises if no type is registered with that name.

View source

Class methods#

.add_type(name : ::String, type : AORM::Types::Type) : Nil#

Registers type as name, so fields can be mapped to it with @[AORMA::Column(type: name)].

Raises if a type is already registered with that name; use .override_type to replace one.

View source

.has_type?(name : ::String) : Bool#

Returns true if a type is registered as name.

View source

.override_type(name : ::String, type : AORM::Types::Type) : Nil#

Replaces the type registered as name with type, e.g. to change how a built-in type converts its values.

Raises if no type is registered with that name.

View source

.type_map : Hash(::String, AORM::Types::Type.class)#

Returns the name of every registered type, mapped to its Type struct.

View source

.type_registry : Athena::ORM::Types::TypeRegistry#

Returns the registry holding an instance of every known type, keyed by name. It starts out with the built-in types.

View source

Methods#

#from_db_sql(sql_expression : ::String, platform : AORM::Platforms::Platform) : ::String#

Returns the SQL expression that converts sql_expression, a selected column, from its database representation. Returns it unchanged by default.

Applied to the columns of a SELECT.

View source

#initialize#

View source

abstract #sql_declaration(column : Schema::Column, platform : AORM::Platforms::Platform) : ::String#

Returns the SQL used to declare column as this type on platform, e.g. VARCHAR(255).

Built-in types delegate to the matching declaration method of AORM::Platforms::Platform, which custom types can reuse.

Todo

Nothing in the ORM generates schema yet, so this isn't called by the ORM itself.

View source

#to_crystal_value(value : DB::ResultSet, platform : Platforms::Platform)#

Reads the next column from value and converts it into the Crystal value this type represents. Returns nil for a NULL value.

Used while hydrating entities. Advances the cursor by exactly one column.

View source

abstract #to_crystal_value(value : _, platform : Platforms::Platform)#

Converts value, as read from the database, into the Crystal value this type represents. Returns nil for a NULL value, and raises if value can't be converted.

Used for values that have already been read, such as a database-generated identifier. Each type defines this once, as the place for its translation logic (parsing, narrowing, decoding, etc.). It accepts any input, since drivers disagree on the Crystal type of some columns, and validates it inside the body, e.g. with case value.

View source

#to_db(value : _, platform : AORM::Platforms::Platform)#

Converts value, a property's Crystal value, into the value bound to the query. Returns value unchanged by default.

The returned value must be one the database driver can bind (DB::Any), otherwise executing the query raises.

View source

#to_db_sql(sql_expression : ::String, platform : AORM::Platforms::Platform) : ::String#

Returns the SQL expression that converts sql_expression, a bound parameter's placeholder, to its database representation. Returns it unchanged by default.

Applied to the placeholders of values written by an INSERT or UPDATE, and of criteria values.

View source