ぜったいってなに

Software Engineerのブログです。

今後やること

前回の続き。

今回も書き散らしで申し訳ないですが、
自分のエンジニアとしての市場価値を上げる方法を考えました。


できなければならないこと


IT周りの能力を磨く

  • プログラミング能力

    • スピード
      • アウトプットが速い
      • 可読性が高い
      • パフォーマンスを考慮している
      • 変更しやすい
  • 一部の分野に関する専門的な(あまり人が知らないような)知識、経験

    • まだ自分の中では決まっていない
  • 流行の技術に関する全般的な知識

    • どういう技術なのか
    • 何故流行っているのか、その経緯

この部分は同年代のエンジニアを見ると明らかに足りていないと感じる。

Soft Skillsを鍛える

  • 周りを巻き込みながらプロダクトを作る能力
  • 意識しながら業務を回すことで培うことができるはず

自分の市場を広げる

  • IT技術が必要なのは世界共通
  • 日本市場だけでなく、世界市場で買ってもらえるようにする

実際にやること


毎日プログラミングする

output

  • 良いコードを速く書く。

    • 読みやすいコード
    • 変更しやすいコード
    • パフォーマンスを考慮したコード
    • 言語の機能を最大限利用したコード
  • 技術力を試される場には極力参加する。
    コンテストやコンペだけでなく、Tech talkなどの場にも積極的に。

「どの技術を使うべきか」とかごちゃごちゃ考えていたけど、 そこで悩み続けているのは時間の無駄だし、結局どの選択が一番良いかなんてあとになってみないとわからない。 何らかの技術選択をしたとしても、個人で使う分には事業に悪影響を及ぼすわけでもなく、リスクが無いので とりあえず使ってみてこれは駄目だと思ったら変えて、というスタイルが一番いいと思った。

input

  • 出られる勉強会には極力参加する。
  • 自分の使っている言語やFrameworkのつくりを理解したり、コードを読んだりする。
  • 自分の使っている技術に今後必要な機能を考えて、
    ライブラリを作るプロジェクトに参加したり、自分で興したりする。

Soft Skillsを鍛える

  • 具体的に何をすべきか、というところがまだ自分の中にない。
  • 本を読む。

英語で読み書きできるようにする

  • レアジョブ
  • OSS contribute
  • その他。まだ考えられていない

こんなところでまた明日考える。

今の自分の能力と今後のキャリアについて

RubyKaigi2017に参加させていただきました。

いろんな発表を聞いて、自分のエンジニアとしての価値について考えることがあったので、今後のキャリアも含めて考えたことをつらつらと書いていきます。


現状の自分のエンジニアとしての価値

1. プログラミング能力

Ruby on Railsがそこそこ書ける

  • MVCにそれぞれの役割を理解した上でコードが書ける。
  • システム要件に即したビジネスロジックが書ける。
  • エラーハンドリングができる。
  • アプリケーションコードレベルでパフォーマンスを考えたクエリが書ける。
  • 可読性の高いコードが書ける。
    (社外でコードレビューを受けたことがないので、人によってはクソコードと思うかもしれない)

概念の切り分け方を考えられる

  • 機能によって、プログラミング言語に合わせた概念の切り分けが行える。
    • Railsで言うとClass(内部Classも含める), Module, Concernなど。
  • クラス命名をアンチパターンを踏まえながら適切に命名できる。
  • ディレクトリ構成も概念によって切り分けを意識的に行う。
  • とはいえベストプラクティスが浮かばないような場面も多々ある。

ソフトウェア開発を取り巻く概念をある程度理解している

2. Soft Skills

Soft Skillsを参照のこと。

粗筋から参照すると、

対人的な交渉・指導・意思疎通などをうまく行える能力や知恵

要件定義ができる

  • 依頼者からのビジネス要件をヒアリングできる。
  • 依頼者が本当にやりたいことは何かを考え、提案できる。
  • 全くの新機能なのか、既存機能に対する追加機能なのかを踏まえて
    工数見積を行える。
  • 依頼者に「なぜこれだけ時間がかかるのか」を伝え、交渉できる。

エンジニアとして世の中ででかい価値を出す方法

