Grails 事始め

# Grails とは

Grails はコア技術とそれに関連するプラグインを通じて Web 開発における課題の多くを解決できるよう開発されたフルスタックフレームワークです。

Java 界隈における Web フレームワークの多くが、複雑であり DRY 原則を受け入れていない一方で、Grails は Rails や Django のような Web アプリケーションに関する現代的な考え方を導いた動的フレームワークのコンセプトを基にして、Spring や Hibernate のような既存の Java テクノロジーをベースに構築されています。

# Grails をインストールする

参考 http://docs.grails.org/latest/guide/single.html#requirements

記事では下記のバージョンを使いました。

- Grails Version: 3.3.5
- Groovy Version: 2.4.15
- JVM Version: 1.8.0_111

## *nix (Mac OS, Linux, BSD OS) ユーザ向けの Grails インストール方法

※ Ubuntu 16 でのみ動作確認しております。詳細は SDKMAN の[ドキュメント](https://sdkman.io/) を参照してください。

* SDKMAN をインストールする(SDKMAN の [Installation](https://sdkman.io/install) を参照)
* Java をインストールする  
    ```
    $ sdk install java
    ```
* Grails をインストールする  
    ```
    $ sdk install grails
    ```

## Windows ユーザ向けの Grails インストール方法

* Grails のバイナリファイル(zip形式) をダウンロードする([ダウンロード先](https://github.com/grails/grails-core/releases/))
* インストールしたい任意のフォルダ配下に zip ファイルを展開する (`C:\grails` 等)
* 展開した先のディレクトリ名を `GRAILS_HOME` としてシステム環境変数を設定する (`GRAILS_HOME=C:\grails`)
* 環境変数の `PATH` に `%GRAILS_HOME%\bin` を追加する
* インストールが出来たことを確認する
    * `grails -version` を実行して Grails のバージョンが表示されればインストール完了

# Grails アプリケーションを作成する

Grails アプリケーションを作成する際、必要なファイルを一括で作成する grails create-app コマンドを使います。

```shell
$ cd ${アプリケーション保存先ディレクトリ}
$ grails create-app grails-sample-app
# grails-sample-app は適宜アプリケーション名に変更する。
# "grails-sample-app" ディレクトリが作成され、
# アプリケーションのパッケージ名は "grails.sample.app" となる。
```

Grails 各コマンドをインタラクティブに実行できるモードを使うことも出来ます。 `grails` コマンドをオプション無しで実行するとインタラクティブモードが利用でき、インタラクティブモードではコマンドを Tab キーにより補完することが出来ます。

## Grails アプリケーションを実行する

アプリケーションを実行するには `run-app` コマンドを実行します。

```shell:Grailsアプリケーションを実行する(grailsインタラクティブモードを使わない場合)
$ cd ${create-app保存先ディレクトリ}
$ grails run-app
```

```shell:Grailsアプリケーションを実行する(grailsインタラクティブモードを使う場合)
$ cd ${create-app保存先ディレクトリ}
$ grails # インタラクティブモードでgrailsを起動する
grails> run-app
```

※ `grails help` でオプション一覧が表示され、 `grails help run-app` のように help 後にオプションを指定すると、そのオプションを実行するためのヘルプが表示されます。

### アプリケーション画面

run-app でアプリケーションを実行した後、http://localhost:8080/ へアクセスすると下記のような Grails のイントロ画面が表示されます。
(画面は create-app で作成したファイルに加えて HelloController を作成した状態)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/7b51cecf-6992-727f-ce80-d1a03a2ac4e8.png)

### ディレクトリ構成

```
.gradle/
.idea/              ... IntelliJ IDE 用設定ファイル
build/              ... ビルドファイル(create-app実行直後には存在しない)
gradle/
grails-app/         ... アプリケーションのソースコード
  assets/           ... アセットリソース置き場(アセットパイプラインで処理されるファイル群)
  conf/             ... ランタイム設定
  controllers/      ... コントローラ(MVC モデルの C)
    ${app名}/       ... app 名 (ハイフンが含まれる場合、ハイフン毎にディレクトリが分かれる)
      UrlMappings.groovy ... URLマッピング
  domain/           ... ドメインクラス(MVC モデルの M)
  i18n/             ... internationalization(i18n) 設定のサポート
  init/             ... アプリケーション起動時の処理
  services/         ... サービス層
  taglib/           ... Tag ライブラリ(View で利用できるカスタムタグを定義したもの)
  utils/            ... Grails に特化したユーティリティ
  views/            ... Groovy Server Page(GSP) or JSON Views (MVC モデルの V)
src/
  integration-test/ ... インテグレーションテスト
  main/             ... アセットパイプライン処理を通さない静的ファイル
    groovy/         ... DBテーブルと関連づけを行いたくないドメインクラス
    webapp/         ... 静的ファイル(WARに含まれ、JARに含まれない)
    resources/public ... 静的ファイル(/static/x/y/zでアクセスする)
  test/             ... ユニットテスト
    groovy/
      ${app名}/
        ${***Spec}.groovy
.gitignore          ... Git用 VCS 管理外ファイル設定
build.gradle        ... ビルド設定
gradle.properties   ... Gradle 設定
gradlew             ... Gradle 起動用スクリプト(UN*X用)
gradlew.bat         ... Gradle 起動用スクリプト(Windows用)
grails-wrapper.jar
grailsw             ... Grails 起動用スクリプト(UN*X用)
grailsw.bat         ... Grails 起動用スクリプト(Windows用)
README.md           ... README
```

詳細な情報

* build.gradle
    * [Groovyを知らない人のためのbuild.gradle読み書き入門](https://qiita.com/opengl-8080/items/a0bb31fb20cb6505188b)

# Hello World を表示する

文字列で "Hello World!" を表示させてみます。

Grails ではコントローラを作成してそのクラスの中でアクションを記述すると一意の URL にマッピングされ、ブラウザでアクセスできるようになります。

マッピングルールは `/<appname>/<controller>/<action>` となり、 `<controller>` はコントローラクラスから `Class` を除いた文字列になります。

例えば `HelloController` を作成して `index` アクションに "HelloWorld" を表示させるためには次の操作を行います。

```shell:Grailsアプリケーションを実行する
grails> create-controller hello # HelloController Class が作成される
```

`grails-app/controllers/grails/sample/app/HelloController.groovy` に作成された HelloController クラスを以下の内容に編集します。

```groovy:grails-app/controllers/grails/sample/app/HelloController.groovy
package grails.sample.app

class HelloController {
  def index() {
    render "Hello World!"
  }
}
```

アプリケーションを実行すると http://localhost:8080/hello/index 又は http://localhost:8080/hello/ へアクセスすると文字列 "Hello World!" が表示されます。
(index アクションは URL 上で省略できる)


# ドメインの CRUD 機能を作成する

ドメインは Grails における MVC の M です。
先ほどの HelloWorld を表示させた例のように、コントローラやビューを個別に作成することも出来ますが、 `generate-all` オプションを使うことでドメインの CRUD 機能を持つコントローラとビュー(加えてテスト用クラス)を作成することが出来ます。

```shell:EmployeeドメインクラスとCRUD用コントローラとビュー、テストを作成
# Employee ドメインクラスを作成する
$ grails create-domain-class employee
| Created grails-app/domain/grails/sample/app/Employee.groovy
| Created src/test/groovy/grails/sample/app/EmployeeSpec.groovy
# Employee ドメインクラスをCRUDするコントローラ、ビュー、テスト用クラスを作成する
$ grails generate-all grails.sample.app.Employee
| Rendered template Controller.groovy to destination grails-app\controllers\grails\sample\app\EmployeeController.groovy
| Rendered template Service.groovy to destination grails-app\services\grails\sample\app\EmployeeService.groovy
| Rendered template Spec.groovy to destination src\test\groovy\grails\sample\app\EmployeeControllerSpec.groovy
| Rendered template ServiceSpec.groovy to destination src\integration-test\groovy\grails\sample\app\EmployeeServiceSpec.groovy
| Scaffolding completed for grails-app\domain\grails\sample\app\Employee.groovy
| Rendered template create.gsp to destination grails-app\views\employee\create.gsp
| Rendered template edit.gsp to destination grails-app\views\employee\edit.gsp
| Rendered template index.gsp to destination grails-app\views\employee\index.gsp
| Rendered template show.gsp to destination grails-app\views\employee\show.gsp
| Views generated for grails-app\domain\grails\sample\app\Employee.groovy
```

以上で Employee ドメインクラスを CRUD する機能が作成できました。

まだドメインクラスには何も属性が無いため、ファイル `grails-app/domain/grails/sample/app/Employee.groovy` を編集して適当に属性を追加します。

```groovy:grails-app/domain/grails/sample/app/Employee.groovy
package grails.sample.app

class Employee {

  String name

  static constraints = {
  }
}
```

run-app を再度実行してブラウザでアプリケーションの TOP 画面を表示すると、 Available Controllers: に `grails.sample.app.EmployeeController` が追加されています。

grails.sample.app.EmployeeController の文字列はリンクになっており、クリックすると Employee 一覧画面(http://localhost:8080/employee/index) に遷移します。

|Employee一覧画面|
| --- |
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/b38f5543-337c-9996-9771-48199033757c.png)|

ある程度操作をすると分かると思いますが、次のルートが追加されています。

|ルート|機能|
| --- | --- |
|GET /employee/index|Employee一覧画面を表示|
|GET /employee/create|Employee新規作成画面を表示|
|GET /employee/show/:id|Employee詳細画面(:idはドメインクラスのID)を表示|
|GET /employee/edit/:id|Employee編集画面(:idはドメインクラスのID)を表示|
|POST /employee/save|Employee新規作成|
|PUT /employee/update/:id|Employee更新(:idはドメインクラスのID)|
|DELETE /employee/delete/:id|Employee削除(:idはドメインクラスのID)|

アプリケーションが利用する DB は development, test, production といった環境変数毎に変えることが出来ます。
Grails では全ての環境においてデフォルトで [H2 Database](http://h2database.com/html/main.html) を利用するよう設定されています。

また CRUD 操作を一通り終えて run-app を終了して再度アプリケーションを実行すると DB が初期化されていることに気が付くと思います。
これは H2 はインメモリ DB であり、加えて開発環境の DB はアプリケーション終了時に DB を DROP し、起動時に作成するように設定されているためです。

これらの設定は grails-app/conf/application.yml で設定します。
(設定内容の詳細は [The Grails Framework > The DataSource](http://docs.grails.org/latest/guide/single.html#dataSource) を参照してください。)

今回、development 環境で作成したデータベースは破棄しないよう設定することにします。
次の内容で `grails-app/conf/application.yml` を書き換えてください。

```yaml:grails-app/conf/application.yml
environments:
  development:
    dataSource:
      # create-drop から update へ変更する
      dbCreate: update
      # mem:devDbから./devDbへ変更する
      url: jdbc:h2:./devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
```

尚、dbCreate で設定できる値は次のとおりです。

|dbCreateの設定値|説明|
| --- | --- |
|create|起動時に既存スキーマ(テーブル, 索引, 他)をドロップし再作成する|
|create-drop|createと同じだが、アプリケーション終了時にも table を削除する|
|update|存在しないテーブルや索引を作成又は更新する。既存のテーブルやデータはドロップしない。(カラム名の変更などを適切に処理できず古いカラムとデータが残ることに注意)|
|validate|DBに変更を加えず、既存スキーマと設定を比較してワーニングを出力する|
|*any other value*|何もしない|

# ドメインクラスをソースコードで操作する

## ドメインを作成する

```groovy
def p = new Person(name: "Fred", age: 40, lastVisit: new Date())
p.save()
```

## ドメインを読み取る

```groovy
def p = Person.get(1)
println('name: ' + p.name)
```

* get (http://docs.grails.org/latest/ref/Domain%20Classes/get.html)
    * 指定したIDのドメインクラスのインスタンスを取得する
    * 指定したIDが見付からなかった場合は null が返される
* read (http://docs.grails.org/latest/ref/Domain%20Classes/read.html)
    * 指定したIDのドメインクラスのインスタンスを読み取り専用で取得する
    * 指定したIDが見付からなかった場合は null が返される
    * 読み取り専用とは自動でダーティ検出をしないことを指し、明示的に save() を呼び出すことで DB の変更は可能である
* load (http://docs.grails.org/latest/ref/Domain%20Classes/load.html)
    * 指定したIDのドメインクラスのプロクシとなるインスタンスを取得する
        * プロクシは getId() 以外のメソッドを呼び出した時に初期化される
    * 指定したIDが見付からなかった場合もプロクシが返される
        * プロクシに getId() 以外のメソッドを呼び出した時に例外 `org.hibernate.ObjectNotFoundException` が発生する
    * ID として null を渡した時に null が返される

## ドメインを更新する

```groovy
def p = Person.get(1)
p.name = 'Bob'
p.save()
```

## ドメインを削除する

```groovy
def p = Person.get(1)
p.delete()
```

# Employee モデルを管理するアプリケーションを作る

もう少し Employee ドメインクラスを拡張させてより実践的にしてみます。

```groovy:grails-app/domain/grails/sample/app/Employee.groovy
package grails.sample.app

class Employee {

  String name
  String department
  String gender
  Date birth
  Date joinedDate
  Long payment
  String note

  static constraints = {
    name blank: false, unique: true
    department blank: false
    gender blank: false, inList: ['male', 'female', 'other']
    birth blank: false
    joinedDate blank: false
    payment min: new Long(0), blank: false
    note blank: true
  }

  /**
  * 勤続年数
  */
  public int serviceYears() {
    def today = new Date()
    return Math.max(0, today.year - this.joinedDate.year)
  }
}
```

ひとまず note 以外は `nullable: false` (default なので constraints には未指定), `blank: false` としました。
note のように `blank: true` にする場合は、データが blank である場合に null へ変換する処理を無効化する必要があります。

その設定は `application.groovy` に下記のとおり設定することで無効化できます。
ファイルが存在しなければ `grails-app/conf/` 配下に作成してください。

```groovy:grails-app/conf/application.groovy
// the default value for this property is true
grails.databinding.convertEmptyStringsToNull = false
```

さて、Employee を表示させる一覧画面と編集画面を次のように変更します。

- 一覧画面
    - ID, name, department, gender を表示する
    - 各項目でソートが出来る
    - Employee 新規作成ボタンを表示する
        - いつでも新規作成ボタンを押せることとする
    - Employee 編集ボタンを表示する
        - どれか Employee が選択された場合だけ押せることとする
- 詳細画面
    - 一覧画面で表示する項目に加えて、birth, serviceYears()実行結果, payment, note を表示する

```html:一覧画面のビュー(grails-app/views/employee/index.gsp)
<!DOCTYPE html>
<html>
  <head>
    <meta name="layout" content="main" />
    <g:set var="entityName" value="${message(code: 'employee.label', default: 'Employee')}" />
    <title><g:message code="default.list.label" args="[entityName]" /></title>
    <script type="text/javascript">
      /* 編集ボタンのステータスをラジオボタンのステータスを基に設定する */
      function setEditButtonStatusByRadioButton() {
        var edit_button_id = "edit_button";
        var radios = document.getElementsByName('id');
        var checkedNum = 0;
        radios.forEach(e => e.checked && checkedNum++);
        if (checkedNum > 0) {
          document.getElementById(edit_button_id).disabled = false;
        } else {
          document.getElementById(edit_button_id).disabled = true;
        }
      }
    </script>
  </head>
  <body>
    <a href="#list-employee" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
    <div class="nav" role="navigation">
      <ul>
        <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
        <li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
      </ul>
    </div>
    <div id="list-employee" class="content scaffold-list" role="main">
      <h1><g:message code="default.list.label" args="[entityName]" /></h1>
      <g:if test="${flash.message}">
        <div class="message" role="status">${flash.message}</div>
      </g:if>

      <g:form action="edit">
        <table class="table table-striped">
          <thead>
            <tr>
              <th>Edit</th>
              <g:each in="${['id', 'name', 'department', 'gender']}" var="p">
                <g:sortableColumn property="${p}" title="${p}" />
              </g:each>
            </tr>
          </thead>
          <tbody>
          <g:each in="${employeeList}" var="employee">
            <tr>
              <td><input name="id" type="radio" value="${employee.id}" onclick="setEditButtonStatusByRadioButton()" /></td>
              <g:each in="${['id', 'name', 'department', 'gender']}" var="p">
                <g:if test="${p=='id'}">
                  <td><g:link method="GET" resource="${employee}">${employee.properties[p]}</g:link></td>
                </g:if>
                <g:else>
                  <td>${employee.properties[p]}</td>
                </g:else>
              </g:each>
            </tr>
          </g:each>
          </tbody>
        </table>

        <button disabled="false" id="edit_button"><g:message code="default.edit.label" args="[entityName]" /></button>
      </g:form>

      <div class="pagination">
        <g:paginate total="${employeeCount ?: 0}" />
      </div>
    </div>
  </body>
</html>
```

```html:詳細画面のビュー(grails-app/views/employee/show.gsp)
<!DOCTYPE html>
<html>
  <head>
    <meta name="layout" content="main" />
    <g:set var="entityName" value="${message(code: 'employee.label', default: 'Employee')}" />
    <title><g:message code="default.show.label" args="[entityName]" /></title>
  </head>
  <body>
    <a href="#show-employee" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
    <div class="nav" role="navigation">
      <ul>
        <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
        <li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
        <li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
      </ul>
    </div>
    <div id="show-employee" class="content scaffold-show" role="main">
      <h1><g:message code="default.show.label" args="[entityName]" /></h1>
      <g:if test="${flash.message}">
      <div class="message" role="status">${flash.message}</div>
      </g:if>

      <ol class="property-list employee">
        <li class="fieldcontain">
          <g:each in="${['id', 'name', 'department', 'gender', 'birth', 'serviceYears', 'payment', 'note']}" var="p">
            <span id="name-label" class="property-label">${p}</span>
            <g:if test="${p=='serviceYears'}">
              <div class="property-value" aria-labelledby="name-label">${employee.serviceYears()}</div>
            </g:if>
            <g:else>
              <div class="property-value" aria-labelledby="name-label">${employee.properties[p]}</div>
            </g:else>
          </g:each>
        </li>
      </ol>
      
      <g:form resource="${this.employee}" method="DELETE">
        <fieldset class="buttons">
          <g:link class="edit" action="edit" resource="${this.employee}"><g:message code="default.button.edit.label" default="Edit" /></g:link>
          <input class="delete" type="submit" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" />
        </fieldset>
      </g:form>
    </div>
  </body>
</html>
```

以上で、少し実践的な内容になりました。

尚、ビューは Groovy Server Pages (GSP) で記述されており、 `<g:link>`, `<g:form>` 等は GSP タグです。(詳細は [Groovy Server Pages (GSP)](https://gsp.grails.org/latest/guide/index.html) を参照してください)

# Twitter Bootstrap を導入する

Grails 3.3.5 にはデフォルトで Bootstrap 3.3.6 が導入されていました。

- grails-app/assets/javascripts/bootstrap.js
- grails-app/assets/stylesheets/bootstrap.css

Grails plugin の [twitter-bootstrap-grails-plugin](https://github.com/groovydev/twitter-bootstrap-grails-plugin) があるようですが、'18/09/18 現在のバージョンが 3.3.5 のようであり、最終コミットが 2 年前ということであまりメンテされている様子もないため、Bootstrap v4 を手動で導入してみることにします。

## Twitter Bootstrap v4 導入手順

1. インストールされている Bootstrap(js, css), JQuery を削除する
    - grails-app/assets/javascripts/bootstrap.js
    - grails-app/assets/stylesheets/bootstrap.css
    - grails-app/assets/javascripts/jquery-2.2.0.min.js
1. Bootstrap v4 をダウンロードする
    - https://getbootstrap.com/docs/4.1/getting-started/download/
        - "Compiled CSS and JS" から Download ボタンを押す
1. JQuery v3 をダウンロードする
    - https://jquery.com/download/
        - "Download the compressed, production jQuery 3.x.y" のリンク先ファイルを保存する
1. Bootstrap v4, JQuery v3 を Grails アプリケーションフォルダにコピーする
    - grails-app/assets/javascripts/bootstrap.bundle.js
    - grails-app/assets/javascripts/jquery-3.3.1.min.js
    - grails-app/assets/stylesheets/bootstrap.css
    - grails-app/assets/stylesheets/bootstrap.css.map (CSSのデバッグ用途なので好みで追加してください)
1. `grails-app/assets/javascripts/application.js` を編集する  
    - `//= require jquery-2.2.0.min` から `//= require jquery-3.3.1.min` へ変更する
    - `//= require bootstrap` から `//= require bootstrap.bundle` へ変更する

アプリケーションを再実行すれば完了です。(navbar が崩れますが使っていないので無視します)

## 導入後の確認

先ほど記述した index.gsp ビューを編集してみましょう。

```html:grails-app/views/layouts/main.gsp
<head>
  : <snip>
  <asset:stylesheet src="application.css"/>
  <asset:javascript src="application.js"/> <!-- body内に書かれている行をhead内へ移動させる -->
  : <snip>
</head>
  : <snip>
```

```html:grails-app/views/employee/index.gsp
  : <snip>
<button class="btn btn-secondary" disabled="false" id="edit_button"><g:message code="default.edit.label" args="[entityName]" /></button>
  : <snip>
```

ボタンに Bootstrap 4 のスタイルが適用されれば OK です。

# DataTable を使って一覧画面にフィルタ機能を追加する

## 事前準備

Employee 一覧画面にフィルタ機能を追加して、もう少し実用的にしてみます。

今回一覧画面に表示したテーブルに検索機能を追加するために、[DataTables](https://github.com/DataTables/DataTables) を使うことにします。

尚、Grails plugin として [grails-datatables](https://bitbucket.org/ben-wilson/grails-datatables/wiki/Home) 等がありましたが、bootstrap, jquery と同様に最新バージョンの DataTables をインストールすることにします。

[DataTables の Download ページ](https://datatables.net/download/)から下記の項目を選択してファイルをダウンロードします。

* Styling framework: Bootstrap4
* Packages: DataTables
* Extensions: 好みに応じて(例では何も選択してません)
* Minify, Concatenate: 無効

ダウンロードしたファイルから下記のファイルを種別に応じて、grails-app/assets/(javascripts|stylesheets)/配下にコピーします。

* DataTables-x.y.z/
    * js/
        * dataTables.bootstrap4.js
        * jquery.dataTables.js
    * css/
        * dataTables.bootstrap4.css

最後に grails-app/assets/application.(js|css) を編集します。
application.(js|css) に記述した require 先のファイルは上から順に読み込まれるため依存関係を持つパッケージは記述順序に気を付けてください。

```javascript:grails-app/assets/javascripts/application.js
  : <snip>
//= require jquery-3.3.1.min
//= require bootstrap.bundle
//= require jquery.dataTables
//= require dataTables.bootstrap4
//= require_tree .
//= require_self
  : <snip>
```

```css:grails-app/assets/stylesheets/application.css
/*
  : <snip>
*= require bootstrap
*= require dataTables.bootstrap4
*= require grails
*= require main
*= require mobile
*= require_self
  : <snip>
*/
```

これで DataTables を使う準備が整いました。

## 一覧画面の表に DataTables を適用する

一覧画面の表に DataTables を適用します。

DataTables を適用するためには、jQuery で適用したい table を指す DOM に対して `dataTable()` メソッドを適用します。

```html:grails-app/views/employee/index.gsp
<!DOCTYPE html>
<html>
  <head>
    : <snip>
    <script type="text/javascript">
      /* 編集ボタンのステータスをラジオボタンのステータスを基に設定する */
      function setEditButtonStatusByRadioButton() {
        var edit_button_id = "edit_button";
        var radios = document.getElementsByName('id');
        var checkedNum = 0;
        radios.forEach(e => e.checked && checkedNum++);
        if (checkedNum > 0) {
          document.getElementById(edit_button_id).disabled = false;
        } else {
          document.getElementById(edit_button_id).disabled = true;
        }
      }

      $(document).ready(function() {
        $('#employeeindex').dataTable();
      } );
    </script>
  </head>
  <body>
    : <snip>
        <table id="employeeindex" class="display table table-striped">
          : <snip>
        </table>
    : <snip>
  </body>
</html>
```

アプリケーションを実行すると DataTables が適用されたことが分かります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/130976a2-4bc8-a923-5f76-117b599cf529.png)

ここで、DataTables と Grails のページネーション機能が冗長となってしまっていることが分かります。

どちらの機能を有効にするかはアプリケーションの設計次第となりますが、今回はデータ処理量を減らすよう、Grails のページネーション機能を残すことにします。

DataTables のページネーション機能を無効化するためには[公式ページ](https://datatables.net/reference/option/paging)に書かれているとおり、dataTable() メソッドに `paging: true` パラメータを渡します。

```javascript:#employeeindexのDOMに対してページネーション機能なしでDataTablesを適用する
$(document).ready(function() {
  $('#employeeindex').dataTable({
    "paging": false
  });
} );
```

最後に、画面上部の Navbar を修正します。
また、合わせてスタイルを修正し、ICON を使うために [Font Awesome](https://fontawesome.com/) を CDN で追加します。

```css:grails-app/assets/stylesheets/main.css
/* NAVIGATION MENU */

.nav, nav {
    zoom: 1;
}

.nav ul {
    overflow: hidden;
    padding-left: 0;
    zoom: 1;
}

.nav li {
    display: block;
    float: left;
    list-style-type: none;
    margin-right: 0.5em;
    padding: 0;
}

.nav a, nav a {
    color: #666666;
    display: block;
    padding: 0.25em 0.7em;
    text-decoration: none;
    -moz-border-radius: 0.3em;
    -webkit-border-radius: 0.3em;
    border-radius: 0.3em;
}

.nav a:active, .nav a:visited, nav a:active, nav a:visited {
    color: #666666;
}

.nav a:focus, .nav a:hover, nav a:focus, nav a:hover {
    background-color: #999999;
    color: #ffffff;
    outline: none;
    text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
}

.no-borderradius .nav a:focus, .no-borderradius nav a:focus, .no-borderradius .nav a:hover, .no-borderradius nav a:hover {
    background-color: transparent;
    color: #444444;
    text-decoration: underline;
}

.nav a.home, .nav a.list, .nav a.create, nav a.home, nav a.list, nav a.create {
    background-position: 0.7em center;
    background-repeat: no-repeat;
    text-indent: 25px;
}

.nav a.home, nav a.home {
    background-image: url(../images/skin/house.png);
}

.nav a.list, nav a.list {
    background-image: url(../images/skin/database_table.png);
}

.nav a.create, nav a.create {
    background-image: url(../images/skin/database_add.png);
}

.nav li.dropdown ul.dropdown-menu, nav li.dropdown ul.dropdown-menu {
    background-color: #424649;
}
```

```css:grails-app/assets/stylesheets/mobile.css
@media screen and (max-width: 480px) {
    .nav, nav {
        padding: 0.5em;
    }

    .nav li, nav li {
        margin: 0 0.5em 0 0;
        padding: 0.25em;
    }
  : <snip>
}
```

```html:grails-app/views/layouts/main.gsp
<!doctype html>
<html lang="en" class="no-js">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
  <title>
    <g:layoutTitle default="Grails"/>
  </title>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <asset:link rel="icon" href="favicon.ico" type="image/x-ico" />

  <asset:stylesheet src="application.css"/>
  <asset:javascript src="application.js"/>
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">

  <g:layoutHead/>
</head>
<body>

  <nav class="navbar-expand-lg pr-3 navbar navbar-default">
    <a class="navbar-brand" href="/#">
      <asset:image src="grails.svg" alt="Grails Logo"/>
    </a>
    <button class="navbar-toggler mx-2" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="fas fa-bars" style="font-size: 3rem;"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto">
        <g:pageProperty name="page.nav" />
      </ul>
    </div>
  </nav>

  <g:layoutBody/>

  <div class="footer" role="contentinfo"></div>

  <div id="spinner" class="spinner" style="display:none;">
      <g:message code="spinner.alt" default="Loading&hellip;"/>
  </div>

</body>
</html>
```

|Large Display Size|Small Display Size|
| --- | --- |
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/97189e90-215c-d9d7-6a7e-bc6541853d67.png)|![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/fa1c9f63-8e03-cfa2-e82b-b8d13020f95d.png)|


# データベースマイグレーション

これまでデータベースは `dbCreate: update` に設定することでアプリケーション起動時にモデルファイルを参考にして更新をしましたが、変更が出来る条件が限られていることや古いカラムが残ることから、複数人でアプリケーションを開発する時には人によって違うデータベース構造となってしまう可能性があります。

そこで、マイグレーションファイルを作成して、そこからデータベースを作成することにします。

Grails でデータベースマイグレーションを行うためには `database-migration` プラグインを使います。(`database-migratation` プラグインは [Liquibase](http://www.liquibase.org/) ライブラリを使っています。)

マイグレーションは Groovy DSL 又は native Liquibase XML により記述された1つ以上のチェンジログファイルを使って処理されます。

チェンジログファイルはグローバルにユニークな ID を持つ。(ID にはチェンジログを作成したユーザのユーザ名が含まれる)

## 事前準備

`database-migration` プラグインを使うために `bundle.gradle` を下記のように編集します。(プラグインのバージョンは適宜変更する)

尚、 `bundle.gradle` は Gradle 用の設定ファイルであり、Gradle とは Apache Ant や Apache Maven のコンセプトに基づくオープンソースビルド自動化システムです。(参考: [Wikipedia > Gradle](https://ja.wikipedia.org/wiki/Gradle))

```gradle
buildscript {
  repositories {
    : <snip>
  }
  dependencies {
    : <snip>
    classpath 'org.grails.plugins:database-migration:3.0.4' # database-migrationプラグインを追加
  }
}

dependencies {
    : <snip>
  compile 'org.grails.plugins:database-migration:3.0.4'
  compile 'org.liquibase:liquibase-core:3.5.5'
    : <snip>
}

  : <snip>

sourceSets {
  main {
    resources {
      srcDir 'grails-app/migrations'
    }
  }
}
```

次にデータベースの `dbCreate` を `none` に設定します。
そうしないと、 `dbm-gorm-diff` オプションを使ってマイグレーションファイルを作成する際にうまく差分が出なくなってしまいます。

```yaml:grails-app/conf/application.yml
environments:
  development:
    dataSource:
      dbCreate: none
      url: jdbc:h2:./devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
  : <snip>
```

## チェンジログファイルを作成する

次にチェンジログファイルを作成します。

記述形式は選ぶことが出来ますが、Groovy DSL を使うことにします。

また、チェンジログはデータベースから作成する方法とドメインクラスから作成する方法の2つがあります。今はドメインクラスから起動時に自動でデータベースを作成していると思いますので、データベースからチェンジログを作成する方法を選択します。

```shell:データベースからチェンジログをGroovyDSL形式で作成する方法
$ grails dbm-generate-changelog changelog.groovy
  : <snip> 必要に応じてプラグインがダウンロードされる
:compileJava NO-SOURCE
:compileGroovy UP-TO-DATE
:buildProperties UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:findMainClass
:dbmGenerateChangelog

BUILD SUCCESSFUL

Total time: 16.478 secs
```

すると、次のチェンジログが作成されます。

```groovy:チェンジログ(grails-app/migrations/changelog.groovy)
databaseChangeLog = {

  changeSet(author: "tatsurou (generated)", id: "1537102923747-1") {
    createTable(tableName: "EMPLOYEE") {
      column(autoIncrement: "true", name: "ID", type: "BIGINT(19)") {
        constraints(primaryKey: "true", primaryKeyName: "CONSTRAINT_7")
      }

      column(name: "VERSION", type: "BIGINT(19)") {
        constraints(nullable: "false")
      }

      column(name: "NAME", type: "VARCHAR(255)") {
        constraints(nullable: "false")
      }
    }
  }
}
```

これでチェンジログを基に DB を構築することが出来るようになりました。
(構築されるのはデータベースのスキーマだけであり、データはマイグレーションされません)

今後、ドメインクラスを編集した後にチェンジログを手動又は自動で作成し、 `dbm-update` を実行することになります。

1. ドメインクラスを編集する
2. チェンジログを編集する
    - 自動で差分を作成する方法(ファイル名と `--add` オプションをつけるとチェンジログを指定したファイル名で別途保存し、changelog.groovy から include により読み込まれるよう追記される)  
        ```
        $ grails dbm-gorm-diff add_birth_column_to_employee.groovy --add
        ```
3. データベースをバックアップする(何か問題が発生した時のため)
4. `grails dbm-update` を任意の環境(development, test, production)に対して実行する

# Groovy コンソール

Groovy コンソールとは、Groovy ソースコードを入力して実行することが出来るアプリケーションです。
Grails アプリケーションで実装したドメインクラスを読み込むことや、そのドメインクラスの ORM を使ってデータベースを操作することも出来ます。

grails console コマンドを実行することで Grails コンソールを起動することが出来ます。

```shell:groovyコンソールを起動する
$ cd ${applicationのディレクトリ}
$ grails console
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/7370c4bb-539e-dbe2-dc3c-69fa385b16ec.png)

```groovy
package grails.sample.app

def id = 1
def e = Employee.get(id)
if (e == null) {
  println("Not found employee id " + id)
  exit()
}
println("employee: " + e)
println("name: " + e.name)
e.name = 'test99'
e.save(flush: true)
```

# エラー対応

Grails コマンドを実行する中で発生するエラーとその対応方法を記述します。

## Could not acquire change log lock

```
Command execution error: Could not acquire change log lock.  Currently locked by ${COMPUTER_NAME} (${IP_ADDRESS}) since 18/09/17 0:09
```

- `grails dbm-release-locks` 又は DB を削除する