2012-10-23 6 views
12

Ich beginne gerade mit Anorm und Parser-Kombinatoren. Es scheint so, als gäbe es eine Menge Code. Zum Beispiel habe ichGibt es ein Tool zum automatischen Generieren von Anorm-Parser-Kombinatoren?

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 
      ) 
    } 
} 

Jedes Datenbankfeld wird vier (!) Mal wiederholt, bevor das Ganze definiert ist. Es scheint, als ob der Parser halbautomatisch aus der Fallklasse abgeleitet werden könnte. Irgendwelche Tools oder andere Techniken, um die Arbeit hier zu reduzieren?

Danke für alle Hinweise.

+0

Ich habe genau das gleiche Problem von anorm verwenden. Ich vermute, dass die Antwort darin besteht, Anorm überhaupt nicht zu benutzen. Ich nehme an, dass Slick (früher ScalaQuery) der Weg nach vorn ist, indem man Makros verwendet, um den Standard zu reduzieren. Leider benötigen Makros Scala 2.10. Siehe auch: http://stackoverflow.com/questions/11379608/play-framework-slick-scalaquery-tutorial –

Antwort

3

Hier ist die Lösung, die ich schließlich entwickelt habe. Ich habe dies derzeit als eine Klasse in meinem Play-Projekt; es könnte (sollte!) zu einem eigenständigen Werkzeug gemacht werden. Um es zu verwenden, ändern Sie den Wert tableName in den Namen Ihrer Tabelle. Führen Sie es dann mit dem main am Ende der Klasse. Es wird ein Skelett der Fallklasse und des Parser-Kombinators gedruckt. Meistens erfordern diese Skelette nur sehr wenig Feinabstimmung.

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

Nun, Sie müssen eigentlich gar nichts wiederholen. Sie können flatten verwenden, um ein Tupel zu machen und dann aus diesem Tupel Modell Instanz erstellen:

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

Wenn Sie jedoch einige weitere Umwandlungen tun müssen, müssen Sie das Tupel ändern irgendwie:

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

Danke für den Vorschlag. Ich habe eine andere Lösung gefunden (siehe unten), weil ich immer noch mindestens zweimal wiederholen muss, um die Felder aufzulisten. Mit der folgenden Lösung muss ich die Felder überhaupt nicht schreiben. Ich arbeite übrigens mit einer bestehenden Datenbank. – bwbecker

+0

Hoppla, "unten" und "oben" änderten die relativen Positionen, nachdem ich meine eigene Antwort akzeptiert hatte. – bwbecker

Verwandte Themen