1. 何らかの技術のエキスパートになる

  • プログラミング言語フレームワークをよく知っている、もしくは作っている人になって、
    その言語やフレームワークを利用している会社のアドバイザになる。
  • 提供するサービスの性質上、利用するプログラミング言語が限定されており、
    かつその言語を書ける人が少ない場合は、それだけでプログラマとして食っていけると思う。
    例えばミドルウェアの会社や組み込みのソフトウェアの会社でプログラムを書くとか、
    恐ろしく速さが求められるサービスをフルスクラッチで作る、とかいった状況の場合
    使われる言語はC++とかRustとか?だと思うが、これらを書ける人はインタプリタ言語よか少なそうなので、
    書けるだけで価値があると思う。
    つまり学ぶのが難しくて、かつそのツールを使う理由があるような場では
    それが使える人は価値が出せるということ。

2. エンジニア目線で事業に貢献できる人になる

  • 流行りの枠組み、例えばWebサービスならWebサービスを構成する主要な要素の基本的な仕組みと、
    代表的なアプリケーションの特徴を知っており、技術選択ができるレベルならば、
    アーキテクトとして食っていけそう。
  • エンジニアとビジネスサイドの間に立つ人として、予算レベルの折衝からプロダクトの要件定義、 工数見積までできれば、事業を回せる人として価値が出そう。

3. スタートアップを起こす

  • 思いついたサービスを実現してユーザに使ってもらう。
  • 技術の限界に挑戦するようなスタートアップでない限り、ものを作るエンジニアというよりかは
    速く書いて、ユーザと話して、VCと資金の交渉をして、といった、Soft Skillsが強く求められそう。

今日はここまで。


今後も自分の歩めるキャリアの選択肢と、自分がどういうキャリアを歩みたいか、ということを整理して意識化していきたい。

Ruby on Rails 画面表示情報に関する悩み

Railsを書いていて、画面に渡す情報が変わったときに、やりたいことはシンプルなのに内部ロジックの変更は多くて、しかも自分のやり方が正しいのかどうかもわからないのでいつもすごくもやもやする。

抽象化できてなくてわかりづらいと思いますが、備忘録としてつらつらと書いていきます。



前提

下記のようなテーブルがあるとする。

users

column_name desc
id なし
name ユーザ名
password パスワード


user_name_timestamps

column_name desc
id なし
user_id usersテーブルのID
name ユーザ名
created_at レコード生成日時
updated_at レコード更新日時


usersuser_name_timestamps1:Nで結びついていて、 ユーザがユーザ名を変更するたびに、変更前のユーザ名をuser_name_timestampsレコードとして保存しておく。

/app/models/user.rb

class User < ApplicationRecord
  has_many :user_name_timestamps
end


/app/models/user_name_timestamp.rb

class UserNameTimestamp < ApplicationRecord
  belongs_to :user

  def stamp!(user)
    self.create!(user_id: user.id, name: user.name)
  end
end


UsersControllerは下記のようになっている。

/app/controllers/users_controller.rb

class UsersController < ApplicationController
  ...

  def show
    @user = User.find_by(params[:id])
  end
end


User#show画面に「ユーザ名変更履歴」というリンクがあって、リンクをクリックするとUsers::NameTimestamps#indexに飛ぶ。


app/controllers/users/name_timestamps_controller.rb

class Users::NameTimestampsController < ApplicationController
  def index
    @user = User.includes(:user_name_timestamps).find_by(params[:user_id])
    @name_timestamps = @user.user_name_timestamps
  end
end


viewでは@user.id@user.nameを表示してどのユーザかをわかるようにし、 @name_timestamps.each do |t|でタイムスタンプをリスト表示するようにしている。



悩みごと

下記のようなビジネス要件が来たとする。

『ユーザ詳細画面で、タイムスタンプのリンクをクリックするのがめんどくさい』というユーザからの意見が多い。 リンクをクリックしなくても、ユーザ詳細画面で見れるようにしてほしい。


現状の実装に改修を加えるとすると、素朴に考えたらこうだろうか。

/app/controllers/users_controller.rb

class UsersController < ApplicationController
  ...

  def show
    @user = User.includes(:user_name_timestamps).find_by(id: params[:id])
    @name_timestamps = user.user_name_timestamps
  end
