RailsとVue.jsを使ってプレビュー付きの画像登録機能を作ってみる

複数の画像登録機能(プレビュー機能付き)の作成手順を記載してみる。

###環境
Mac　High Sierra
ruby 2.5.1
Rails 5.2.1

#実装したこと
複数画像をアップして表示する際の手順を記載してみる。
・index画面に画像一覧を表示させる
・new画面を用意する
・new画面でファイル選択した際の画像をプレビュー表示する
**バリデーションやエラーチェックなどはあまり気にせず作成**
**最低限の機能ってことで、一覧、新規追加、削除しか搭載しない**

前の記事をそれぞれ参考にして作成
[RailsとVue.jsを使った簡単な項目追加機能](https://qiita.com/me-me-me/items/964efa5044a8b76108d9)
[ActiveStorageを使った複数画像管理](https://qiita.com/me-me-me/items/3b259bbc70f62898bd6f)

#1.rails new
rails newに--webpack=vueオプションを付けて、プロジェクトの作成時にVue.jsもインストールさせる

```bash:console
rails new image_sample --webpack=vue
```

記事作成時はsqlite3でエラーが出た

```bash
Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile.
```

ので、Gemfileの編集をする

```Gemfile:Gemfile
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.3.6'
```

#2.active_storageインストール、マイグレーション

```bash:console
cd image_sample
rails active_storage:install
rails db:migrate
```

[マイグレーション結果]
active_storage_blobs,active_storage_attachmentsテーブルが作られる

```
== 2019030XXXXXXX CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs)
   -> 0.0027s
-- create_table(:active_storage_attachments)
   -> 0.0124s
== 2019030XXXXXXX CreateActiveStorageTables: migrated (0.0153s) ===============
```

#3.モデル作成、画像関連づけ

```bash:console
rails g model item name:string
rake db:migrate
```

```ruby:app/models/item.rb
class Item < ApplicationRecord
  has_many_attached :images
end
```
#4.コントローラー作成

```bash:console
rails g controller items
```

#5.ルーティング設定

```ruby:config/routes.rb
Rails.application.routes.draw do
  resources :items
end
```

#6.コントローラー設定

```ruby:app/controller/items_controller.rb
class ItemsController < ApplicationController
  # item一覧
  def index
    @items = Item.all
  end

  # item新規作成
  def new
    @item = Item.new
  end

  # item作成
  def create
    @item = Item.new(item_params)

    if @item.save
      redirect_to items_path
    else
      render :new
    end
  end

  # item削除
  def destroy
    @item = Item.find(params[:id])
    @item.destroy
    redirect_to items_path
  end

  private

  def item_params
    params.require(:item).permit(:name, images: [])
  end
end
```


#7.ビューの設定
一覧と新規作成画面を用意する。

### 一覧画面
```html:app/views/items/index.html.erb
<h1>Items Index</h1>
<%= link_to 'Add item', new_item_path %>

<ul>
  <% @items.each do |item| %>
    <li>
      <p><%= item.name %></p>
      <p>
        <% item.images.each do |image| %>
          <%= image_tag(image, width:100) %>
        <% end %>
      </p>
      <p>
        <%= button_to '削除', item, method: :delete, data: { confirm: 'Are you sure?' } %>
      </p>
    </li>
  <% end %>
</ul>
```
画像のサイズは一旦width:100で設定


### 新規作成画面
```html:app/views/items/new.html.erb
<h1>Add Item</h1>

<%= form_with model: @item do |form| %>
  <div>
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>
  <div>
    <div id="image-app">
      <%= form.file_field :images , class: "hidden" , "v-on:change": "onFileChange($event)", multiple: true %>
      <ul v-if="images.length > 0">
        <li v-for="(image, index) in images">
          <button type="button" @click="removeThumbnail(image)">削除</button>
          <img :src="image.thumbnail.url" width="150" height="150"/>
        </li>
      </ul>
    </div>
    <%= javascript_pack_tag 'image' %>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>
```


#8.Vueの処理設定
###構成
app/javascript配下に以下を設定

```
javascript
 |_packs
　 　|_image.js
```

```javascript:packs/image.js
import Vue from 'vue'

new Vue({
  el: '#image-app',
  data: function() {
    return {
      images: [],
    }
  },
  methods: {
    //ファイル選択後イベント
    onFileChange: function(ev) {
      let files = ev.target.files || ev.dataTransfer.files;
      let length = files.length;

      if ( ! length) return;

      for (var i=0; i<length; i++) {
        this.viewThumbnail(files[i]);
      }
    },
    // サムネイルを表示
    viewThumbnail: function(file) {
      var vm = this;
      var reader = new FileReader();

      reader.onload = function(ev){
        var image = {
          paths: [],
          thumbnail: {
            url: ev.target.result
          },
        };
        vm.images.push(image);
      };
      reader.readAsDataURL(file);
    },
    // サムネイルを削除
    removeThumbnail: function(image){
      this.images.splice(this.images.indexOf(image), 1);
    },
  },
})
```

#9.webpackのBuildをする

```bash:console
bin/webpack-dev-server 
```

#9.ローカルサーバー起動

```bash:console
rails s
```

###9-1. http://localhost:3000/images  にアクセス
<img width="350" alt="スクリーンショット 2019-03-28 7.52.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/248644/b95a7a6f-4faf-b1f1-82c8-431478e9153d.png">

###9-2.item新規作成で画像を選択
<img width="350" alt="スクリーンショット 2019-03-28 8.16.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/248644/e30e4708-522d-53ed-98a5-5e74aaf2d93a.png">

###9-3.Create_itemして一覧に遷移した後
<img width="350" alt="スクリーンショット 2019-03-28 8.17.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/248644/603f6517-603a-72b6-c599-586acea795c6.png">


前の記事のコピーに近くなったけど、プレビュー表示できたのでよかった。

# その他
## item削除でdatabase lockエラー
<img width="869" alt="スクリーンショット 2019-03-28 8.31.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/248644/443df4c4-4789-bc40-0a1e-283f5fe7e8a6.png">

SQLite3のタイムアウトを変更する

```config/database.yml
：
：
development:
  <<: *default
  database: db/development.sqlite3
  # 追加
  timeout: 1000
：
：
```