プログラマブル深海魚

目立たないけど華やかに

WindowsでCircleCIのローカル環境を動かす

CIツールの学習としてCircleCIを動かしてみたのですが、Windows上でCircleCIのローカル環境を動かす際になかなかうまくいかず紆余曲折があったため記録を残します。

目次

用意したCircleCIの設定ファイル

今回はCircleCIを動かしてみるのが目的だったため、Docker上でPythonを使って"Hello, world!"を表示するだけのビルドを用意しました。(ビルドしてないけど)

version: 2.1
jobs:
  build:
    docker:
      - image: cimg/python:3.10.5
    steps:
      - run:
          name: The First Step
          command: |
            python -c "print('Hello World!')"

Windows用のローカルCLI環境を入れてみる

↓でWindows用のローカルCLI環境の案内があるため、とりあえず入れてみました。
https://circleci.com/docs/ja/local-cli

動かしてみると早速↓のエラーが発生。

PS D:\Users\ancov\OneDrive\CircleCiSample> circleci config validate
Config file at .circleci/config.yml is valid.
PS D:\Users\ancov\OneDrive\CircleCiSample> circleci local execute
Error: Error creating temporary config file: open /tmp\3197476852_circleci_config.yml: The system cannot find the path specified.

どうやらドライブ直下にtmpフォルダが無いとエラーになるようなので、tmpフォルダを手動で作成して再度動かしてみると、またもやエラー。

PS D:\Users\ancov\OneDrive\CircleCiSample> circleci local execute
Fetching latest build environment...
Docker image digest: sha256:72624b09a668f0684c0151a61231d8458defe7d2dd46430f6db33db5f63c0f88
Error: failed to execute docker: not supported by windows

CircleCIのIssueなんかを読むと、Windowsではビルドの実行に対応していないみたい?
https://github.com/CircleCI-Public/circleci-cli/issues/152
仕方がないので、WSLを使ってCircleCIのローカルCLIを動かしてみることにしました。

WSL上でローカルCLI環境を動かしてみる

Linuxの場合、公式ではSnapを利用してインストールする方法が案内されていますが、WSLの場合はSnapを使うのに小細工が必要でした。
↓を参考に、WSLにローカルCLI環境をインストールしました。
https://qiita.com/matarillo/items/f036a9561a4839275e5f

ancov@DESKTOP-17P3D24:~$ sudo apt-get update
ancov@DESKTOP-17P3D24:~$ sudo apt-get install -yqq daemonize dbus-user-session fontconfig
ancov@DESKTOP-17P3D24:~$ sudo daemonize /usr/bin/unshare --fork --pid --mount-proc /lib/systemd/systemd --system-unit=basic.target
ancov@DESKTOP-17P3D24:~$ exec sudo nsenter -t $(pidof systemd) -a su - $LOGNAME
ancov@DESKTOP-17P3D24:~$ sudo apt install snapd
ancov@DESKTOP-17P3D24:~$ sudo snap refresh
ancov@DESKTOP-17P3D24:~$ sudo snap install docker circleci
ancov@DESKTOP-17P3D24:~$ sudo snap connect circleci:docker docker

ただ、これで実行してもエラーが発生しました。

ancov@DESKTOP-17P3D24:/mnt/d/Users/ancov/OneDrive/CircleCiSample$ circleci config validate
Config file at .circleci/config.yml is valid.
ancov@DESKTOP-17P3D24:/mnt/d/Users/ancov/OneDrive/CircleCiSample$ sudo circleci local execute
Fetching latest build environment...
Docker image digest: sha256:62f727286472ed21ad35c59d71ae72b373f566557b69951e2d3471b85c2ba587
Error: failed to start event processor: failed to compute task config: failed to read config file: read /tmp/local_build_config.yml: is a directory

↓を見る限りだと、マウントドライブで利用はできないみたい?
https://github.com/CircleCI-Public/circleci-cli/issues/212

WSLの中に設定ファイルを作成してもいいのですが、実はDocker Desktop for WindowsのDockerをWSL上で動かす機能があったので、それを利用してみます。