end


resourceが2つあるのが気持ち悪いので、UserWithNameTimestampクラスを定義してまとめる。

/app/usecases/user_with_name_timestamp.rb

class UserWithNameTimestamp
  attr_reader :user, :name_timestamps

  def initialize(user_id)
    @user = User.includes(:user_name_timestamps).find_by(id: user_id)
    @name_timestamps = user.try!(:user_name_timestamps)
  end
end


これでUsers#showのresourceが1つにまとまった。

/app/controllers/users_controller.rb

class UsersController < ApplicationController
  ...

  def show
    @user_with_name_timestamps = UserWithNameTimestamp.new(params[:id])
  end
end

UsersControllerが意識するresourceは1つになったけど、これ、扱っているresourceがUsersじゃなくてUserWithNameTimestampだからUsersControllerじゃないな。命名を変更するか。


と思ったけど、よく見たら他のactionではUserがresourceとして使われている。

class UsersController < ApplicationController
  ...

  def create
    User.create!(params)
    redirect_to users_index_path
  end

  def show
    @user_with_name_timestamps = UserWithNameTimestamp.new(params[:id])
  end
end


…じゃあ別のControllerを作るしかない。 Users::WithNameTimeStampsControllerを定義する。


/app/controllers/users/with_name_timestamps_controller.rb

class Users::WithNameTimestampsController < ApplicationController
  def show
    @user_with_name_timestamps = UserWithNameTimestamp.new(params[:id])
  end
end


これでUsers#showは機能としては要らなくなるけど、user_idパラメータはなんとかして渡さなきゃいけないし、 Users#show actionに当たるものは存在しますよ、と明示しておきたいので、下記のようにする。


/app/controllers/users_controller.rb

class UsersController < ApplicationController
  ...

  def show
    redirect_to users_with_name_timestamps_show_path, {id: params[:id]}
  end
end


Users::NameTimestampsControllerは不要になる。画面としては消しておきたいので、actionをコメントアウトしておいて、後々ファイルごと削除する。


/app/controllers/users/name_timestamps_controller.rb

class Users::NameTimestampsController < ApplicationController
  # def index
  #   @user = User.includes(:user_name_timestamps).find_by(params[:user_id])
  #   @name_timestamps = @user.user_name_timestamps
  # end
end



…やることは終わったけど、これってやり方としてどうなんだろう。

「resrouce1つに対してcontrollerは1つ」を守るならば、改修後のUsers#showはそれを守っていると言えるのか?と言われたら微妙だと思う。UsersControllerはなくして、Userに対するCRUDは全部UserWithNameTimestampクラスのメソッドとして定義し直せば、Users::WithNameTimestampsControllerに1つのリソースとしてまとめられるけど、そんなことわざわざする…? とも思うし。



言いたいことの説明が下手くそすぎるんですけど、もしご意見、感想などありましたら是非いただきたいです。

Play Scala ViewからDBレコード追加

モデル追加に続いて、ViewからDBレコードのCLUDができるようにする。

前回に引き続き、こちらを参考に。 qiita.com



routesの設定

/conf/routes

  1 # Routes
  2 # This file defines all application routes (Higher priority routes first)
  3
  4 GET     /                           controllers.HomeController.index
  5 GET     /article                    controllers.ArticleController.index # added
  6 POST    /article                    controllers.ArticleController.create # added
  7
  8 # Map static resources from the /public folder to the /assets URL path
  9 GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)


今回はArticlesControllerではなくArticleControllerと命名した。

Ruby on RailsではController名は複数形で書くことを推奨されている。
「推奨されている」というのはどういうことかというと、Controllerが複数形で書かれていれば、resources{resource}s_{action}_pathのように、Railsがパスを自動生成してくれる。


が、この「Controller名は複数形で書くべき」という設計(思想?)は間違っているのでは?と思う。


Railsの開発者であるDHHは、Controllerのアクションは全部resourcesで間に合わせるべきで、それ以外のアクションが必要になったらController自体を分けることを推奨している。

postd.cc


