astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

はじめてのExpress.js 〜導入編(2)〜

こんにちは。フロントエンドエンジニアのkitoです。
前回、Node.jsで簡単なアプリを作成しました。 今回からExpress.jsに触れていきたいと思います。

簡単なアプリケーションをつくる

任意のディレクトリを作成して下記コマンドを実行してください。

npm init
npm install express --save

念のためExpress.jsがインストールできてるか確認しましょう。バージョンを表した数字が表示されればインストールされています。

express -V

それではExpress.jsで最小限のアプリケーションをつくります。 app.jsというファイルを作成して以下のコードを記述してください。

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000, function () {
  console.log('listening on port 3000');
});

以下コマンドで起動させます。

node app

ブラウザをひらいてhttp://localhost:3000 にアクセスしてみましょう。 Hello World!と表示されていると思います。
app.jsのコードを説明すると、require('express')で先ほどインストールしたExpress.jsのnpmモジュールを読み込んでいます。

var express = require('express');

次にアプリケーションの元となるオブジェクトを作成します。

var app = express();

特定のアドレスにアクセスしたときの処理、いわゆるルーティングを記述します。

app.get('/', function (req, res) {
  res.send('Hello World!');
});

これはルート直下にgetメソッドでアクセスしたのときの処理です。第1引数にアドレスを記述し、第2引数に関数を設定しています。この関数の引数にそれぞれrequestとresponseが渡されています。
sendはExpress.jsのメソッドで、HTMLの文字列やJSON、ステータスコードなどを送信できます。
そして、listenでアプリケーションサーバーにバインドして待ち受け状態にして、第1引数にport番号を記入します。

middleware

次にディレクトリを設定していきましょう。 ディレクトリは自由に設定できますが、下記のような設定がオススメです。

├── app.js
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
├── routes
│   └── index.js
│  
└── views
    └── index.ejs

app.jsとpackage.jsonは既に作成したのでそのままにします。 画像やcssなどのリソースの置き場所となるのがpublicディレクトリです。さらに後ほど補足するroutesディレクトリとviewsディレクトリを作成しておきましょう。 ディレクトリ構造を変更するのに伴ってapp.jsの変更が必要になります。

var express = require('express');
var path = require('path');
var routes = require('./routes/index');
var bodyParser = require('body-parser');
var app = express();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes);

app.listen(3000, function () {
  console.log('listening on port 3000');
});

require('path')で、Node.jsに組み込まれている標準モジュールのpathモジュールを読み込みます、これはファイルパスを扱う際に必要になります。 さらに、middlewareと呼ばれるタイプのnpmモジュールをインストールする必要があります。
Express.jsは最小限のフレームワークなので、頻繁に使われる機能だったとしてもmiddlewareとして分離されています。
実際、上記のbody-parserは、レスポンスボディを解析する際に必要になる機能ですがmiddlewareとして分離されているので、別途npmでインストールする必要があります。

npm install body-parser --save

viewディレクトリのパスをセットします。
またejsというテンプレートエンジンを使えるように設定します。これを使うとhtmlのなかで変数やfor文を使えるようになります。こちらもnpmでインストールしてください。 (テンプレートエンジンは他にjade,ectなど複数あり、好みに応じて変更可能です。ここではejsを使用します。)

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
npm install ejs --save

viewsディレクトリのなかにindex.ejsを作成して、以下を記述してください。 <%= title %>は、app.jsから渡される変数が展開されて表示されます。

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
  </body>
</html>

publicディレクトリは画像などのリソースを配置するためのディレクトリです。express.staticという標準搭載されているmiddlewareで設定します。

app.use(express.static(path.join(__dirname, 'public')));

ルーティングをroutesディレクトリに移動します。 routesディレクトリのなかにindex.jsを作成して以下を記述します。

var express = require('express');
var router = express.Router();

router.get('/', function(req, res, next) {
  res.render('index', { title: 'Hello World!' });
});

module.exports = router;

app.jsでルーティングを読み込みます。

var routes = require('./routes/index');