WSLからWindowsのDockerを使って動かす

SnapでインストールするとLinux上のDockerが使われてしまいそうなので、CircleCIのローカルCLI環境を手動でインストールしました。
(あまり真面目に調べてないので、Snapでインストールしてもできるかも)

ancov@DESKTOP-17P3D24:~$ sudo sh -c "curl -fLSs https://circle.ci/cli | bash"
[sudo] password for ancov:
Starting installation.
Installing CircleCI CLI v0.1.20500
Installing to /usr/local/bin
/usr/local/bin/circleci

WindowsのDockerは、デフォルトのWSLディストリビューションに対してのみ使用できるらしいので、デフォルトを変更します。
Docker Desktopの設定で"Enable WSL 2 Windows Features"にチェックが入ってなければ、これにチェックを入れる必要もあるみたいです。

PS D:\Users\ancov> wsl -l -v
  NAME                   STATE           VERSION
* docker-desktop-data    Running         2
  Ubuntu                 Running         2
  docker-desktop         Running         2
PS D:\Users\ancov> wsl --set-default Ubuntu

これで無事に動きました。

ancov@DESKTOP-17P3D24:/mnt/d/Users/ancov/OneDrive/CircleCiSample$ circleci config validate
Config file at .circleci/config.yml is valid.
ancov@DESKTOP-17P3D24:/mnt/d/Users/ancov/OneDrive/CircleCiSample$ sudo circleci local execute
Fetching latest build environment...
Docker image digest: sha256:9d8489ec41d4835fbc1ae1a4b42d23bb9dd68838c803f7f3779d2ce43b7e050b
====>> Spin up environment
......
The redacted variables listed above will be masked in run step output.====>> The First Step
  #!/bin/bash -eo pipefail
python -c "print('Hello World!')"
Hello World!
Success!

参考文献

CircleCI のローカル CLI のインストール - CircleCI
https://circleci.com/docs/ja/local-cli

設定ファイルの概要 - CircleCI
https://circleci.com/docs/ja/config-intro

いまさらだけどCircleCIに入門したので分かりやすくまとめてみた - Qiita
https://qiita.com/gold-kou/items/4c7e62434af455e977c2

Error: Error creating temporary config file · Issue #411 · CircleCI-Public/circleci-cli · GitHub
https://github.com/CircleCI-Public/circleci-cli/issues/411

Unable to run circleci local execute from windows machine · Issue #152 · CircleCI-Public/circleci-cli · GitHub
https://github.com/CircleCI-Public/circleci-cli/issues/152

WSL2(Ubuntu 20.04)上でSnapdを動かす
https://zenn.dev/dozo/articles/63c3e066f3c6ed

WSL2でSystemdを使うハック - Qiita
https://qiita.com/matarillo/items/f036a9561a4839275e5f

local execute - fails to read config file · Issue #212 · CircleCI-Public/circleci-cli · GitHub
https://github.com/CircleCI-Public/circleci-cli/issues/212

Windows10+WSL2(Ubuntu)+DockerでCircleCIのローカル実行ができる環境を作る - Qiita https://qiita.com/ysd_marrrr/items/a9370c4c88b5da094194

WSL:WindowsLinuxでファイルを交換する
https://zenn.dev/ohno/articles/13af6f4e1588a5

Windows Subsystem for Linux に関するドキュメント | Microsoft Docs
https://docs.microsoft.com/ja-jp/windows/wsl/

Scala 3 への移行方法を調べる

Scala 3が正式リリースされてからしばらく経っているので、以前作成したPlay FrameworkのサンプルプログラムをScala 3に移行できるか調べました。

目次

Scala 3 への移行ツール

プロジェクトが依存しているプラグインScala 3に対応しているかどうかを確認できるプラグインがあるため、それを利用します。
https://scalacenter.github.io/scala-3-migration-guide/docs/tooling/scala-3-migrate-plugin.html

plugin.sbtに以下を追加すると、migrate-libsコマンドでScala 3 Migrate Pluginが利用できます。