でも、全部resourcesで書くべきならば、ControllerによってはORM(Railsの場合はActiveRecord)を継承しないクラスを扱うこともあるだろうし、Controller内でindexアクションを使わないならば、それ以外のresourcesのアクションに関してはresourceが複数であることを意識しないので、どちらかというと単数形で統一するのが正しいのではないかと思う。


Play Scalaでは、今のところ「Controllerは複数形を使ってね」と言われていないので、
単数形で書くことにした。


evolutionsの修正

テーブル名が複数形になっていないのが気持ち悪いので、全部複数形に直す。

/conf/evolutions/default/1.sql

  1 # --- !Ups
  2
  3 CREATE TABLE "users" ( # user -> users
  4     "username" TEXT NOT NULL,
  5     "password" TEXT NOT NULL,
  6     "name" TEXT NOT NULL
  7 );
  8
  9 CREATE TABLE "articles" ( # article -> articles
 10     "user_id" INT NOT NULL,
 11     "content" TEXT NOT NULL
 12 );
 13
 14 CREATE TABLE "comments" ( # comment -> comments
 15     "user_id" INT NOT NULL,
 16     "article_id" INT NOT NULL,
 17     "content" TEXT NOT NULL
 18 );
 19
 20 # --- !Downs
 21
 22 DROP TABLE "users"; # user -> users
 23 DROP TABLE "articles"; # article -> articles
 24 DROP TABLE "comments"; # comment -> comments

DAOの修正

  1. daoディレクトリが複数形になっていなかったので直す。
  2. evolutionsの修正にて、複数形に直したテーブルを参照するように修正する。

/app/daos/articleDAO.scala

  1 package daos // dao -> daos
  2
  3 import scala.concurrent.Future
  4
  5 import javax.inject.Inject
  6 import models.Article
  7 import play.api.db.slick.DatabaseConfigProvider
  8 import play.api.db.slick.HasDatabaseConfigProvider
  9 import play.api.libs.concurrent.Execution.Implicits.defaultContext
 10 import slick.driver.JdbcProfile
 11
 12 class ArticleDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
 13   import driver.api._
 14
 15   private val Articles = TableQuery[ArticlesTable]
 16
 17   def all(): Future[Seq[Article]] = db.run(Articles.result)
 18
 19   def insert(article: Article): Future[Unit] = db.run(Articles += article).map { _ => () }
 20
 21   private class ArticlesTable(tag: Tag) extends Table[Article](tag, "articles") { // article -> articles
 22
 23     def user_id = column[Int]("user_id")
 24     def content = column[String]("content")
 25
 26     def * = (user_id, content) <> (Article.tupled, Article.unapply _)
 27   }
 28 }

uerDAO, commentDAOも同様に修正(省略)。


Controllerの設定

例に倣って書く。

/app/ArticleController.scala

  1 package controllers
  2
  3 import javax.inject._
  4 import play.api._
  5 import play.api.mvc._
  6 import play.api.db._
  7
  8 import daos.ArticleDAO
  9 import models.Article
 10 import play.api.data.Form
 11 import play.api.data.Forms.mapping
 12 import play.api.data.Forms.number
 13 import play.api.data.Forms.text
 14 import play.api.libs.concurrent.Execution.Implicits.defaultContext
 15
 16 @Singleton
 17 class ArticleController @Inject() (articleDao: ArticleDAO) extends Controller {
 18   def index = Action.async {
 19     articleDao.all().map {
 20       articles => Ok(views.html.article.index(articles))
 21     }
 22   }
 23
 24   def create = Action.async { implicit request =>
 25     val article: Article = articleForm.bindFromRequest.get
 26     articleDao.insert(article).map(_ => Redirect(routes.ArticleController.index))
 27   }
 28
 29   val articleForm = Form(
 30     mapping(
 31       "user_id" -> number,
 32       "content" -> text()
 33     )(Article.apply)(Article.unapply)
 34   )
 35 }


ちなみに31行目、user_idはDB上ではINT、Modelとしてもintとして扱っているので、formでtext()にmappingするとエラーになる。

Playのドキュメントを漁ってみると、int型を扱うにはnumberが適切っぽいのでnumberを設定した。


f:id:zettaittenani:20170914164451p:plain

参照元: play.api.data.Forms



Viewの設定

これも例に倣う。