res.renderは、第1引数で渡したviewを描画するメソッドです。
ここではviews/index.ejsのtitle変数に「Hello World!」という文字列を渡しています。
<%= title %>が、「Hello World!」と展開されて表示されます。

これでミニマムなExpress.jsのアプリケーションは完成です。

generator

Express.jsには、アプリケーションの雛形を作成するgeneratorという便利な機能があります。
下記のコマンドを実行してgeneratorをインストールしてください。

npm install express-generator -g

下記のようにexpress [アプリ名]を実行すると、アプリのスケルトンが作成されます。

express my_app

create : my_app
create : my_app/package.json
create : my_app/app.js
create : my_app/public
create : my_app/routes
create : my_app/routes/index.js
create : my_app/routes/users.js
create : my_app/public/javascripts
create : my_app/views
create : my_app/views/index.jade
create : my_app/views/layout.jade
create : my_app/views/error.jade
create : my_app/public/images
create : my_app/public/stylesheets
create : my_app/public/stylesheets/style.css
create : my_app/bin
create : my_app/bin/www

先ほど手動で作成していたファイルが自動で生成されているのがわかると思います。
標準のテンプレートエンジンはjadeなのでejsを使いたければ引数 -eを加えます。

express -e myapp

create : my_app
create : my_app/package.json
create : my_app/app.js
create : my_app/public
create : my_app/routes
create : my_app/routes/index.js
create : my_app/routes/users.js
create : my_app/public/javascripts
create : my_app/views
create : my_app/views/index.ejs
create : my_app/views/error.ejs
create : my_app/public/images
create : my_app/public/stylesheets
create : my_app/public/stylesheets/style.css
create : my_app/bin
create : my_app/bin/www

npm installでpacake.jsonに記述されている依存関係にあるモジュールをインストールします。

cd my_app && npm install

npm startで起動できます。

npm start

http://localhost:3000にアクセスしてみましょう。
Express Welcome to Expressと表示されていれると思います。 f:id:astamuse:20160906143054p:plain

まとめ

Express.jsを使うと、前回のnode.jsのみで作成するアプリより簡単に作成できたと思います。 次回はより複雑なアプリケーション作成を通じてExpress.jsについて説明します。

アスタミューゼでは、エンジニア・デザイナーを募集中です。ご興味のある方は遠慮なく採用サイトからご応募ください。お待ちしています。

ExcelにはVBAがある!

お久しぶりでございます。scalaでバックエンドを開発しているaxtstar(@axtstart)でございます。

前回はRe:ゼロから始めるJavaScript入門 の話をしましたが、今回は、、、、 やっとScalaの話…ではなく、Scalaは私よりも詳しい方にお任せして Excel マクロでツールを作りましたのでその話をします。

f:id:astamuse:20160831035719p:plain

DevOpsでよくある要望

DevOpsや開発、システムテスト等で、とあるExcel上にあるデータをDBに格納したい。みたいな案件・要望はよくあると思います。

そしてそのExcelファイルも、セルのデータの中に改行コードや引用符がないのであれば良いのですが、そうとも限らないというのが実情です。 また昨今はxmlやJSONといった形式のデータがセルに格納されていて、それを登録するなんて話もあったりするかもしれません。

なのでVBAでExcelのセル内容をそのままDBに格納するExcelマクロを作りました。都合上PostgreSQL専用になっています。

.NETのCOM相互運用機能やJavaのpoiを使って、Excelファイルを読み込むことで、同様の機能は作れますが、プログラムのメンテナンス性を除いては、 データと処理が一体化できるExcelマクロに優位性があると感じて作成してみました。 *1

ただVBAのプログラムのメンテナンス性は著しく悪いです。

  • ソースファイルとExcelファイルが一体となって、バイナリで出来ている(最近のExcelの場合、xmlをzip圧縮しています。)

  • VBA自体の仕様が古いまま、あまり更新されていない

そこは少し調査して下記により、ソース(VBA)とバイナリ(xlsmファイル)を分離することにしました。