addSbtPlugin("ch.epfl.scala" % "sbt-scala3-migrate" % "0.4.2")
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3")

プラグインを実行すると、大まかに分けて「Scala 3では使えない(Scala 3対応のバージョンがない)」「そのままScala 3で使える」「Scala 3対応版のバージョンに上げる必要がある」の3種類のどれかが出力されます。

実際に使ってみる

実際にサンプルプロジェクトに対して使用してみます。

サンプルプロジェクトの元のplugin.sbtは以下。

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8")
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.13.1")
addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.4.16")
addSbtPlugin("com.github.tototoshi" % "sbt-slick-codegen" % "1.4.0")

libraryDependencies += "org.postgresql" % "postgresql" % "42.2.23"

build.sbtは以下。

name := """PlayScalaSample"""
organization := "com.example"

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala, CodegenPlugin)

scalaVersion := "2.13.6"

libraryDependencies += guice
libraryDependencies += "com.google.inject" % "guice" % "5.1.0"
libraryDependencies += "com.google.inject.extensions" % "guice-assistedinject" % "5.1.0"
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "5.1.0" % Test
libraryDependencies += "org.typelevel" %% "discipline-scalatest" % "2.1.5"
libraryDependencies += "com.typesafe.play" %% "play-slick" % "5.0.0"
libraryDependencies += "com.typesafe.slick" %% "slick" % "3.3.3"
libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % "3.3.3"
libraryDependencies += "com.typesafe.slick" %% "slick-codegen" % "3.3.3"
libraryDependencies += "org.postgresql" % "postgresql" % "42.3.1"
libraryDependencies += "org.typelevel" %% "cats-core" % "2.7.0"
libraryDependencies += "org.typelevel" %% "cats-laws" % "2.7.0"

routesGenerator := InjectedRoutesGenerator

wartremoverErrors ++= Seq()
wartremoverWarnings ++= Warts.unsafe ++ Warts.all
wartremoverExcluded ++= (Compile / routes).value
wartremoverExcluded += (Compile / baseDirectory).value / "target"

Compile / sourceGenerators += slickCodegen
slickCodegenDatabaseUrl := "jdbc:postgresql://localhost:5432/mydb"
slickCodegenDatabaseUser := "postgres"
slickCodegenDatabasePassword := "postgres"
slickCodegenDriver := slick.jdbc.PostgresProfile
slickCodegenJdbcDriver := "org.postgresql.Driver"
slickCodegenOutputPackage := "entities"
slickCodegenExcludedTables := Seq("schema_version")

plugin.sbtにScala 3 Migrate Pluginを追加して、migrate-libs <プロジェクトの変数>を実行すると結果が出力されます。(今回なら'migrate-libs root')
ただ、今回はこれだけだとsbtの起動時に以下のエラーが出ました。

[error] stack trace is suppressed; run 'last update' for the full output
[error] stack trace is suppressed; run 'last ssExtractDependencies' for the full output
[error] (update) sbt.librarymanagement.ResolveException: Error downloading org.scalameta:semanticdb-scalac_2.13.6:4.4.10

SemanticDBというライブラリのバージョンがsbtに依存しているらしく、使用しているScalaのバージョンのアーティファクトが存在しないバージョンを参照しようとしているみたいです。 SemanticDBのバージョンをbuild.sbtで指定できるので、以下を追加します。

semanticdbVersion := "4.4.33"

プラグインの出力結果は以下のとおりでした。