/app/views/article/index.html.scala

  1 @(articles: Seq[Article])
  2 @main("Article database") {
  3 <div>
  4   <div id="articles">
  5     <h2>Insert an article here:</h2>
  6
  7     <form action="/article" method="POST">
  8       <input name="user_id" type="text" placeholder="user_id"/>
  9       <input name="content" type="text" placeholder="content"/>
 10       <input type="submit"/>
 11     </form>
 12
 13     <h2>Previously created articles:</h2>
 14     <table>
 15       <tr><th>UserId</th><th>Content</th></tr>
 16       @for(a <- articles){
 17       <tr><td>@a.user_id</td><td>@a.content</td></tr>
 18       }
 19     </table>
 20   </div>
 21 </div>
 22 }

実行してみる

sbt runしてhttp://localhost:9000/articleにアクセスしたら問題なく動作しました。

f:id:zettaittenani:20170914162319p:plain


レコードのcreateもちゃんとできた。

f:id:zettaittenani:20170914163123p:plain

@playdb, PostgreSQL

playdb=> select * from articles;
 user_id |     content
---------+------------------
       1 | hogehoge
       2 | 日本語
       3 | Play Scala
       4 | ぜったいってなに
(4 rows)


次はModelにビジネスロジックの実装かな。
Controllerに関しては他のresourcesアクションを追加するくらいしかやることないだろうし、
Viewに関してはControllerから値が渡せればサーバーサイドエンジニアとしてはやること終わりだと思うし。

Play Scala モデル追加

DB connectionができたので、次はモデルを記述して、
モデルからテーブルのレコードをCRUDできるようにする。



こちらの記事をまるっと参考にさせていただきました。

qiita.com



まずは昨日時点のevolutionファイルを修正する。
PostgreSQLでは大文字小文字が区別されないようなので、
テーブル名、カラム名を小文字で統一。


/conf/evolutions/default/1.sql

  1 # --- !Ups
  2
  3 CREATE TABLE "user" (
  4     "username" TEXT NOT NULL,
  5     "password" TEXT NOT NULL,
  6     "name" TEXT NOT NULL
  7 );
  8
  9 CREATE TABLE "article" (
 10     "user_id" INT NOT NULL,
 11     "content" TEXT NOT NULL
 12 );
 13
 14 CREATE TABLE "comment" (
 15     "user_id" INT NOT NULL,
 16     "article_id" INT NOT NULL,
 17     "content" TEXT NOT NULL
 18 );
 19
 20 # --- !Downs
 21
 22 DROP TABLE "user";
 23 DROP TABLE "article";
 24 DROP TABLE "comment";


次に各テーブルのモデルを定義する。


/app/models/Models.scala

  1 package models
  2
  3 case class User(username: String,  password: String, name: String)
  4 case class Article(user_id: Int, content: String)
  5 case class Comment(user_id: Int, article_id: Int, content: String)



モデルファイルがエンティティによって分かれていないのがちょっと不思議。
ファイルを別にすることもできると思いますがひとまず例に倣ってこれで。


DAOは下記のように記述。


/app/dao/UserDAO.scala

  1 package dao
  2
  3 import scala.concurrent.Future
  4
  5 import javax.inject.Inject
  6 import models.User
  7 import play.api.db.slick.DatabaseConfigProvider
  8 import play.api.db.slick.HasDatabaseConfigProvider
  9 import play.api.libs.concurrent.Execution.Implicits.defaultContext
 10 import slick.driver.JdbcProfile
 11
 12 class UserDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
 13   import driver.api._
 14
 15   private val Users = TableQuery[UsersTable]
 16
 17   def all(): Future[Seq[User]] = db.run(Users.result)
 18
 19   def insert(user: User): Future[Unit] = db.run(Users += user).map { _ => () }
 20
 21   private class UsersTable(tag: Tag) extends Table[User](tag, "user") {
 22
 23     def username = column[String]("username")
 24     def password = column[String]("password")
 25     def name = column[String]("name")
 26
 27     def * = (username, password, name) <> (User.tupled, User.unapply _)
 28   }
 29 }


ArticleDAO, CommentDAOも同様に定義したが、UserDAOと殆ど同じコードなので省略。



