Подтвердить что ты не робот

Есть ли инструмент для автоматического создания комбинаторов парсеров Anorm?

Я только начинаю с комбинаторов Anorm и парсера. Кажется, что есть очень много шаблонов. Например, у меня есть

case class Model(
    id:Int,
    field1:String,
    field2:Int,
    // a bunch of fields omitted
)

val ModelParser:RowParser[RegdataStudentClass] = {
  int("id") ~
  str("field1") ~
  int("field2") ~
  // a bunch of fields omitted
  map {
    case id ~ field1 ~ field2 //more omissions
        => Model(id, field1, field2, // still more omissions
           )
  }
}

Каждое поле базы данных повторяется четыре (!) раза до того, как все это будет определено. Похоже, что парсер должен быть выведен полуавтоматически из класса case. Любые инструменты или другие методы, предлагающие уменьшить вовлеченную здесь работу?

Спасибо за любые указатели.

4b9b3361

Ответ 1

Вот решение, которое я в конечном итоге разработал. В настоящее время у меня это класс в моем проекте Play; он может (должен!) быть превращен в автономный инструмент. Чтобы использовать его, измените значение tableName val на имя вашей таблицы. Затем запустите его, используя main в нижней части класса. Он напечатает скелет класса case и комбинатор парсера. Большую часть времени эти скелеты требуют очень небольшой настройки.

Байрон

package tools

import scala.sys.process._
import anorm._

/**
 * Generate a parser combinator for a specified table in the database.
 * Right now it just specified with the val "tableName" a few lines
 * down.  
 * 
 * 20121024 bwbecker
 */
object ParserGenerator {

  val tableName = "uwdata.uwdir_person_by_student_id"


  /** 
   * Convert the sql type to an equivalent Scala type.
   */
  def fieldType(field:MetaDataItem):String = {
    val t = field.clazz match {
      case "java.lang.String" => "String"
      case "java.lang.Boolean" => "Boolean"
      case "java.lang.Integer" => "Int"
      case "java.math.BigDecimal" => "BigDecimal"
      case other => other
    }

    if (field.nullable) "Option[%s]" format (t)
    else t
  }

  /**
   * Drop the schema name from a string (tablename or fieldname)
   */
  def dropSchemaName(str:String):String = 
    str.dropWhile(c => c != '.').drop(1)

  def formatField(field:MetaDataItem):String = {
    "\t" + dropSchemaName(field.column) + " : " + fieldType(field)
  }

  /** 
   * Derive the class name from the table name:  drop the schema,
   * remove the underscores, and capitalize the leading letter of each word.
   */
  def deriveClassName(tableName:String) = 
    dropSchemaName(tableName).split("_").map(w => w.head.toUpper + w.tail).mkString

  /** 
   * Query the database to get the metadata for the given table.
   */
  def getFieldList(tableName:String):List[MetaDataItem] = {
      val sql = SQL("""select * from %s limit 1""" format (tableName))

      val results:Stream[SqlRow] = util.Util.DB.withConnection { implicit connection => sql()  }

      results.head.metaData.ms
    }

  /**
   * Generate a case class definition with one data member for each field in
   * the database table.
   */
  def genClassDef(className:String, fields:List[MetaDataItem]):String = {
    val fieldList = fields.map(formatField(_)).mkString(",\n")

    """    case class %s (
    %s
    )
    """ format (className, fieldList )
  }

  /**
   * Generate a parser for the table. 
   */
  def genParser(className:String, fields:List[MetaDataItem]):String = {

    val header:String = "val " + className.take(1).toLowerCase() + className.drop(1) + 
    "Parser:RowParser[" + className + "] = {\n"

    val getters = fields.map(f => 
      "\tget[" + fieldType(f) + "](\"" + dropSchemaName(f.column) + "\")"
    ).mkString(" ~ \n") 

    val mapper = " map {\n      case " + fields.map(f => dropSchemaName(f.column)).mkString(" ~ ") +
        " =>\n\t" + className + "(" + fields.map(f => dropSchemaName(f.column)).mkString(", ") + ")\n\t}\n}"

    header + getters + mapper
  }

  def main(args:Array[String]) = {

    val className = deriveClassName(tableName)
    val fields = getFieldList(tableName)

    println( genClassDef(className, fields) )

    println( genParser(className, fields))
  }
}

Ответ 2

Ну, вам вообще не нужно ничего повторять. Вы можете использовать flatten для создания кортежа, а затем создать экземпляр модели из этого кортежа:

(int("id") ~ str("field1") ~ int("field2"))
  .map(flatten)
  .map { tuple => (Model apply _).tupled(tuple) }

Однако, если вам нужно сделать некоторые дальнейшие преобразования, вам нужно как-то изменить кортеж:

(int("id") ~ str("field1") ~ int("field2"))
  .map(flatten)
  .map { tuple => (Model apply _).tupled(tuple.copy(_1=..., _2=....) }