[info] com.typesafe.slick:slick:3.3.3                              -> X : Contains Macros and is not yet published for 3.0.0
[info] org.wartremover:wartremover:2.4.16:plugin->default(compile) -> X : Scala 2 compiler plugins are not supported in scala 3.0.0. You need to find an alternative
[info] org.postgresql:postgresql:42.3.1                            -> Valid
[info] com.google.inject.extensions:guice-assistedinject:5.1.0     -> Valid
[info] com.google.inject:guice:5.1.0                               -> Valid
[info] com.typesafe.play:twirl-api:1.5.1                           -> "com.typesafe.play" % "twirl-api_2.13" % "1.5.1"
[info] com.typesafe.play:play-docs:2.8.8:docs                      -> "com.typesafe.play" % "play-docs_2.13" % "2.8.8" % "docs"
[info] com.typesafe.play:play-logback:2.8.8                        -> "com.typesafe.play" % "play-logback_2.13" % "2.8.8"
[info] org.typelevel:cats-laws:2.7.0                               -> "org.typelevel" % "cats-laws_2.13" % "2.7.0"
[info] com.typesafe.play:play-slick:5.0.0                          -> "com.typesafe.play" % "play-slick_2.13" % "5.0.0"
[info] com.typesafe.play:play-guice:2.8.8                          -> "com.typesafe.play" % "play-guice_2.13" % "2.8.8"
[info] com.typesafe.play:play-test:2.8.8:test                      -> "com.typesafe.play" % "play-test_2.13" % "2.8.8" % "test"
[info] org.scalatestplus.play:scalatestplus-play:5.1.0:test        -> "org.scalatestplus.play" % "scalatestplus-play_2.13" % "5.1.0" % "test"
[info] org.typelevel:discipline-scalatest:2.1.5                    -> "org.typelevel" % "discipline-scalatest_2.13" % "2.1.5"
[info] com.typesafe.slick:slick-hikaricp:3.3.3                     -> "com.typesafe.slick" % "slick-hikaricp_2.13" % "3.3.3"
[info] com.typesafe.slick:slick-codegen:3.3.3                      -> "com.typesafe.slick" % "slick-codegen_2.13" % "3.3.3"
[info] org.typelevel:cats-core:2.7.0                               -> "org.typelevel" % "cats-core_2.13" % "2.7.0"
[info] com.typesafe.play:filters-helpers:2.8.8                     -> "com.typesafe.play" % "filters-helpers_2.13" % "2.8.8"
[info] com.typesafe.play:play-server:2.8.8                         -> "com.typesafe.play" % "play-server_2.13" % "2.8.8"
[info] com.typesafe.play:play-akka-http-server:2.8.8               -> "com.typesafe.play" % "play-akka-http-server_2.13" % "2.8.8"

Slick(DBライブラリ)はScala 2のマクロを使用しているため、Scala 3では使用できないようです。(Scala 2とScala 3ではマクロの互換性がないらしい)
また、WartRemoverはScala 2用のプラグインなので使用できません。
その他は、そのまま使えるか、もしくは2.13用のアーティファクトScala 3でも使用できるようです。

おわり

SlickがScala 3で使えないとのことなので、しばらくはまだ移行できなさそうです。
SlickのScala 3サポート自体は、着々と進められているようでした。
https://github.com/slick/slick/pull/2187

参考文献

New in Scala 3 | Scala Documentation
https://docs.scala-lang.org/ja/scala3/new-in-scala3.html

Scala 3への道
https://www.infoq.com/jp/news/2021/05/scala3/

Scala 3 Migrate Plugin · Scala 3 Migration guide
https://scalacenter.github.io/scala-3-migration-guide/docs/tooling/scala-3-migrate-plugin.html

業務アプリケーションのScala 3アップデートを試してみた - FLINTERS Engineer's Blog
https://labs.septeni.co.jp/entry/2021/07/05/110000

Error downloading org.scalameta:semanticdb-scalac_2.13.8:4.4.28 · Issue #1560 · scalacenter/scalafix · GitHub
https://github.com/scalacenter/scalafix/issues/1560

Play Framework上でTwirlを使う

Scalaで何かしらのテンプレートエンジンを使ってみようと思い、Play Frameworkに組み込まれているTwirlを使ってみました。

目次

前提

Play Framework組み込みということで、Playのプロジェクトであれば依存関係の追加などはせずに使用できます。

  • Play Frameworkのsbtプロジェクトを作成済みである

テンプレートファイルの配置場所

