Ruby 製サーバーレスフレームワーク Jets で DynamoDB テーブルを使ったサンプルアプリケーションを実装する

## はじめに

最近ずっと検証をしている [Jets](https://github.com/tongueroo/jets)。徐々に記事が積み重なってきました。

* [Ruby 製サーバーレスフレームワークの Jets を検証してみたら、Rails ライクに使えていい感じだった](https://qiita.com/gotchane/items/fd3c1e54f7bdaa4ab813)
* [Ruby 製サーバーレスフレームワーク Jets から DynamoDB アイテムの CRUD 操作を試す](https://qiita.com/gotchane/items/d998b49dcaa31fe286b9)
* [Ruby 製サーバーレスフレームワーク Jets は AWS Container HERO がメインで開発しているようだ](https://qiita.com/gotchane/items/02b9c539bfaf3a69d76e)

今回は、DynamoDB テーブルを使った Jets のサンプルアプリケーションを作ってみました。

## 前提環境

* Mac OS X 10.13.6
* HomeBrew 1.8.6
* ruby 2.5.1
* Jets 1.4.5
* nodejs v9.11.2
* Java version 100

## 作成するサンプルアプリケーション

DynamoDB テーブルを使った、`Post` モデルの CRUD 操作ができるアプリケーションを作ります。

ソースコードは以下にアップしています。

[GitHub - gotchane/jets-dynamodb-app-sandbox](https://github.com/gotchane/jets-dynamodb-app-sandbox)

## 手順

DynamoDB テーブル作成までは、以下記事にも手順を書いてあるので、最低限のコマンドだけ記載します。

[Ruby 製サーバーレスフレームワーク Jets から DynamoDB アイテムの CRUD 操作を試す - Qiita](https://qiita.com/gotchane/items/d998b49dcaa31fe286b9)

### Jets アプリケーション新規作成

```bash
$ jets new jets-dynamodb-app-sandbox
$ cd jets-dynamodb-app-sandbox
```

### dynamodb-local インストール

```bash
$ brew cask install dynamodb-local
$ dynamodb-local
```

### dynamodb-admin インストール

```bash
$ npm install dynamodb-admin -g
$ export DYNAMO_ENDPOINT=http://localhost:8000
$ dynamodb-admin
```

### DynamoDB テーブル作成

```bash
$ jets dynamodb:generate create_posts
$ jets dynamodb:migrate ./dynamodb/migrate/20190114114303-create_posts_migration.rb

Running database migrations
Calling create_table with params:
{:table_name=>"jets-dynamodb-app-sandbox-dev-posts",
 :key_schema=>[{:attribute_name=>"id", :key_type=>"HASH"}],
 :attribute_definitions=>[{:attribute_name=>"id", :attribute_type=>"S"}],
 :provisioned_throughput=>{:read_capacity_units=>5, :write_capacity_units=>5}}
DynamoDB Table: posts Status: ACTIVE
```

### scaffold で Post モデルの CRUD 画面のひな形作成

ここまでで、DynamoDB のテーブル作成までできました。

ここからは実際に Post モデルの CRUD 操作ができるアプリケーションのひな形を作成していきます。

以下コマンドを実行しましょう。

```bash
$ jets generate scaffold Post title:string --no-migration

      invoke  active_record
      create    app/models/post.rb
      invoke  resource_route
       route    resources :posts
      invoke  scaffold_controller
      create    app/controllers/posts_controller.rb
      invoke    erb
      create      app/views/posts
      create      app/views/posts/index.html.erb
      create      app/views/posts/edit.html.erb
      create      app/views/posts/show.html.erb
      create      app/views/posts/new.html.erb
      create      app/views/posts/_form.html.erb
      invoke    helper
      create      app/helpers/posts_helper.rb
```

`jets generate scaffold` で、`rails generate scaffold` と同じような感覚で、アプリケーションのひな形を作成できます。

`posts` という DynamoDB のテーブルは事前に作ってあるので、モデル名は `Post` とし、`title` というカラムを一つ準備しました。

ただ、migration ファイルは事前に作ってあるので、RDB 用の migration ファイルは作成する必要がありません。そのため、オプションに `--no-migration` を追加しています。

### Dynomite 用に処理修正

DynamoDB 用に処理が動くよう、Dynomite の使い方に合わせて処理を修正します。

ファイル修正内容のまとめは以下コミットログをご参照ください。

[update processes for dynomite · gotchane/jets-dynamodb-app-sandbox@08bd12f · GitHub](https://github.com/gotchane/jets-dynamodb-app-sandbox/commit/08bd12ffbb4ef056761157a1f17ef34da3c56191)

また、Dynomyte のメソッドについては、[Dynomite の README](https://github.com/tongueroo/dynomite) も併せてご参照ください。


#### Post モデル修正

```ruby:app/models/post.rb
class Post < Dynomite::Item
  include ActiveModel::Validations

  column :id, :title
  validates :title, presence: true
end
```

##### 修正内容

* `Post` モデルの継承元クラスを、`ApplicationRecord`から、`Dynomite::Item` に
* `column` メソッドで `id`、`title` への getter/setter メソッドを準備
* `ActiveModel::Validations` を `include` し、バリデーションを可能に


### Post コントローラ 修正

```ruby:app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :delete]

  # GET /posts
  def index
    @posts = Post.scan
  end

  # GET /posts/1
  def show
  end

  # GET /posts/new
  def new
    @post = Post.new
  end

  # GET /posts/1/edit
  def edit
  end

  # POST /posts
  def create
    @post = Post.new(post_params)

    if @post.replace
      if request.xhr?
        render json: {success: true, location: url_for("/posts/#{@post.id}")}
      else
        redirect_to "/posts/#{@post.id}"
      end
    else
      render :new
    end
  end

  # PUT /posts/1
  def update
    if @post.replace(post_params)
      if request.xhr?
        render json: {success: true, location: url_for("/posts/#{@post.id}")}
      else
        redirect_to "/posts/#{@post.id}"
      end
    else
      render :edit
    end
  end

  # DELETE /posts/1
  def delete
    Post.delete(@post.id)
    if request.xhr?
      render json: {success: true}
    else
      redirect_to "/posts"
    end
  end

private
  # Use callbacks to share common setup or constraints between actions.
  def set_post
    @post = Post.find(params[:id])
  end

  def post_params
    params.require(:post).permit(:title).to_h
  end
end
```

##### 修正内容

* `index` 内のメソッドを `Post.all` から、`Post.scan` (DynamoDB テーブル全検索メソッド) に
* `create` 内のメソッドを `@post.create` から `@post.replace` に
* `update` 内のメソッドを `@post.update` から `@post.replace` に
* `destroy` 内のメソッドを `@post.destroy` から `Post.delete` に
* `post_params` の戻り値のクラスを `to_h` で `Hash` に (`ActionController::Parameters` クラスのオブジェクトに対応していないため)

### 動作確認

ここまで修正できたら、動作するはずです。以下コマンドを実行し、アプリケーションを起動します。

```bash
$ webpack -w
$ jets server
```

##### index

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/9b6dedca-9c0b-21ee-a5a5-e5b178843dbc.png)

##### new

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/83517407-c00e-3a73-5839-4f2981c5c40b.png)

##### show

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/19a06a1a-4c07-8f2d-e55c-108660d2febd.png)

##### edit

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/14a1a9c9-b599-e68d-168c-f375e2191703.png)

以上で検証は終わりです！

## おわりに

この検証で、 `scaffold` といった、`rails` でお馴染みの機能を使いつつ、DynamoDB をテーブルにしたアプリケーションが作れそうだなと思いました。

ただ検証していて、`scaffold` で `Dynomite` のマイグレーションファイル作ってくれないかなーといったことも感じましたが、これからもっと便利になっていくのではないかと思います。

## 参考

* [Middleware - Jets Ruby Serverless Framework](http://rubyonjets.com/docs/middleware/)
* [The Rails Command Line — Ruby on Rails Guides](https://guides.rubyonrails.org/command_line.html#rails-generate)