Helpers for Scala Slick
This module actually does not depend on Slick.
libraryDependencies += "io.github.nafg" %% "slick-additions-entity" % "latest.release"
libraryDependencies += "io.github.nafg" %%% "slick-additions-entity" % "latest.release" // for Scala.jsIt defines abstractions to separate unsafe, low-level database implementation details from your models.
For instance, suppose you have a Person model which consists of a name text field and a numeric age.
You might store it in a person table that uses an auto-incremented BIGINT primary key. However, you might want to
work with a Person that has not been saved yet and therefore has no ID.
You might have other models that can contain references to a Person. For example, you might have a Document model
with an owner column. In the database it's just another BIGINT, except it has a referential integrity constraint
ensuring that it points to a valid person record. In code, you don't want to be dealing with raw Longs. You want
the owner field to have a type that ensures it refers to a Person.
Another way of looking at this is that sometimes we have data about a person (name and age) but not an ID, sometimes we have the ID of a person but not the data, and sometimes we have both. Also, some parts of the code need either the ID or the data and consider the other one optional.
We define an ADT / type hierarchy that allows you to be as concrete or abstract about this as appropriate, while
remaining fully typesafe. All types are parameterized by K, the key type, and A, the value type.
EntityRef: the common base trait, not very useful on its ownLookup: has a key, may or may not have a value. Useful for fields that represent a foreign key reference to another model.Entity: has a value, may or may not have a key. Useful when working with values that may or may not have been saved already.EntityKey: extendsLookup; has a key but no value. You can use this type statically to ensure equality is only comparing by key, for instance in aMap. When comparing explicitly you can just use thesameKeymethod.KeylessEntity: extendsEntity; has a value but no key.KeyedEntity: extendsLookupandEntity; has a key and a value. This is a trait extended by two case classes,SavedEntityandModifiedEntity.SavedEntitymeans it can be assumed that the entity is as it was in the database.ModifiedEntitymeans it was changed after it was retrieved. Usually you can useKeyedEntityas your static type (it has aapplyandunapplymethods) and if you care about whether it was modified you can check with theisSavedmethod. However, occasionally it may be useful to use one of the subtypes statically to communicate or to ensure if you're dealing with an entity that has changes that need to be saved.
To modify an entity use the modify method. Example:
import slick.additions.entity._
case class Person(name: String, age: Int)
def grow(personEnt: Entity[Long, Person]) = personEnt.modify(p => p.copy(age = p.age + 1))SavedEntity#modify will return a ModifiedEntity.
If you need to make a change that shouldn't clear its "saved" status, use transform instead of modify.
// In build.sbt
libraryDependencies += "io.github.nafg" %% "slick-additions" % "latest.release"
// In your codebase, use a custom Profile. For example:
import slick.jdbc._
import slick.additions.AdditionsProfile
trait SlickProfile
extends PostgresProfile // Replace with your database
with AdditionsProfile {
object myApi extends API with AdditionsApi
override val api = myApi
}
object SlickProfile extends SlickProfile
// Then, wherever you import the Slick API, import it from there
import SlickProfile.api._Mix this into your Table to be able to infer the names of columns, indexes, and foreign keys
from the name of the val or def (uses https://github.com/lihaoyi/sourcecode).
You'll need an implicit slick.additions.NameStyle.
If your names in code are camelCased and your database names are snake_case, just use AutoNameSnakify. It will
supply NameStyle.Snakify as the implicit.
For a name to be inferred, use column instead of col, foreignKey instead of foreign,
and index instead of idx.
Saves boilerplate defining an Entity-based Slick table, especially when combined with AutoName.
Example:
object People extends EntityTableModule[Long, Person]("material") {
class Row(tag: Tag) extends BaseEntRow(tag) with AutoNameSnakify {
val name = col[String]
val age = col[Int]
def mapping = (name, age).mapTo[Person]
}
}The TableQuery is defined for you as val Q, so you'd use it as, e.g., People.Q
EntityTableModule is built on EntityTable.
Simplifies defining tables based on slick-additions-entity.
Example:
class People(tag: Tag) extends EntityTable[Long, Person](tag, "doctor_contact") with AutoNameSnakify {
val name = col[String]
val age = col[Int]
override def mapping = (name, age).mapTo[Person]
override def tableQuery = People
}
object People extends EntTableQuery[Long, Person, People](new People(_))EntityTable builds on KeyedTable.
Useful for abstracting over tables that have a separate primary key column.
Alternative code generator, based on Scalameta. Pretty incomplete but very easy to extend, in your own codebase or by sending a PR.
Example usage:
// In build.sbt
libraryDependencies += "io.github.nafg" %% "slick-additions-codegen" % "latest.release"
import com.typesafe.config.ConfigFactory
import slick.jdbc.meta._
import slick.additions.codegen._
trait MyCodegenRulesBase extends EntityGenerationRules {
override def includeTable(table: MTable) =
table.name.schema.forall(_ == "public") && table.name.name != "flyway_schema_history"
}
object MyModelsCodeGenRules extends MyCodegenRulesBase {
override def packageName = "myapp.models"
override def container = "models"
override def extraImports = "myapp.JsonCodecs._" :: super.extraImports
}
object MyTablesCodeGenRules extends MyCodegenGenRulesBase {
override def packageName = "myapp.tables"
override def container = "Tables"
override def extraImports = "myapp.models._" :: "myapp.SlickColumnMappings._" :: super.extraImports
}
object MyModelsCodeGenerator extends KeylessModelsCodeGenerator with CirceJsonCodecModelsCodeGenerator
object MyTablesCodeGenerator extends EntityTableModulesCodeGenerator
val slickConfig = ConfigFactory.load().getConfig("slick.dbs.default")
MyModelsCodeGenerator.doWriteToFile(baseDir, slickConfig, MyModelsCodeGenRules)
MyTablesCodeGenerator.doWriteToFile(baseDir, slickConfig, MyTablesCodeGenRules)Provides a TestContainers instance of PostgreSQL with convenience methods for using Slick to access it.