play consoleを起動しようとする。

activator console -Dplay.evolutions.db.default.autoApply=true


と、ブラウザが開いて下記ページが表示された。

Documentation - Lightbend Activator | @lightbend


To create new Lightbend projects Instead of using the Activator command, make sure you have sbt 0.13.13 (or higher), and use the “sbt new” command, providing the name of the template. For example, “$ sbt new akka/hello-akka.g8”. You can find a list of templates here.


Activatorコマンドは廃止になるらしく、今後はsbtコマンド使ってね、とのこと。
なるほど、Playに関してぐぐってるとactivator runやらsbt runやら
同じようなコマンドが出てきていたけど、そういうことだったのか。


というわけで、下記コマンドでコンソールを起動する。
問題なく起動した。

sbt console -Dplay.evolutions.db.default.autoApply=true


Userモデルをnewしてレコード追加してみる。

scala> import play.api._
scala> val env = Environment(new java.io.File("."), this.getClass.getClassLoader, Mode.Dev)
scala> val context = ApplicationLoader.createContext(env)
scala> val loader = ApplicationLoader(context)
scala> val app = loader.load(context)
scala> Play.start(app)
scala> import dao.UserDAO
scala> import models.User
scala> import scala.concurrent.Await
scala> import scala.concurrent.duration.DurationInt
scala> val app2dao = Application.instanceCache[UserDAO]
scala> val dao = app2dao(app)
scala> val user = new User("zettaittenani", "play", "zettaittenani")
scala> Await.result(dao.insert(user), 1.second)
scala> Await.result(dao.all, 1.second)
res5: Seq[models.User] = Vector(User(zettaittenani,play_test,zettaittenani))


レコードが入ったっぽいのでPostgreSQLの方で確認してみる。

playdb=> select * from user;
 current_user
--------------
 play


…どうやらuserというテーブル名がPostgreSQL自体のユーザのテーブル名とバッティングしていて
追加したレコードがあるかどうかわからない。

命名変更も面倒なのでArticleのレコードで試してみる。

scala> import play.api._
scala> val env = Environment(new java.io.File("."), this.getClass.getClassLoader, Mode.Dev)
scala> val context = ApplicationLoader.createContext(env)
scala> val loader = ApplicationLoader(context)
scala> val app = loader.load(context)
scala> Play.start(app)
scala> import dao.ArticleDAO
scala> import models.Article
scala> import scala.concurrent.Await
scala> import scala.concurrent.duration.DurationInt
scala> val app2dao = Application.instanceCache[ArticleDAO]
scala> val dao = app2dao(app)
scala> val article = new Article(1, "hogehoge")
scala> Await.result(dao.insert(article), 1.second)
scala> Await.result(dao.all, 1.second)
res2: Seq[models.Article] = Vector(Article(1,hogehoge))


playdb=> select * from article;
 user_id | content
---------+----------
       1 | hogehoge

よし、入ってる。


明日はview, controllerを使って画面上からレコードのCRUDをやりたい。

Play Scala DB connection その2

昨日の続き。



現状こんなかんじ。

/conf/application.conf

db {
  slick.dbs.default.driver="slick.driver.PostgresDriver$"
  slick.dbs.default.db.dataSourceClass = "slick.jdbc.DatabaseUrlDataSource"
  slick.dbs.default.db.properties.driver = "org.postgresql.Driver"
  slick.dbs.default.db.user=play
  slick.dbs.default.db.password="play"
}


改めてドキュメントを読んでみる。

PlaySlick - 2.4.x


ここで、"slick.jdbc.DatabaseUrlDataSource"DatabaseUrlDataSourceはべた書きではなく、例えばPostgreSQLのように書くのでは、と気づく。でもフォーマットがわからないのでどうしようもない。


困っていたらTwitterで救いの手が。。。

YoshinoriNさんありがとうございました。



ドキュメントをよくよく読むと、
「Herokuのような特定のプラットフォームでは以下のような書き方もできます」と書いてある。

slick.dbs.default.driver="slick.driver.PostgresDriver$"
slick.dbs.default.db.driver="org.postgresql.Driver"
slick.dbs.default.db.url=${JDBC_DATABASE_URL}