事前準備

残念ながら、Excelマクロは既定の状態では実行できない *2 ように設定されているため、下記によりマクロを有効にする必要があります。

f:id:astamuse:20160831092322p:plain

Ariawaseを使用したExcelマクロからVBAの分離

今回のレシピ

  • Windows

  • Microsoft Excel

  • コマンドプロンプト

標準の機能として、ExcelマクロはVBAをExportしたりImportすることができます。 この処理を、プログラムで行うvbac.wsfというスクリプトをigetaさんという方がAriawase というツールの中で開発していて今回利用させてもらいました。ありがとうございます。

私の知る限りxlsmでの開発は基本的にずいぶん前から変わっていません。Excel起動後、 開発タブ→Visual Basicの順でエディタを開きます(もしくはAlt-F11)。下記が表示されます。

※Excelのデフォルトでは開発タブが出ていないかもしれません。 その時は、ファイル→オプション→リボンのユーザ設定で開発にチェックを入れるとタブが現れます。

f:id:astamuse:20160830163756p:plain

初回のxlsm、ソース分離

作成したエクセルマクロ(*.xlsm)を上記ツール配下のbinディレクトリに配置します。

その後、コマンドプロンプトで、

$ cscript vbac.wsf decombine

これでbinに配置したxlsmの「ファイル名」をディレクトリ名として、VBAソースがExportされます。

↓こんな↓かんじのフォルダ構成

.
│  vbac.wsf
│
├─bin
│      DBTBL.xlsm ←対象のエクセルマクロ
├─src
│  └─DBTBL.xlsm ←この配下が生成されるフォルダ
│          AnalizeExcel.cls ←Exportされたクラス
│          Automation.bas ←Exportされた標準モジュール
│          ExcelDB.cls ←Exportされたクラス
│          FileUtility.cls ←Exportされたクラス
│          SQL.bas ←Exportされた標準モジュール

さらに、

$ cscript vbac.wsf clear

でbin\DBTBL.xlsmからVBA部分のコードを削除します。

このファイルをtemplateに移動してソース管理を行いました。

git管理時のフォルダ構成

.
│  .gitignore
│  build.bat
│  readme.md
│  vbac.wsf
│
├─.vscode
│      settings.json
│
├─bin
│      .gitkeep
│      DBTBL.xlsm ←成果物
│
├─src ←ソース
│  └─DBTBL.xlsm
│          AnalizeExcel.cls
│          Automation.bas
│          ExcelDB.cls
│          FileUtility.cls
│          SQL.bas
│
└─template ←テンプレートのエクセルマクロ(ボタンや設定シートのみがある)
        DBTBL.xlsm

template からbinにコピー後に先ほどのimportを呼び出すbatスクリプト (実質的なビルドスクリプト) [build.bat]

copy template\DBTBL.xlsm bin\
cscript vbac.wsf combine

binのエクセルマクロをVBA IDEで修正後、decombineでsrcソースに反映、 差分の確認や、gitでのソース管理ができます。

作成したマクロ

このツールDbTblExcelはpostgreSQLを対象にExcelにデータを持ってきたり、データを保存できる、マクロです。

つまり自分自身(DBTbl.xlsm)の変更を行います。今のところデータのImportはPKを指定しての、デリートインサートで行っています。

ソース管理できるようになったのでgithubで公開しました。

使い方はこちらに記載しました。

自由にお使い下さい。ですが、DBを直接修正できる、便利だけど、危険なものなので、 もし使用される際はよく動作を確認して使用して下さい。当マクロを使用して発生した損害は一切の責任を負いかねます。

デモ

※日本語字幕にコメントを記載しました。

www.youtube.com

こちら はマクロそのもののDLリンク

ExcelによるDB接続

ExcelからDBにアクセスする方法には、下記方法があります(たぶん方法的には10年以上変わっていないと思います)。

  • ODBC

https://www.postgresql.org/ftp/odbc/versions/msi/

  • OLEDB

http://pgfoundry.org/projects/oledb

