msawady’s engineering-note

なにも分からないエンジニアです。

【Scala】【mongoDB】mongoDB のレコードを case クラスにマッピングする

株のポジション管理を出来るようにしたいと思っています

  • 大分放置してしまいましたが、ちょっとずつ再開します。
  • まずは銘柄一覧をブラウザから見えるようにします。
  • http://kabusapo.com/ranking/ からダウンロードしたcsvファイルを case クラスにパースしてmongoDBにインサート→ mongoDBからフェッチして画面表示しました
  • クライアントなどは前回のTODO管理で作ったものをほとんど再利用しています。

msawady.hatenablog.com

コード

Stock.scala

package services.domain

import org.mongodb.scala.bson.ObjectId
import org.mongodb.scala.bson.codecs.Macros._
import org.mongodb.scala.bson.codecs.DEFAULT_CODEC_REGISTRY
import org.bson.codecs.configuration.CodecRegistries.{fromRegistries, fromProviders}


case class Stock(_id: ObjectId, code: String, name: String, market: String, industryType: String, unit: Int, isNK225: Boolean)

object Stock {
  def apply(code: String, name: String, market: String, industryType: String, unit: Int, isNK225: Boolean): Stock 
  = new Stock(new ObjectId, code, name, market, industryType, unit, isNK225)
  
  implicit val codecRegistry = fromRegistries(fromProviders(classOf[Stock]), DEFAULT_CODEC_REGISTRY)
}

Entityクラスはこんな感じで書きました。 * mongoDBのユニークキーであるObjectIdを生成してからインスタンス化するようにしています。 * codecRegistry は クラスとJsonのマッパーのようなものだと解釈しています。

StockRepositry.scala

package services.repository

import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Singleton

import org.mongodb.scala.{MongoClient, MongoCollection}
import play.Environment
import services.domain.Stock
import services.repository.Helpers._

import scala.io.Source

@Singleton
class StockRepository {

  val mongoClient = MongoClient()
  val database = mongoClient.getDatabase("database").withCodecRegistry(Stock.codecRegistry)
  val initialized = new AtomicBoolean()


  def toStock(data: String): Stock = {
    val el = data.split(",")
    Stock(el(0), el(1), el(2), el(3), safeStringToInt(el(4)).getOrElse(0), if (el.length > 5) "1".equals(el(5)) else false)
  }

  def initializeIfNeeded(): Unit = {
    if (initialized.get()) {
      return
    }

    val collection: MongoCollection[Stock] = database.getCollection("stock")
    collection.drop().results()

    val s = Source.fromFile(Environment.simple().getFile("data/stocklist.csv"), "UTF-8")
    val lines = s.getLines().toList
    val stocks: List[Stock] = lines.tail.map(l => toStock(l)).toList

    collection.insertMany(stocks).results()
    this.initialized.set(true)
  }

  def getStockList(): List[Stock] = {
    initializeIfNeeded()

    val collection: MongoCollection[Stock] = database.getCollection("stock")
    return collection.find().results().toList
  }

  def safeStringToInt(str: String): Option[Int] = {
    import scala.util.control.Exception._
    catching(classOf[NumberFormatException]) opt str.toInt
  }

}
  • AtomicBoolean を初期化済みフラグとして、初期化されていなかったらファイルの読み込み→インサートとしています。

    • ホントはファイルをダウンロードしたいんだけど上手く行かなかった...
  • tailを使うことで1列目を読み飛ばす(= ヘッダーを読み飛ばす)ことが出来ます。

    val stocks: List[Stock] = lines.tail.map(l => toStock(l)).toList
  • 文字列からオブジェクトにパースする際に、単元株数の列に"単元制度なし"というレコードが有ったので(#^ω^)ピキピキ
  def safeStringToInt(str: String): Option[Int] = {
    import scala.util.control.Exception._
    catching(classOf[NumberFormatException]) opt str.toInt
  }

といったメソッドで Option を返すようにしています。

結果

こんな感じになりました。 f:id:msawady:20171029143445p:plain

次にやること

  • まずは kendo-ui で上手くフィルタリング出来るようにしたい
  • ポジションを登録できるようにする
  • 時価をうまい具合に取れるようにする
  • お気に入り銘柄(ウォッチリスト)を作れるようにする

くらいですかね。ちょっとずつ頑張ります。