${JDBC_DATABASE_URL}にはjdbc:vendor://host:port/dbのようなフォーマットが入るらしい。
これを設定してみる。

db {
  slick.dbs.default.driver="slick.driver.PostgresDriver$"
  slick.dbs.default.db.driver="org.postgresql.Driver"
  slick.dbs.default.db.url="jdbc:postgresql://localhost/playdb"
  slick.dbs.default.db.user=play
  slick.dbs.default.db.password="play"

  play.evolutions.enabled=true
}


が、駄目。



どうしようもなく、playのサンプルアプリケーションのapplication.confを漁ってみると、
他の人はdb{}内にslickの設定を書いていないことに気づいた。


それに倣ってslickの設定をdb{}の外に出してみる。

db{
}

slick.dbs.default.driver="slick.driver.PostgresDriver$"
slick.dbs.default.db.driver="org.postgresql.Driver"
slick.dbs.default.db.url="jdbc:postgresql://localhost/playdb"
slick.dbs.default.db.user=play
slick.dbs.default.db.password="play"

play.evolutions.enabled=true


動いた!!

f:id:zettaittenani:20170912234611p:plain


Apply this script now!をクリックして、
PostgreSQLにテーブルが入っているかどうか確認する。

$ psql -d playdb -U play -W
Password for user play:
psql (9.6.3)
Type "help" for help.

playdb=> \d
            List of relations
 Schema |      Name       | Type  | Owner
--------+-----------------+-------+-------
 public | ARTICLE         | table | play
 public | COMMENT         | table | play
 public | member          | table | play
 public | play_evolutions | table | play
(4 rows)


ちゃんと入ってる!



調子に乗ってこんな書き方もしてみる。

/conf/application.conf

slick {
  dbs.default.driver="slick.driver.PostgresDriver$"
  dbs.default.db.driver="org.postgresql.Driver"
  dbs.default.db.url="jdbc:postgresql://localhost/playdb"
  dbs.default.db.user=play
  dbs.default.db.password="play"
}

play.evolutions.enabled=true


これも動いた。



かなり苦戦したけどこれでDB接続ができたので、次はModelを設定します。
Model設定ができればアプリケーションが書けるようになるので
今より楽しくなりそう。

Play Scala DB connection

やってみる。

Play * slick * PostgreSQL


設定

/build.sbt

こんなかんじ。

name := """play_test"""

version := "1.0-SNAPSHOT"

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

scalaVersion := "2.11.7"

libraryDependencies ++= Seq(
  cache,
  ws,
  "org.postgresql" % "postgresql" % "42.1.4",
  "com.typesafe.play" %% "play-slick" % "2.0.2",
  "com.typesafe.play" %% "play-slick-evolutions" % "2.0.2"
)

slick-evolutionsというのは、/conf/evolutions/default1.sql, 2.sql, … のように
SQL文を書いたファイルをおいておくと、最初にそれを実行してくれるslickのプラグインで、
Playはevolutionsをサポートしているそうです。
Railsのmigrationと違うけどちょっと似てる。


ちなみに最新版のslickを使いたくて以下のように書いていたが、
activator runが通らなかったので差し替えた。
(PostgreSQL用のJDBCは最新版で大丈夫、っぽい。)

  "org.postgresql" % "postgresql" % "42.1.4",
  "com.typesafe.play" % "play-slick_2.11" % "3.0.1",
  "com.typesafe.play" % "play-slick-evolutions_2.11" % "3.0.1"

/conf/application.conf

下記を参考に設定。

PlaySlick - 2.4.x

db {
  slick.dbs.default.driver="slick.driver.PostgresDriver$"
  slick.dbs.default.db.dataSourceClass = "slick.jdbc.DatabaseUrlDataSource"
  slick.dbs.default.db.properties.driver = "org.postgresql.Driver"
  slick.dbs.default.db.user=play
  slick.dbs.default.db.password="play"
}


DB playdbは予め作成済み。


localでPostgreSQLを起動し、activator runしてみる。



が、特に反応なし。



evolutions効かせてるのに起動後に画面に何も出ないし
ログにも何も出てないので連携そのものに失敗しているっぽい。



今日は解決できず、断念。