テンプレートファイルは sourceDirectories / TwirlKeys.compileTemplates で指定したディレクトリ配下に配置する必要があります。sbtのinspectコマンドでこの設定を調べたところPlayプロジェクトでは"/app"なようです。(PlayLayoutPluginによって指定されている)
https://github.com/playframework/playframework/blob/53e55f017f508f0dcdf2a70327bd73153fe87e5e/dev-mode/sbt-plugin/src/main/scala/play/sbt/PlayLayoutPlugin.scala

命名規則

テンプレートは簡単な命名規則に従って、ただの Scala の関数としてコンパイルされます。views/Application/index.scala.html というテンプレートファイルを作成すると、コンパイルにより views.html.Application.index という apply() メソッドを持つクラスが生成されます。

Playのドキュメントを見ると上記のように簡単に書かれているのですが、 https://github.com/playframework/twirl/blob/main/compiler/src/main/scala/play/twirl/compiler/TwirlCompiler.scala あたりのコードを読むとそれほど単純でもないようです。

フォルダ階層にtopDirectory="views"が含まれているか否かによって生成されるパッケージが異なるようで、以下のようになります。

  • "viewsが含まれる" → <viewsより上のフォルダ階層>.views.<テンプレート拡張子>.<viewsより下のフォルダ階層>.<テンプレートファイル名>
  • "viewsが含まれない" → <フォルダ階層>.<テンプレート拡張子>.<テンプレートファイル名>

  • views/Application/index.scala.html → views.html.Application.index
  • shop/views/Application/index.scala.html → shop.views.html.Application.index
  • mypage/top.scala.html → mypage.html.top
  • user/mypage/top.scala.html → user.mypage.html.top

テンプレート作成

次のようなモデルを用意しておきます。

package models

case class SampleModel(name: String, age: Integer, country: String)

views/Test.scala.htmlにテンプレートを作成します。

@import models._
@(user: SampleModel)

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8" />
    <title>User Profile | example.com</title>
    <style>
    html {
      background-color: #fdfdfd;
      font-size: 100%;
    }
    body {
      margin: 0 auto;
      width: 60vw;
    }
    h1 {
      font-size: 2.5rem;
      margin: 1.2rem 0;
    }
    p {
      margin: 0.9rem 0;
      font-size: 1rem;
    }
    .data-box {
      background-color: #efefef;
      padding: 0 1.5rem;
      font-size: 90%;
    }
    .data-line {
      display: flex;
      padding: 1rem 0;
      border-bottom: 1px solid #cccccc;
    }
    .data-line:last-child {
      border-bottom: none;
    }
    .data-item-1-3 {
      flex: 0 1 33.3%;
    }
    .data-item-2-3 {
      flex: 0 1 66.6%;
    }
    .item-key {
      font-weight: bold;
    }
    .item-value {
    }
  </style>
</head>
<body>
<main>
    <h1>User Profile</h1>
    <p>Profile:</p>
    <div class="data-box">
        <div class="data-line">
            <div class="data-item-1-3 item-key">Name</div>
            <div class="data-item-2-3 item-value">@user.name</div>
        </div>
        <div class="data-line">
            <div class="data-item-1-3 item-key">Age</div>
            <div class="data-item-2-3 item-value">@user.age</div>
        </div>
        <div class="data-line">
            <div class="data-item-1-3 item-key">Country</div>
            <div class="data-item-2-3 item-value">@user.country</div>
        </div>
    </div>
</main>
</body>
</html>

コントローラの作成

今回はただモデルを生成してビューに渡すだけの簡易コントローラを作成します。

package controllers

import models.SampleModel
import play.api._
import play.api.mvc._

import javax.inject.Inject

class IndexController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {

  def index(): Action[AnyContent] = Action { implicit request =>
    Ok(views.html.Test(SampleModel("Mano Sakuragi", 16, "Japan")))
  }
}

テンプレート
テンプレート

参考文献

Scala Templates - 2.3.x
https://www.playframework.com/documentation/ja/2.3.x/ScalaTemplates

Java Templates - 2.8.x
https://www.playframework.com/documentation/2.8.x/JavaTemplates

sbt Reference Manual — Inspect the build
https://www.scala-sbt.org/1.x/docs/Howto-Inspect-the-Build.html

