Using raw types such as Long, java.util.UUID, and Option[Long] for database ids invites errors.
Using raw types such as Long, java.util.UUID, and Option[Long] for database ids invites errors.
Scala developers should instead use the Id and HasId wrapper types provided by this project
because of the type safety they provide over raw types.
Id and HasId are database-agnostic.
Both auto-increment Ids and Ids whose value is defined before persisting them are supported.
Id
Id can wrap Long, UUID and String values, and any of them can be optional.
The supported flavors of Id are:
Id[Option[Long]] - commonly used with autoincrement columns
Id[Option[UUID]]
Id[Option[String]]
Id is a Scala value object, which means there is little or no runtime cost for using it as compared to the value that it wraps.
In other words, there is no penalty for boxing and unboxing.
Convenience Types
For convenience, the following types are defined in Types:
OptionLong – Option[Long]
OptionString – Option[String]
OptionUuid – Option[UUID]
IdLong – Id[Long]
IdString – Id[String]
IdUuid – Id[UUID]
IdOptionLong – Id[Option[Long]
IdOptionString – Id[Option[String]]
IdOptionUuid – Id[Option[UUID]]
Id.empty
Ids define a special value, called empty.
Each Id flavor has a unique value for empty.
FYI, the values for empty are:
IdUuid.empty == new UUID(0, 0)
IdLong.empty == 0L
IdString.empty == ""
IdOptionUuid.empty = None
IdOptionLong.empty = None
IdOptionString.empty = None
Depending on the context, you might need to provide type ascription when using Id.empty.
For example, IdUuid.empty or IdOptionLong.empty.
Id.toOption
You can use the Id.toOption method to convert from an IdLong or IdUuid to IdOptionLong or IdOptionUuid.
Each case class that uses Id to represent the persisted record id in the database must extend HasId.
HasId is a parametric type with two type parameters:
The first type parameter must match the name of the case class
The second type parameter must match the type of the Id for the case class.
For example, you can make MyCaseClass extend HasId by writing something like this for the extended type:
HasId[MyCaseClass, Long]
HasId[MyCaseClass, UUID]
HasId[MyCaseClass, String]
HasId[MyCaseClass, OptionLong] – Most commonly used flavor
HasId[MyCaseClass, OptionUuid]
HasId[MyCaseClass, OptionString]
Usage Examples
Here are examples of using Id and HasId:
Simple Example
/** A person can have at most one Dog.
* Because their Id is based on `OptionUuid`, those `Id`s do not always have `Some` value */caseclass Person(
age: Int,
name: String,
dogId: Id[OptionLong],
overrideval id: Id[UUID] = Id(UUID.randomUUID) // Id type (UUID) matches the HasId type (also UUID)
) extends HasId[Person, UUID]
/** Dogs are territorial. They ensure that no other Dogs are allowed near their FavoriteTrees.
* Because the Ids for Dog and FavoriteTree are based on Option[Long] and not UUID,
* those Ids might have value None until they are persisted */caseclass Dog(
species: String,
color: String,
overrideval id: Id[OptionLong] = Id.empty
) extends HasId[Dog, OptionLong]
HasId Sub-Subclasses
Subclasses of HasId subclasses should be parametric.
In the following example, Rateable is an abstract class that subclasses HasId.
Notice that Rateable is parametric in T, and HasId's first type parameter is also T:
Using raw types such as
Long
,java.util.UUID
, andOption[Long]
for database ids invites errors. Scala developers should instead use the Id and HasId wrapper types provided by this project because of the type safety they provide over raw types.Id
andHasId
are database-agnostic. Both auto-incrementId
s andId
s whose value is defined before persisting them are supported.Id
Id
can wrapLong
,UUID
andString
values, and any of them can be optional. The supported flavors ofId
are:Id[Long]
- maps to PostgresBIGINT
orBIGSERIAL
Id[UUID]
- do not misuseId[String]
Id[Option[Long]]
- commonly used with autoincrement columnsId[Option[UUID]]
Id[Option[String]]
Id
is a Scala value object, which means there is little or no runtime cost for using it as compared to the value that it wraps. In other words, there is no penalty for boxing and unboxing.Convenience Types
For convenience, the following types are defined in Types:
OptionLong
–Option[Long]
OptionString
–Option[String]
OptionUuid
–Option[UUID]
IdLong
–Id[Long]
IdString
–Id[String]
IdUuid
–Id[UUID]
IdOptionLong
–Id[Option[Long]
IdOptionString
–Id[Option[String]]
IdOptionUuid
–Id[Option[UUID]]
Id.empty
Id
s define a special value, calledempty
. EachId
flavor has a unique value forempty
. FYI, the values forempty
are:IdUuid.empty == new UUID(0, 0)
IdLong.empty == 0L
IdString.empty == ""
IdOptionUuid.empty = None
IdOptionLong.empty = None
IdOptionString.empty = None
Depending on the context, you might need to provide type ascription when using
Id.empty
. For example,IdUuid.empty
orIdOptionLong.empty
.Id.toOption
You can use the
Id.toOption
method to convert from anIdLong
orIdUuid
toIdOptionLong
orIdOptionUuid
.Be sure to cast the result to the desired
Id
subtype, otherwise you'll get a weird unhelpful type:HasId
Each case class that uses
Id
to represent the persisted record id in the database must extendHasId
.HasId
is a parametric type with two type parameters:Id
for the case class.For example, you can make
MyCaseClass
extendHasId
by writing something like this for the extended type:HasId[MyCaseClass, Long]
HasId[MyCaseClass, UUID]
HasId[MyCaseClass, String]
HasId[MyCaseClass, OptionLong]
– Most commonly used flavorHasId[MyCaseClass, OptionUuid]
HasId[MyCaseClass, OptionString]
Usage Examples
Here are examples of using
Id
andHasId
:Simple Example
HasId Sub-Subclasses
Subclasses of
HasId
subclasses should be parametric. In the following example,Rateable
is an abstract class that subclassesHasId
. Notice thatRateable
is parametric inT
, andHasId
's first type parameter is alsoT
:The following two
Rateable
subclasses provide values forT
that match the names of the derived classes: