Skip to content
Jiawei Li edited this page Apr 26, 2014 · 14 revisions

Associations overview

Associations methods

  • where
  • assign
  • <<
  • associate
  • :=
  • toOption

Eager loading associations

includes

You can load association while loading object using includes(association). (solution for the n+1 query problem) For example:

Order.includes(_.client).limit(10).foreach { order =>
  println(order.client.name)
}

This produces:

Select
  orders.price,
  orders.id
From
  orders
limit 10 offset 0;

Select
  clients.name,
  clients.age,
  clients.id
From
  clients inner join orders on (clients.id = orders.client_id)
Where
  (orders.id in (1,2,3,4,5,6,7,8,9,10))

Cache

When you want to clear the eager loading cache, using association#reload:

Client.head.orders.reload
Orders.head.client.reload

Defining Associations

One-to-One

package sample01

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

object Tables extends ActiveRecordTables {
  val suppliers = table[Supplier]
  val accounts = table[Account]
}

case class Supplier(name: String) extends ActiveRecord {
  // OneToOne relation
  lazy val account = hasOne[Account]
}

case class Account(number: String) extends ActiveRecord {
  val supplierId: Option[Long] = None
  lazy val supplier = belongsTo[Supplier]
}

object Supplier extends ActiveRecordCompanion[Supplier]
object Account extends ActiveRecordCompanion[Account]

object OneToOneSample extends App {
  Tables.initialize(Map("schema" -> "sample01.Tables"))

  val supplier = Supplier("supplier").create
  val account1 = Account("account1").create
  val account2 = Account("account2").create
  supplier.account := account1
  println(supplier.account.number)    // => account1
  println(account1.supplier.toOption) // => Some(Supplier("supplier1"))

  supplier.account := account2
  println(supplier.account.number)    // => account2
  println(account2.supplier.toOption) // => Some(Supplier("supplier2"))
  println(account1.supplier.toOption) // => None

  Tables.cleanup
}

One-to-Many

One-to-Many-image

package sample02

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

object Tables extends ActiveRecordTables {
  val users = table[User]
  val groups = table[Group]
}

case class User(name: String, isAdmin: Boolean = false) extends ActiveRecord {
  val groupId: Option[Long] = None
  // ManyToOne association
  lazy val group = belongsTo[Group]
}

case class Group(name: String) extends ActiveRecord {
  // OneToMany association
  lazy val users = hasMany[User]
  // only admin users association
  lazy val adminUsers = hasMany[User](conditions = Map("isAdmin" -> true))
}

object User extends ActiveRecordCompanion[User]
object Group extends ActiveRecordCompanion[Group]

object OneToManySample extends App {
  Tables.initialize(Map("schema" -> "sample02.Tables"))

  val user1 = User("user1").create
  val user2 = User("user2").create
  val group1 = Group("group1").create
  val group2 = Group("group2").create
  group1.users << user1
  group1.adminUsers << user2

  println(group1.users.toList) // => List(User("user1", false), User("user2", true))
  println(user1.group.orNull)  // => Group("group1")
  println(user2.isAdmin)       // => true

  Tables.cleanup
}

Many-to-Many

HABTM association

HABTM-image

package sample03

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

object Tables extends ActiveRecordTables {
  val users = table[User]
  val groups = table[Group]
}

case class User(name: String) extends ActiveRecord {
  // ManyToMany(HABTM) association 
  lazy val groups = hasAndBelongsToMany[Group]
}

case class Group(name: String) extends ActiveRecord {
  // ManyToMany(HABTM) association
  lazy val users = hasAndBelongsToMany[User]
}

object User extends ActiveRecordCompanion[User]
object Group extends ActiveRecordCompanion[Group]

object HasAndBelongsToManySample extends App {
  Tables.initialize(Map("schema" -> "sample03.Tables"))

  val user1 = User("user1").create
  val user2 = User("user2").create
  val group1 = Group("group1").create
  val group2 = Group("group2").create

  user1.groups := List(group1, group2)

  println(user1.groups.toList) // => List(Group("group1"), Group("group2"))
  println(group1.users.toList) // => List(User("user1"))

  Tables.cleanup
}

hasManyThrough association

HMT-image

package sample04

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

object Tables extends ActiveRecordTables {
  val users = table[User]
  val projects = table[Project]
  val roles = table[Role]
  val memberships = table[Membership]
}

case class User(name: String) extends ActiveRecord {
  lazy val memberships = hasMany[Membership]

  // ManyToMany(hasManyThrough) association 
  lazy val projects = hasManyThrough[Project, Membership](memberships)
}

case class Project(name: String) extends ActiveRecord {
  lazy val memberships = hasMany[Membership]

  // ManyToMany(hasManyThrough) association
  lazy val users = hasManyThrough[User, Membership](memberships)
}

case class Role(name: String) extends ActiveRecord {
  lazy val memberships = hasMany[Membership]
}

// Intermediate table's model
case class Membership(userId: Long, projectId: Long, roleId: Option[Long] = None) extends ActiveRecord {
  lazy val user = belongsTo[User]
  lazy val project = belongsTo[Project]
  lazy val role = belongsTo[Role]
}

object User extends ActiveRecordCompanion[User]
object Project extends ActiveRecordCompanion[Project]
object Role extends ActiveRecordCompanion[Role]
object Membership extends ActiveRecordCompanion[Membership]

object HasManyThroughSample extends App {
  Tables.initialize(Map("schema" -> "sample04.Tables"))

  val user1 = User("user1").create
  val user2 = User("user2").create
  val role1 = Role("role1").create
  val role2 = Role("role2").create
  val project1 = Project("project1").create
  val project2 = Project("project2").create

  val membership1 = user1.projects.assign(project1)
  membership1.role := Role("aaa")
  membership1.save

  user1.projects << project2
  project1.users << user2

  println(user1.projects.toList) //=> List(Project(project1), Project(project2))
  println(project2.users.exists(_.name === "user1")) //=> true

  Tables.cleanup
}

hasMany Association Reference

Options for hasMany

The hasMany association supports these options (now available):

  • conditions
  • foreignKey

conditions

The conditions option lets you specify the conditions that the associated object must meet via Map[String, Any].

case class User(name: String, isAdmin: Boolean = false) extends ActiveRecord {
  val groupId: Option[Long] = None
  lazy val group = belongsTo[Group]
}

case class Group(name: String) extends ActiveRecord {
  lazy val adminUsers = hasMany[User](conditions = Map("isAdmin" -> true))
}

If you use :conditions option, then record creation via this association will be automatically assign.

group.adminUsers << user    // => user.isAdmin == true

foreignKey

By convention, Scala ActiveRecord assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix Id added. The foreignKey option lets you set the name of the foreign key directly:

case class User(name: String) extends ActiveRecord {
  lazy val comments = hasMany[Comment](foreignKey = "authorId")
}

case class Comment(content: String) extends ActiveRecord {
  val authorId: Long = 0
  lazy val author = belongsTo[User](foreignKey = "authorId")
}
Clone this wiki locally