こちらから対応したbitバージョン *3 のODBCドライバ、OLEDBドライバを取得してください。

※私の環境ではフリーのOLEDBドライバでは、うまくいかないことがありました。ODBCドライバをお勧めします。

おまけ - VisualStudio Codeでの設定 -

VSCodeデフォルト設定の場合、.clsがVisualBasicと判定されていないようなので、下記設定を行えばよいようです。(フォームがあるマクロの場合は.frmも?未確認)

また、Exportされるソースファイルは(日本語版Excelマクロの場合)SHIFT_JISですのでこちらもあわせて設定しておくのが良いです。

.vscode/settings.json

// 既定の設定とユーザー設定を上書きするには、このファイル内に設定を挿入します
{
      "files.encoding": "shiftjis",
      "files.associations": {
            "*.cls":"vb"
      }
}

下記のようになります。

f:id:astamuse:20160830171215p:plain

このソース管理には問題がある!

かつてxlsmファイルそのものをソース管理していたことがありました、が、バックアアップされているという意味以外では全く無意味でした。

ソース管理できていない状態でのエクセルマクロ開発は本当に苦痛でした。なので作った後どうしてもメンテナンスが続かなかったです。

みなさんもフォルダの片隅にそういうマクロがあるんじゃないですかね?

2016年になっても、VBAって言葉は結構聞きますね。 需要あるんでしょう。

手前みそですが、こんなサイトも。。

vbvba-job.com

今回はこの辺で。

最後に

アスタミューゼでは現在、エンジニア・デザイナーを募集中です。 興味のある方はぜひ 採用サイト からご応募ください。

*1:.NETが誕生してかなり経ちますが、残念ながら、MicrosoftはVBA.NETのような製品は世に出しておらず、Office製品内から自分自身や外部IOを操作するのはVBAを使う必要があります。

*2:マクロウィルスが大流行したせいですね。。

*3:Excelが32bit版の場合は32bit版、64bit版の場合は64bit版のドライバが必要

Play Framework 2.5でPostリクエストを処理してみる

https://www.playframework.com/assets/images/logos/play_full_color.png

アスタミューゼ 開発部のYanagitaです。

今回は前回執筆したPlay Frameworkの続きで、 Play Frameworkを使用してPost送信したデータを受け取るところを書きます。

事前準備

Play Frameworkの実行環境の設定がまだの方はこちらを参照

Postデータを受け取って表示する

まず、リクエストで受け取ったデータを格納するためのFormクラスとそのFormクラスにリクエストデータを格納するためのルールを記述します。

格納するためのFormクラス
/sample-app/app/forms/RequestForm.scala

case class RequestForm(
                      name: String, // 名前
                      sex: String, // 性別
                      birthday: Date, // 誕生日
                      height: Int, // 身長
                      bloodType: String // 血液型
                    )

リクエストデータを上のFormクラスに格納するルール
今回はサンプルのためControllerクラス内にルールを記述します。
他のリクエスト(Controllerクラス)でも使用する場合はルールを切り出してください。

/sample-app/app/controllers/SampleController.scala

package controllers

import javax.inject.Inject

import forms.RequestForm
import play.api.data.Form
import play.api.data.Forms._
import play.api.mvc._
import play.api.i18n.Messages.Implicits._
import play.api.Play._

class SampleController @Inject() extends Controller {
 // ここからが格納ルール
 val form = Form(
   mapping(
     "name" -> text,
     "sex" -> text,
     "birthday" -> date,
     "height" -> number,
     "bloodType" -> text
   )(RequestForm.apply)(RequestForm.unapply)
 )
 // ここまでが格納ルール
}

格納ルールの定義は、リクエストのパラメータ名とplay.api.data.Forms配下の型をマッピングしていきます。
以下はマッピングの組わせ