Play Framework上でSlickを使う

Play Frameworkをいろいろと弄っていたのですが、その中でScala用のDBライブラリであるSlickを利用してみました。 実際にSlickでデータ取得ができるようになるまでの流れを、備忘録的にここに記します。

目次

前提

依存関係の追加

build.sbtに依存関係を追加していきます。 Play FrameworkでSlickを使うためには、Play Slickライブラリを追加します。また、Play Slickを追加するとSlickのライブラリも依存関係に追加されるのですが、バージョンが古いようなので今回は明示的にSlickライブラリ(およびHikariCP用の実装)を追加します。
後述するコードジェネレータのライブラリCodegenPostgreSQLのドライバも追加します。

libraryDependencies += "com.typesafe.play" %% "play-slick" % "5.0.0"
libraryDependencies += "com.typesafe.slick" %% "slick" % "3.3.3"
libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % "3.3.3"
libraryDependencies += "com.typesafe.slick" %% "slick-codegen" % "3.3.3"
libraryDependencies += "org.postgresql" % "postgresql" % "42.2.23"

DBの用意

適当に以下のようなDBを作成します。

CREATE TABLE user_info
(
    user_id serial NOT NULL,
    first_name text,
    last_name text,
    age integer CHECK (age >= 0),
    CONSTRAINT user_id_pkey PRIMARY KEY (user_id)
)

INSERT INTO user_info (first_name, last_name, age)
values ('Mano', 'Sakuragi', '16'), ('Hiori', 'Kazano', '15'), ('Meguru', 'Hachimiya', '16');

DB接続設定

デフォルトのDB接続設定をconf/application.confに記述します。
HikariCPによるコネクションプールを利用する設定と利用しない設定をできるようですが、今回はHikariCPを利用するようにします。numThreads, maxConnectionsの設定は適当なため、削っても問題なし。
他のDBであってもprofile, driverを該当のDBのものに差し替えれば動作するはずですが、未確認。

slick.dbs = {
  default = {
    profile = "slick.jdbc.PostgresProfile$"
    db = {
      connectionPool = "HikariCP"
      dataSourceClass = "slick.jdbc.DatabaseUrlDataSource"
      properties = {
        driver = "org.postgresql.Driver"
        url = "jdbc:postgresql://<ホスト名>:<ポート番号>/<DB名>"
        user = "<ユーザ名>"
        password = "<パスワード>"
      }
      numThreads = 20
      maxConnections = 20
    }
  }
}

コードジェネレータを使う

コードジェネレータを使うと、データベーススキーマを操作するためのコードが生成されます。
本来は別なScalaプログラムを書いて実行するか、あるいはsbtタスクを自分で定義するかでコードジェネレータを使うようですが、今回は横着して有志の方が作られているsbtプラグインを利用します。
GitHub - tototoshi/sbt-slick-codegen: slick-codegen compile hook for sbt

project/plugin.sbtに以下を追記します。

addSbtPlugin("com.github.tototoshi" % "sbt-slick-codegen" % "1.4.0")
libraryDependencies += "org.postgresql" % "postgresql" % "42.2.23"

build.sbtには設定を追加します。
自動生成クラスはslickCodegenOutputPackageのパッケージ下に出力されます。

Compile / sourceGenerators += slickCodegen
slickCodegenDatabaseUrl := "jdbc:postgresql://localhost:5432/mydb"
slickCodegenDatabaseUser := "postgres"
slickCodegenDatabasePassword := "postgres"
slickCodegenDriver := slick.jdbc.PostgresProfile
slickCodegenJdbcDriver := "org.postgresql.Driver"
slickCodegenOutputPackage := "entities"
slickCodegenExcludedTables := Seq("schema_version")

sbtシェルからslickCodegen(またはcompile)を実行すると、target/scala-2.13/src_managed/mainにTables.scalaが出力されるかと思います。

実際に使ってみる

ここまで来ればSlickを使えるようになっているはずです。
例として、ユーザの取得と追加を作成してみました。

