Scala 是否可以自定义光滑的代码生成,使生成的类扩展自定义特性?

Scala 是否可以自定义光滑的代码生成,使生成的类扩展自定义特性?,scala,slick,codegen,Scala,Slick,Codegen,我目前正在使用Slick codegen(版本3.2.0-M1)为数据库生成Slick代码。我的许多表都包含相同的列(具有相同的名称和类型),因此我希望有一些方法可以以通用方式对这些表执行操作,例如,一个通用方法可以根据特定的共享字段从这些表中选择行 为此,我可以创建一个包含这些共享字段的trait,然后让Slick表类扩展或混合它们。理想情况下,我希望代码生成器为我添加扩展或with 我看到生成器中有一个可重写的code方法,但我希望避免直接处理代码,例如通过正则表达式 我还没有在网上或光滑的

我目前正在使用Slick codegen(版本3.2.0-M1)为数据库生成Slick代码。我的许多表都包含相同的列(具有相同的名称和类型),因此我希望有一些方法可以以通用方式对这些表执行操作,例如,一个通用方法可以根据特定的共享字段从这些表中选择行

为此,我可以创建一个包含这些共享字段的trait,然后让Slick表类扩展或混合它们。理想情况下,我希望代码生成器为我添加
扩展
with

我看到生成器中有一个可重写的
code
方法,但我希望避免直接处理代码,例如通过正则表达式


我还没有在网上或光滑的文档中找到任何使用代码生成器定制的简单解决方案,因此,我想知道是否有人知道这是否可能。

我已经使用从
slick.codegen.AbstractSourceCodeGenerator
修改的代码覆盖了源代码生成器的几个可自定义方法:

/* `TableUtils` contains the definitions of the `GenericRow`
      and `GenericTable` traits. */
  override def code = "import data.TableUtils._\n" + super.code

  override def Table = new Table(_) {

    override def EntityType = new EntityTypeDef {

      /* This code is adapted from the `EntityTypeDef` trait's `code` method
         within `AbstractSourceCodeGenerator`.
         All code is identical except for those lines which have a corresponding
         comment above them. */
      override def code = {
        val args = columns.map(c=>
          c.default.map( v =>
            s"${c.name}: ${c.exposedType} = $v"
          ).getOrElse(
            s"${c.name}: ${c.exposedType}"
          )
        ).mkString(", ")
        if(classEnabled){
          /* `rowList` contains the names of the generated "Row" case classes we
              wish to have extend our `GenericRow` trait. */
          val newParents = if (rowList.contains(name)) parents :+ "GenericRow" else parents
          /* Use our modified parent class sequence in place of the old one. */
          val prns = (newParents.take(1).map(" extends "+_) ++ newParents.drop(1).map(" with "+_)).mkString("")
          s"""case class $name($args)$prns"""
        } else {
          s"""type $name = $types
              /** Constructor for $name providing default values if available in the database schema. */
              def $name($args): $name = {
              ${compoundValue(columns.map(_.name))}
              }
            """.trim
        }
      }
    }

    override def TableClass = new TableClassDef {

      /* This code is adapted from the `TableClassDef` trait's `code` method
         within `AbstractSourceCodeGenerator`.
         All code is identical except for those lines which have a corresponding
         comment above them. */
      override def code = {
        /* `tableList` contains the names of the generated table classes we
            wish to have extend our `GenericTable` trait. */
        val newParents = if (tableList.contains(name)) parents :+ "GenericTable" else parents
        /* Use our modified parent class sequence in place of the old one. */
        val prns = newParents.map(" with " + _).mkString("")
        val args = model.name.schema.map(n => s"""Some("$n")""") ++ Seq("\""+model.name.table+"\"")
        s"""class $name(_tableTag: Tag) extends profile.api.Table[$elementType](_tableTag, ${args.mkString(", ")})$prns {
            ${indent(body.map(_.mkString("\n")).mkString("\n\n"))}
            }
          """.trim()
      }
    }
  }
}

这个解决方案适用于我的目的,但是复制和修改源代码感觉有点不雅观。如果有人知道一种更好的方法,我很想看看你想出了什么。

使用中概述的解决方案,回答这个问题(我的问题也几乎和你的一样)。我们希望扩展Slick模型生成的代码,而不修改它(或者生成器)

在这里进行了调整,以实现自我控制、方便性,并适合我的特定用例。这是我的流畅代码gen生成的代码:

/** Table description of table user. Objects of this class serve as prototypes for rows in queries. */
class User(_tableTag: Tag) extends Table[UserRow](_tableTag, "user") { ... }
但是我需要我的
User
Slick数据模型来实现
be.objectify.deadbolt.java.models.Subject
java接口,这样我就可以作为Scala Play Web应用程序的一部分使用了

因此,我会:

import be.objectify.deadbolt.java.models.Subject     

/**
 * Mixin framework or infrastructural code
 */
trait DynamicMixinCompanion[TT] {
  implicit def baseObject[OT](o: Mixin[OT]): OT = o.obj

  def ::[OT](o: OT): Mixin[OT] with TT
  class Mixin[OT] protected[DynamicMixinCompanion](val obj: OT)
}

/**
 * Subject Mixin implementation
 */
object SubjectMixinHelper extends DynamicMixinCompanion[Subject] {
  def ::[T](o: T) = new Mixin(o) with Subject {
     def getPermissions = ...  
     def getRoles = ...
  }
}
最后:

import SubjectMixinHelper._
withSession{ implicit session =>
  val user = Query(User).where(_.id === id).firstOption :: Subject

  // then use user as a Subject too
  user.getPermissions
  user.getRoles
}

请注意,我还没有测试它(但我很快就会测试)

如果您想向每个生成的表中添加trait,您需要覆盖TableClassEntityType代码生成器traits中的parents方法:

class ExtSourceCodeGenerator(model: m.Model) extends SourceCodeGenerator(model: m.Model) {

  //Import packages with your traits
  override def code: String = "import models._\n" + super.code

  override def Table = new Table(_) {

    override def TableClass = new TableClass {
      //Add custom traits to Table classes
      override def parents: Seq[String] = {
        Seq("IdentityTable")
      }
    }

    override def EntityType = new EntityType {
      //Add custom traits to rows
      override def parents: Seq[String] = {
        Seq("Entity")
      }
    }
  }
}
如果需要筛选某些表,可以使用模型字段获取当前表名:

model.name.table

希望这能对你有所帮助。

哇,你找到了解决问题的办法,真好。授予!她不是很漂亮,但真见鬼!代码生成器只是运行并获得干净的scala数据模型。也就是说。我认为在Scala中可能有一种方法可以做到这一点。我之所以这么做,是因为我有一个类似的用例,可以在不更改代码的情况下动态地向类添加功能。