2012-10-23 10 views
12

Właśnie zaczynam od Anorm i kombinatorów parsera. Wygląda na to, że jest strasznie dużo kodu standardowego. Na przykład: MamCzy istnieje narzędzie do automatycznego generowania kombinatorów analizatorów 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 
      ) 
    } 
} 

Każde pole bazy danych powtarza się cztery (!) Razy, zanim cała rzecz zostanie zdefiniowana. Wygląda na to, że parser powinien być w stanie wywnioskować półautomatycznie z klasy case. Jakieś narzędzia lub inne techniki sugerujące zmniejszenie pracy tutaj?

Dzięki za wszelkie wskazówki.

+0

Mam dokładnie ten sam problem z użyciem anorm. Podejrzewam, że odpowiedź nie polega na używaniu anorm w ogóle. Zauważyłem, że Slick (wcześniej ScalaQuery) jest drogą do przodu, używając makr, aby zmniejszyć boom. Niestety, makra wymagają Scala 2.10. Zobacz także: http://stackoverflow.com/questions/11379608/play-framework-slick-scalaquery-tutorial –

Odpowiedz

3

Oto rozwiązanie, które ostatecznie opracowałem. Obecnie mam to jako lekcję w moim projekcie Play; może (powinno!) zostać przekształcone w samodzielne narzędzie. Aby z niego skorzystać, zmień wartość parametru tableName val na nazwę swojej tabeli. Następnie uruchom go przy użyciu klasy main u dołu klasy. Wypisze szkielet klasy case i combinator parsera. Przez większość czasu szkielety te wymagają niewielkiego podkręcania.

Byron

package tools 

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

/** 
* Generate a parser combinator for a specified table in the database. 
* Right now it's 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)) 
    } 
} 
3

Cóż, w rzeczywistości nie musisz niczego powtarzać. Można użyć flatten aby krotki, a następnie utworzyć instancję modelu z tej krotki:

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

Jednakże, jeśli trzeba zrobić kilka dalszych przekształceń, trzeba będzie zmodyfikować krotki jakoś:

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

Dzięki za sugestię. Wymyśliłem inne rozwiązanie (patrz poniżej), ponieważ nadal wymaga ono powtórzenia się co najmniej dwa razy, aby wyświetlić listę pól. Za pomocą poniższego rozwiązania nie muszę w ogóle wypisywać pól. W każdym razie pracuję z istniejącą bazą danych. – bwbecker

+0

Ups, "poniżej" i "powyżej" zmieniło pozycje względne, gdy zaakceptowałem własną odpowiedź. – bwbecker

Powiązane problemy