package controllers

import entities.Tables.{UserInfo, UserInfoRow}
import play.api._
import play.api.mvc._
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import play.api.libs.json._
import play.api.libs.json.Json
import slick.jdbc.PostgresProfile
import slick.jdbc.PostgresProfile.api._

import javax.inject.Inject
import scala.concurrent.ExecutionContext

class UserController @Inject()(protected val dbConfigProvider: DatabaseConfigProvider, cc: ControllerComponents)(implicit ec: ExecutionContext)
    extends AbstractController(cc) with HasDatabaseConfigProvider[PostgresProfile] {
  case class UserInfoModel(userId: Option[Int], firstName: Option[String], lastName: Option[String], age: Option[Int])
  private implicit val recordReads: Reads[UserInfoModel] = Json.reads[UserInfoModel]
  private implicit val recordWrites: Writes[UserInfoModel] = Json.writes[UserInfoModel]

  def getUserAll: Action[AnyContent] = Action.async { implicit request =>
    val action = UserInfo.result
    db.run(action)
      .map(users => {
        Ok(Json.obj(
          "users" -> users.map(u => UserInfoModel(Some(u.userId), u.firstName, u.lastName, u.age))
        ))
      })
  }

  def getUser(id: Int): Action[AnyContent] = Action.async { implicit request =>
    val action = UserInfo.filter(_.userId === id).result.head
    db.run(action)
      .map(user => {
        Ok(Json.obj(
          "user" -> UserInfoModel(Some(user.userId), user.firstName, user.lastName, user.age)
        ))
      })
  }

  def addUser: Action[JsValue] = Action.async(parse.json) { implicit request =>
    val body = request.body.validate[UserInfoModel]
    body.map(data => {
      val action = (UserInfo returning UserInfo.map(_.userId) into ((u, id) => u.copy(userId = id))) +=
        UserInfoRow(0, data.firstName, data.lastName, data.age)
      db.run(action)
        .map(newUser => {
          Ok(Json.obj(
            "user" -> UserInfoModel(Some(newUser.userId), newUser.firstName, newUser.lastName, newUser.age)
          ))
        })
    }).get
  }
}

conf/routesには以下を追記します。

GET     /user/all                   controllers.UserController.getUserAll()
GET     /user/:id                   controllers.UserController.getUser(id: Int)
POST    /user                       controllers.UserController.addUser()

POST /user (リクエスト)
POST /user (リクエスト)
POST /user (レスポンス)
POST /user (レスポンス)
GET /user/all
GET /user/all
GET /user/1
GET /user/1

参考文献

Slick
https://scala-slick.org/doc/3.3.3/

Play Slick - 2.8.x
https://www.playframework.com/documentation/2.8.x/PlaySlick

ScalaDB用ライブラリ、Slickを使い倒すハンズオン - Qiita
https://qiita.com/sonken625/items/bcfe5f78323a67205933

Scala Play Framework と Slick で Connection Pool を利用する - 猫でもわかるWebプログラミングと副業
https://www.utakata.work/entry/20190406/1554545383

『本格的シリーズ 電車でGO! Windows版 プロフェッショナル仕様』を動かす

www.amazon.co.jp

『本格的シリーズ 電車でGO! Windows版 プロフェッショナル仕様』を久しぶりにプレイしようとしたのですけど、画質をハイグレードにするとチラツキが発生してしまっていたので、グラフィックAPIのラッパーを利用するようにしました。折角なので、メモを残しておきます。

手順

  1. 『dgVoodoo 2』を導入する
    (導入方法は他の記事に譲ることとして省略します)
  2. 以下の設定項目を変更する
    • [DirectX]→[Disable Alt-Enter to toggle screen state] のチェックを外す
      (チェックを入れたままだとゲーム終了時にエラーが発生する)
    • [GeneralExt]→[Fullscreen mode attributes]→[Fake] のチェックを入れる
      (チェックを外していると別のウィンドウにフォーカスを移したときにゲームがバグる)

以上で正常に動作するようになります。その他の設定はお好みで。