giant-scala
GiantScala is wrapper on top of the Scala MongoDB driver to provide higher level functionality and better type-safety.
Quick Start
For people that want to skip the explanations and see it action, this is the place to start!
Dependency Configuration
libraryDependencies += "com.outr" %% "giant-scala" % "1.2.0"
If you are developing a cross-project with Scala.js support:
libraryDependencies += "com.outr" %%% "giant-scala" % "1.2.0"
Note: while MongoDB obviously doesn't work in the browser, the purpose of the Scala.js functionality is to allow shared model objects that extend from the base ModelObject
.
Case Classes
Most of the functionality in GiantScala is accessible via the import:
import com.outr.giantscala._
In order to represent our object, we begin with a case class:
case class Person(_id: String,
name: String,
age: Int,
created: Long = System.currentTimeMillis(),
modified: Long = System.currentTimeMillis()) extends ModelObject
Notice that this case class extends from ModelObject
. This is a simple trait that defines three required fields for all model objects:
_id: String
: A unique identifier in the databasecreated: Long
: The creation date of this documentmodified: Long
: The last modified date of this document
Database
In order to represent the tie-in to an actual MongoDB instance, we must create a database object:
object Database extends MongoDatabase(name = "giant-scala-tutorial") {
... collections listed here ...
}
DBCollection
Now that we have a Person
case class, we need to tie it to a database collection. To do this, we use the DBCollection
object:
class PersonCollection extends DBCollection[Person]("person", Database) {
override val converter: Converter[Person] = Converter.auto[Person]
val name: Field[String] = Field("name")
val age: Field[Int] = Field("age")
val created: Field[Long] = Field("created")
val modified: Field[Long] = Field("modified")
val _id: Field[String] = Field("_id")
override def indexes: List[Index] = List(
name.index.ascending.unique
)
}
There's a lot of boilerplate code in this class, but we can simplify this setup if we use the GiantScala SBT plugin. Add the following line to your project/plugins.sbt
file in your project:
addSbtPlugin("com.outr" % "giant-scala-plugin" % "1.2.0")
Now you can run sbt generateDBModels
and a PersonModel
class will be generated in your source directory. This simplifies set up by changing the signature of the PersonCollection
to:
class PersonCollection extends PersonModel("person", Database) {
override def indexes: List[Index] = List(
name.index.ascending.unique
)
}
The fields and converter are all defined in the PersonModel
class. The only things left to define are indexes.
Now, we need to update our Database
to include the collection:
object Database extends MongoDatabase(name = "giant-scala-tutorial") {
val person: PersonCollection = new PersonCollection
}
Initialize the Database
GiantScala supports advanced features like database upgrades that are handled during the initialization phase:
Database.init()
Note that init()
returns a Future[Unit]
that must complete before the database is fully usable. We'll talk about database upgrades later in this tutorial.
Inserting a Person
Now that our database is fully defined we can easily insert a person:
Database.person.insert(Person(_id = "john.doe", name = "John Doe", age = 30))
Note that this returns a Future[Either[DBFailure, Person]]
representing the success or failure of the operation.
Querying a Person
Now that there's a person in our database, we can query them back with:
Database.person.all()
This will return a Future[List[Person]]
For a more advanced query, GiantScala offers a type-safe implementation of aggregation:
import Database.person._
aggregate
.`match`(name === "John Doe")
.toFuture
Note that this will return a Future[List[Person]]
.
If we wanted a more simplistic, limited type to result from aggregation we could do:
import Database.person._
case class SimplePerson(_id: String, name: String)
aggregate
.project(name.include)
.`match`(name === "John Doe")
.as[SimplePerson]
.toFuture
This will return a Future[List[SimplePerson]]
. This can be extremely useful for complex queries to avoid being bound to the original type. It is also worth noting that instead of calling toFuture
you can call toQuery()
and it will generate a String
representation of the query that can be directly pasted into the mongo
REPL. This allows for much easier testing of complex queries.
Advanced Features (TODO: Give examples for each of these)
- Database Upgrades: Extend from
DatabaseUpgrade
and callDatabase.register(upgrade)
before callingDatabase.init()
- Batch Operations: GiantScala provides several features for batch operations. The typical path is to simply call
collection.batch
to get started - Key/Value Store:
Database.store
provides lots of functionality for storing key/value data into the database - Realtime Monitoring: GiantScala provides advanced functionality to monitor changes happening in real-time to the database (see
collection.monitor
)