2012-10-23 14 views
12

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

すべてのものが定義される前に4回(!)回繰り返されます。パーサーは、ケースクラスから半自動的に推論できるように思われるようです。ここに含まれる作業を減らすために提案するツールやその他のテクニックはありますか?

ありがとうございます。

+0

私はanormを使用してからまったく同じ問題を持っています。私はその答えがまったく嫌になることではないと思う。私は、Slick(以前はScalaQuery)が、定型文を減らすためにマクロを使用しているという前進だと考えています。残念ながら、マクロにはScala 2.10が必要です。参照:http://stackoverflow.com/questions/11379608/play-framework-slick-scalaquery-tutorial –

答えて

3

私が最終的に開発したソリューションは次のとおりです。私は現在、これを私のPlayプロジェクトのクラスとして持っています。それはスタンドアローンのツールに変えることができますか?これを使用するには、tableNameのvalをテーブルの名前に変更します。次に、クラスの下部にあるmainを使って実行します。これは、ケースクラスとパーサーコンビネータのスケルトンを出力します。ほとんどの場合、これらのスケルトンはほとんど微調整を必要としません。

バイロン

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

まあ、実際に何も繰り返す必要はありません。あなたはタプルを作るために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=....) } 
+0

提案をありがとう。私は別の解決策を考え出しました(下記参照)。これはフィールドの一覧を表示するために少なくとも2回自分自身を繰り返す必要があるためです。以下の解決策では、フィールドをまったく書き出す必要はありません。私は既存のデータベースで作業しています。 – bwbecker

+0

自分の答えを受け入れると、「下」と「上」の相対的な位置が変わってしまいました。 – bwbecker

関連する問題