*Form内の型 *マッピング
scala.String play.api.data.Forms.text
scala.Int play.api.data.Forms.number
scala.Long play.api.data.Forms.longNumber
java.util.Date play.api.data.Forms.date
java.sql.Date play.api.data.Forms.sqlDate
org.joda.time.DateTime play.api.data.Forms.jodaDate
org.joda.time.LocalDate play.api.data.Forms.jodaLocalDate
scala.String play.api.data.Forms.email
scala.Boolean play.api.data.Forms.boolean
scala.Boolean play.api.data.Forms.checked
scala.Option play.api.data.Forms.optional

次に、入力ページと結果ページを表示するActionを上のControllerクラスに追記します。
前回はOkメソッドでただの文字列を返却するだけでしたが、今回はHTMLを返却したいのでHTMLを記述するテンプレートを指定します。
指定の際、テンプレート内で動的に変わるデータを扱う場合はテンプレートの引数に設定してます。
今回はリクエストデータをそのまま表示するため、Formクラスを設定します。
/sample-app/app/controllers/SampleController.scala

  // 入力ページを表示するAction
  def input = Action {
    Ok(views.html.sample.input(form))
  }
  // 結果ページを表示するAction
  def result = Action {
    Ok(views.html.sample.result(form))
  }

※ この時点ではテンプレートが未作成のためIDEだとエラーとなります。

次に、テンプレートを作成します。
テンプレートは"/プロジェクトディレクトリ/app/views"配下に配置し、拡張子は".scala.html"となります。
入力ページのテンプレート
/sample-app/app/views/sample/input.scala.html

@import helper._
@import forms.RequestForm
@(requestForm: Form[RequestForm])(implicit messages: Messages)
<!DOCTYPE html>
<html>
   <head>
       <title>Sample page.</title>
   </head>
   <body>
       <h2>Sample page(input).</h2>
       <hr>
       Sample form.
       @form(action = routes.SampleController.result()) {
           <!-- name -->
           @inputText(requestForm("name"))
           <!-- sex -->
           @inputRadioGroup(
               requestForm("sex"),
               options = Seq("M"->"Male","F"->"Female"))
           <!--  birthday -->
           @inputDate(field = requestForm("birthday"))
           <!-- height -->
           @inputText(requestForm("height"))
           <!-- bloodType -->
           @select(
               field = requestForm("bloodType"),
               options = Seq(
                   "A" -> "A",
                   "B" -> "B",
                   "O" -> "O",
                   "AB" -> "AB"
               )
           )
           <input type="submit" >
       }
   </body>
</html>

結果ページのテンプレート
/sample-app/app/views/sample/result.scala.html

@import forms.RequestForm
@(requestForm: Form[RequestForm])(implicit messages: Messages)
<!DOCTYPE html>
<html>
    <head>
        <title>Sample page.</title>
    </head>
    <body>
        <h2>Sample page(result).</h2>
        <hr>
        result <br>
        <table border="1">
            <tr>
                <th>name</th><td>@(requestForm("name").value)</td>
            </tr>
            <tr>
                <th>sex</th><td>@(requestForm("sex").value)</td>
            </tr>
            <tr>
                <th>birthday</th><td>@(requestForm("birthday").value)</td>
            </tr>
            <tr>
                <th>height</th><td>@(requestForm("height").value)</td>
            </tr>
            <tr>
                <th>bloodType</th><td>@(requestForm("bloodType").value)</td>
            </tr>
        </table>
    </body>
</html>

記述の内容は、JSPのPlay Framework版のようなもので、 表示に関するところはJSP同様にHTMLタグを使用して記述し、JSPのディレクティブやスクリプトレットに当たるものを"@〜〜"で記述していきます。
※ Play Frameworkのテンプレートでは、変数を参照したり、ロジックを書く場合は「@」を先頭についけるというルールがあります。
もう少し解説を入れると、

  • 入力ページの1行目〜2行目は、テンプレート内で必要なクラスをインポートしています。
    これは、JSPで言うところのpageタグのimportと同じ役割と果たしていて、Play Frameworkでは「@import クラスパス」で指定します。
  • 入力ページの3行目、メソッド作成時に引数を指定するのと一緒で、テンプレート内で使用するデータを引数として指定します。
    今回、Actionでテンプレートに値を渡す記述を行ったので、テンプレートではplay.api.data.Form[格納するためのFormクラス]を受け取る記述をしています。

