Skip to content
Yuki Yoshinoya edited this page Nov 3, 2020 · 25 revisions

Using Models

Defining models

A pair of a case class and its companion object is mapped to a database table.

The case class, extends com.github.aselab.activerecord.ActiveRecord, corresponds to records in the table and provides CRUD logic, etc.

The companion object, extends com.github.aselab.activerecord.ActiveRecordCompanion[T], corresponds to the table and provides query logic, etc.

Note : T is a type of corresponding ActiveRecord class

package models

import com.github.aselab.activerecord._

case class Person(var name: String, var age: Int) extends ActiveRecord

object Person extends ActiveRecordCompanion[Person]

Mapping fields for Columns

By default, each field is mapped to a column in the database, where the convention for column names is all lower case separated by underscores. (e.g.createdDate maps onto a column created_date.)

It is possible to override a field’s column name with the com.github.aselab.activerecord.dsl.Column annotation. For example to change the name you can do:

package models

import com.github.aselab.activerecord._
import dsl._
import java.util.Date

case class Group(
  var name: String,
  @Column("CreatedDate") createdDate: Date
) extends ActiveRecord

object Group extends ActiveRecordCompanion[Group]

@Transient

if set attribute value for transient field, you can use the com.github.aselab.activerecord.dsl.Transient annotation.

package models

import com.github.aselab.activerecord._
import dsl._

case class User(
  var name: String,
) extends ActiveRecord {
  @Transient var fieldName = "Not saving the field"
}

object User extends ActiveRecordCompanion[User]

Nullable columns are mapped with Option[] fields

Note : More information is available in the Squeryl manual.

The default (and strongly recommended) way of mapping nullable columns to fields is with the Option[] type.

If you use Scala ActiveRecord to create (or generate) your table schema, all fields have a not null constraint, and Option[] fields are nullable.

package models

import com.github.aselab.activerecord._
import dsl._

case class Foo(var foo: String) extends ActiveRecord

object Foo extends ActiveRecordCompanion[Foo]

case class Bar(var bar: Option[String]) extends ActiveRecord

object Bar extends ActiveRecordCompanion[Bar]

...

Foo(null).save()  // => throws Exception 'NULL not allowed for column "FOO";'
Bar(None).save()  // => OK

Available field types

  • String
  • Boolean
  • Int
  • Long
  • Float
  • Double
  • BigDecimal
  • java.sql.Timestamp
  • java.util.Date
  • java.util.UUID

Automatic timestamping

Timestamps

With mixin Timestamps trait, automatically timestamps create and update operations.

(The timestamp table columns are created_at and updated_at. In model, createdAt, updatedAt)

case class Foo(var foo: String) extends ActiveRecord with Timestamps

object Foo extends ActiveRecordCompanion[Foo]

// val foo = Foo("foo").create()
// foo.createdAt // => created Timestamp
// foo.updatedAt // => updated Timestamp

Datestamps

With mixin Datestamps trait, automatically timestamps create and update operations.

(The datestamp table columns are created_on and updated_on. In model, createdOn, updatedOn)

case class Foo(var foo: String) extends ActiveRecord with Datestamps

object Foo extends ActiveRecordCompanion[Foo]

// val foo = Foo("foo").create()
// foo.createdOn // => created Date
// foo.updatedOn // => updated Date

Optimistic Locking

The Optimistic trait support optimistic locking. Each update to the record increments the occVersionNumber column and the locking facilities ensure that records instantiated twice will let the last one saved raise a StaleObjectException if the first was also updated. Example:

case class Book(var name: String, var price: Int) extends ActiveRecord with Optimistic

object Book extends ActiveRecordCompanion[Book]

val b1 = Book.head
val b2 = Book.head
b1.name = "update"
b1.save
b2.name = "other update"
b2.save  // throws com.github.aselab.activerecord.StaleObjectException

Optimistic locking will also check for stale data when objects are destroyed. Example:

val b1 = Book.head
val b2 = Book.head
b1.name = "update"
b1.save
b2.delete  // throws com.github.aselab.activerecord.StaleObjectException

Defining schema

A database schema is defined in the schema object that extends com.github.aselab.aselab.ActiveRecordTables.

Note : Its fully qualified class name must be written in application.conf. (Database Settings)

Adding table definition

Declare a val field with table[T] in the schema object.

Note : T is a type of ActiveRecord classes

package models

import com.github.aselab.activerecord._
import com.github.aselab.activerecord.dsl._

object Tables extends ActiveRecordTables {
  val people = table[Person]
  // You can also specify its table name. Default is the same name as its class.
  // val people = table[Person]("other_table_name")
}

Columns attributes

Note : More information is available in the Squeryl manual.

Table column properties (unique, index, auto_increment etc.) are also defined table[T].

For example to change the column attributes you can do:

package models

import com.github.aselab.activerecord._
import com.github.aselab.activerecord.dsl._

object Tables extends ActiveRecordTables {
  val people = table[Person]

  on(people)(t => declare(
    t.name is(unique, indexed("idxPeopleName"), dbType("varchar(255)")),
    t.name defaultsTo("defaultName"),
    t.age  is(indexed)
  ))
}

Initializing and finalizing schema

You must call initialize method of the schema object before using model classes. And then you should call cleanup method to release all resources.

If you are using Play with Guice to DI initialization logic with an AbstractModule, it is possible that the default scala-activerecord play plugin may have not fired yet and you will get a com.github.aselab.activerecord.SchemaSettingException. To fix this, you should ensure that your module encapsulates calls to initialize and cleanup against your schema tables definition.

Tables.initialize
...
Tables.cleanup

Basic operations

Create

Person("person1", 15).save()

Find

Person.findBy("name", "person1")                      // => Some(Person("person1", 15))
Person.findBy("name", "not exists").                  // => None
Person.findBy("name" -> "person1", "age" -> 15)       // => Some(Person("person1", 15))
Person.where(_.age gte 10).toList                     // => List(Person("person1", 15))
Person.all.toList                                     // => List(Person("person1", 15))

Update

The fields should be declared as var.

val person = Person.findBy("name", "person1").get
person.name = "new name"
person.age = 21
person.save

If you want to make it immutable, you should declare all fields as constructor arguments and use auto generated copy method of the case class.

Note : You must also declare override val id because id field is declared in base class.

case class Person(name: String, age: Int, override val id: Long = 0) extends ActiveRecord

val person = Person.findBy("name", "person1").get
person.copy(name = "new name", age = 21).save

Delete

// Delete single record
val person = Person.findBy("name", "person1").get
person.delete

// Delete all records
Person.deleteAll

Transaction

The transaction and inTransaction functions are available.

import models._
import com.github.aselab.activerecord.dsl._

Person.inTransaction {
  // transactional code block is here
}

transaction causes a new transaction to begin and commit after the block’s execution, or rollback if an exception occurs. Invoking a transaction always cause a new one to be created, even if called in the context of an existing transaction.

inTransaction will create a new transaction if none is in progress and commit it upon completion or rollback on exceptions. If a transaction already exists, it has no effect, the block will execute in the context of the existing transaction. The commit/rollback is handled in this case by the parent transaction block.