以 DRY 方式扩展 SLICK 表 [英] Extending SLICK Tables in a DRY manner

查看:35
本文介绍了以 DRY 方式扩展 SLICK 表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个关于 Slick/Scala 的有趣问题,我希望你们中的一个好人可以帮助我.

I have an interesting question around Slick/Scala that I am hoping that one of you nice chaps might be able to assist me with.

我有几个表,并且在 SLICK 案例类中进行了扩展

I have several tables and by extension in SLICK case classes

case class A(...)
case class B(...)
case class C(...)

共享这些公共字段

(id: String, livemode: Boolean, created: DateTime, createdBy : Option[Account]) . 

因为这些字段在每个案例类中都会重复,所以我想探索将它们提取到单个对象或类型中的可能性.

Because these fields are repeated in every case class, I'd like to explore the possibility of extracting them into a single object or type.

但是,在创建 SLICK 表对象时,我希望事情最终也包含在这些公共字段中,以便我可以在每个表中保留它们的各个值.

However, when creating the SLICK table objects I would like things to end up where these common fields are included too so I can persist their individual values in each table.

object AsTable extends Table[A]("a_table") { 
  ...
  def id = column[String]("id", O.PrimaryKey)
  def livemode = column[Boolean]("livemode", O.NotNull)
  def created = column[DateTime]("created", O.NotNull)
  def createdBy = column[Account]("created_by", O.NotNull)
  ... 
} 

实际上,我正在寻找的最终结果是允许我更改公共字段而无需更新每个表.

Effectively, the end result I'm looking for is to allow me make changes to the common fields without having to update each table.

有没有办法做到这一点?

Is there a way to do this?

提前致谢

推荐答案

这个我没试过,但是你混入一个特性怎么样:

I have not tried this, but how about a trait you mix in:

trait CommonFields { this: Table[_] =>
  def id = column[String]("id", O.PrimaryKey)
  def livemode = column[Boolean]("livemode", O.NotNull)
  def created = column[DateTime]("created", O.NotNull)
  def createdBy = column[Account]("created_by", O.NotNull)

  protected common_* = id ~ livemode ~ created ~ createdBy 
}

然后你可以这样做:

object AsTable extends Table[(String,Boolean,DateTime,Account,String)]("a_table") 
    with CommonFields { 
  def foo = column[String]("foo", O.NotNull)
  def * = common_* ~ foo
} 

现在唯一需要重复的是元素的类型.

The only thing you'll have to repeat now is the type of the elements.

更新

如果你想进行对象映射并且:

If you want to do object-mapping and:

  1. 您映射到案例类
  2. 案例类中的字段顺序相同

就去做:

case class A(
    id: String,
    livemode: Boolean,
    created: DateTime,
    createdBy: Account,
    foo: String)

object AsTable extends Table[A]("a_table") with CommonFields { 
  def foo = column[String]("foo", O.NotNull)
  def * = common_* ~ foo <> (A.apply _, A.unapply _)
}

这似乎是最经济的解决方案(而不是尝试在 CommonFields 中定义 * 并添加类型参数).但是,如果您的字段发生变化,则需要您更改所有案例类.

This seems to be the most economical solution (rather then trying to define * in CommonFields and adding a type parameter). However, it requires you to change all case classes if your fields change.

我们可以尝试通过在案例类上使用组合来缓解这种情况:

We could try to mitigate this by using composition on the case classes:

case class Common(
    id: String,
    livemode: Boolean,
    created: DateTime,
    createdBy: Account)

case class A(
    common: Common,
    foo: String)

然而,在构造映射器函数时,我们将(某处)最终不得不转换形式的元组:

However, when constructing the mapper function, we will (somewhere) end up having to convert tuples of the form:

(CT_1, CT_2, ... CT_N, ST_1, ST_2, ..., ST_M)

CT 通用类型(在 CommonFields 中已知)
ST 特定类型(在 AsTable 中已知)

CT Common type (known in CommonFields)
ST Specific type (known in AsTable)

致:

(CT_1, CT_2, ... CT_N), (ST_1, ST_2, ..., ST_M)

为了将它们传递给将 CommonA 与元组相互转换的子程序.

In order to pass them to subroutines individually converting Common and A to and from their tuples.

我们必须在不知道CT(在AsTable中实现时)或ST(当在在 CommonFields 中实现).Scala 标准库中的元组无法做到这一点.您需要使用 HLists 作为示例提供的 shapeless 来执行此操作.

We have to do this without knowing the number or the exact types of either CT (when implementing in AsTable) or ST (when implementing in CommonFields). The tuples in the Scala standard library are unable to do that. You would need to use HLists as for exampled offered by shapeless to do this.

这是否值得付出努力值得怀疑.

It is questionable whether this is worth the effort.

基本大纲可能如下所示(不需要所有隐含的混乱).这段代码不会像这样编译.

The basic outline could look like this (without all the implicit mess which will be required). This code will not compile like this.

trait CommonFields { this: Table[_] =>
  // like before

  type ElList = String :: Boolean :: DateTime :: Account :: HNil

  protected def toCommon(els: ElList) = Common.apply.tupled(els.tupled)
  protected def fromCommon(c: Common) = HList(Common.unapply(c))
}

object AsTable extends Table[A] with CommonFields {
  def foo = column[String]("foo", O.NotNull)

  def * = common_* ~ foo <> (x => toA(HList(x)), x => fromA(x) tupled)

  // convert HList to A
  protected def toA[L <: HList](els: L) = {
    // Values for Common
    val c_els = els.take[Length[ElList]]
    // Values for A
    val a_els = toCommon(c_els) :: els.drop[Length[ElList]]

    A.apply.tupled(a_els.tupled)
  }

  // convert A to HList
  protected def fromA(a: A) =
    fromCommon(a.common) :: HList(A.unapply(a)).drop[One]

}

使用更多类型魔法,您可能可以解决最后两个问题:

Using some more type magic you can probably resolve the last two issues:

  1. toAfromA 放入基本 trait(通过在 trait 中使用类型参数,或使用抽象类型成员)
  2. 避免通过使用 这种技术
  1. Put toA and fromA into the base trait (by using type parameters in the trait, or using abstract type members)
  2. Avoid defining ElList explicitly by extracting it from Common.apply by using this technique

这篇关于以 DRY 方式扩展 SLICK 表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