また少し特殊なところで、Play FrameworkにはForm template helpersと呼ばれる機能がビルドインされています。
Form template helpersとは、テンプレート内でフォームや入力フィールドを自動生成してくれる機能で、 HTMLタグの自動生成のほか、エラーメッセージの出力などもしてれます。
今回は、Form template helpersの機能を使用して、Formタグ(@form)、テキストボックス(@inputText、@inputDate)、ラジオボタン(@inputRadioGroup)、セレクトボックス(@select)を表示するサンプルにしています。
サンプルで使用した以外には、

  • パスワード ・・・ @inputPassword
  • ファイル ・・・ @inputFile
  • テキストエリア ・・・ @textarea
  • チェックボックス ・・・ @checkbox

があります。
Form template helpersの細かい設定は別で執筆を予定しているのでここではこれ以上深くは触れません。

ちなみに、テンプレートファイルは一度Scalaコードに変換された後、classファイルとなります。
そのため、Actionから指定する場合は、テンプレートを配置したはパッケージとロジック上のパッケージが異なるので注意が必要です。

テンプレートファイルのパス → views/sample/input.scala.html
Action上で指定する場合 → views.html.sample.input
※ viewsパッケージの下にhtmlパッケージを入れます。

Formクラス、ページを表示するAction、ページのテンプレートができたところで、 routeファイルにURIとActionのひも付けを追記しブラウザで確認します。
/sample-app/conf/routes

# 入力ページを表示する
GET     /sample/input               controllers.SampleController.input
# 結果ページを表示する
POST  /sample/input                controllers.SampleController.result

ブラウザに表示される入力ページ
f:id:astamuse:20160823212323p:plain:h566

もしエラーが発生する場合は、テンプレートファイルの変換が終わっていない可能性があります。
一度、cleanを実施して、再度compileを実行してください。

activator clean
activator compile

当然ですが、ここまででは結果画面に移れても入力した内容は表示されません。
結果ページを表示するActionでリクエストをFormクラスにバインドする処理が抜けているからです。
最後にリクエストのデータをFormクラスにバインドする処理を追記します。
Formクラスへのバインド方法は、FormクラスのbindFromRequest#foldメソッドを使用し、 バインド結果毎に処理を記述します。
/sample-app/app/controllers/SampleController.scala

  // 結果ページを表示するAction
  def result = Action { implicit request => // リクエストオブジェクトを宣言
    form.bindFromRequest().fold(
      errorForm => { // バインドエラー = 入力エラーが発生した場合
        Ok(views.html.sample.input(errorForm)) // 入力画面を再表示します。
      },
      requestForm => { // バインド成功 = 入力エラーがない場合
        Ok(views.html.sample.result(form.fill(requestForm))) // 結果画面を表示します。
      }
    )
  }

前述の通り、バインド結果で実行される処理が別れます。
バインドに失敗した(入力エラーが発生した)場合は、一つ目の引数で渡した処理が実行され、 バインドに成功した(入力エラーが発生しない)場合は、二つ目の引数で渡した処理が実行されます。 この時、それぞれの処理で渡ってくる変数の型が異なるので注意が必要です。
バインドに失敗した場合は、 play.api.data.Form[格納するためのFormクラス]になります。これはエラーメッセージを含むために play.api.data.Formクラスでラップされています。 バインドに成功した場合は、格納するためのFormクラスとなります。

ブラウザに表示される結果ページ
f:id:astamuse:20160823234850p:plain:h336

Play FrameworkでのPost処理はいかがだったでしょうか?
コード量も少なく簡単だったと思います。
次回は初期値の設定と入力チェックについて執筆したいと思います。

最後に

アスタミューゼでは現在、エンジニア・デザイナーを募集中です。 興味のある方はぜひ 採用サイト からご応募ください。

Copyright © astamuse company, ltd. all rights reserved.