7839

雑草魂エンジニアブログ

【Nuxt】Excelファイルを読み込む(vue-js-xlsx)

Excelファイルのデータをアップロードして、DBに登録する処理をしたいと思い、Nuxt.jsでExcelファイルを読み込むことができるようにしたので、備忘録として残しておく。

  • Nuxt.js:v2.15.8
  • Nuxt Buefy:v0.4.7
  • vue-js-xlsx:v0.0.2

vue-js-xlsx

今回は、Excelの表形式での表示や編集などは考えておらず、Excelファイルからデータを読み取ることができれば機能としては十分であったので、vue-js-xlsxを使うことにした。

以下の vue-xlsx はコンポーネントまであってとても使いやすそうではあったが、今回の使用においてはコンポーネントまでは不要であったので選択肢から除外した。(もし複数のシートからいずれかを選択するなどの処理が必要な場合は、vue-xlsxをオススメする。)

インストール

まずは、ライブラリをインストールする。

// npm
npm install --save vue-js-xlsx
// yarn
yarn add vue-js-xlsx

Nuxt.js(Vue.js)で使うことができるように設定する。
plugins/vue-js-xlsx.jsファイルを作成し、nuxt.config.js設定ファイルで読み込ませる。そうすることで、this.$xlsxでどこからでも読み出すことができる。

import Vue from 'vue'
import VueXlsx from 'vue-js-xlsx'
Vue.use(VueXlsx)
export default {
  ...
  /*
  ** Plugins to load before mounting the App
  */
  plugins: [{
    src: '@/plugins/vue-js-xlsx.js',
    ssr: false
  }]
  ...
}

Excelデータをjsonデータに変換する

このライブラリ唯一の以下のメソッドを用いる。

const jsonData = this.$xlsx.toJson(data, options)
  • data[Blob]:Excelファイル
  • options[Object]:設定
    • parsingOpts[Object]:読み取り時の設定
    • sheetIndex[Number]:出力したいシート番号を指定する

実装例

UIライブラリに関しては、Nuxt Buefyを採用した。 実装例では、Excelファイルを読み込み、console.logで出力して確認するまでになっている。

<template>
  <section class="section">
    <div class="box">
      <b-field label="ファイル(.xlsx)を選択してください">
        <b-upload
          v-show="!dropFile"
          v-model="dropFile"
          expanded
          drag-drop
          @input="arrangeData">
          <section class="section">
            <div class="content has-text-centered">
              <p>
                <b-icon
                  icon="upload"
                  size="is-large">
                </b-icon>
              </p>
              <p>Drop your files here or click to upload</p>
            </div>
          </section>
        </b-upload>
      </b-field>
      <div class="tags">
        <span class="tag is-info" v-if="!!dropFile" >
          {{dropFile.name}}
          <button
            class="delete is-small"
            type="button"
            @click="deleteDropFile">
          </button>
        </span>
      </div>
    </div>
  </section>
</template>

<script>
export default {
  data() {
    return {
      dropFile: null,
    }
  },
  methods: {
    deleteDropFile() {
      this.dropFile = null
    },
    arrangeData() {
      const reader = new FileReader()
      const load = () => {
        const jsonData = this.$xlsx.toJson(reader.result, {
          parsingOpts: {
            type: 'array'
          },
          sheetIndex: 1
        })
        console.log(jsonData)
        // jsonデータを必要な形に整形する
      }
      reader.onload = load
      reader.readAsArrayBuffer(this.dropFile)
    }
  }
}
</script>

(おまけ)VueでのCSVデータの読み込み

CSVデータを読み込む場合は、JSのライブラリのみで実装することができる。(今回、機能拡張をする前までは、CSVのみでの読み取りを可能としていた。)上記と異なるのは、以下のarrangeDataのmethodの部分のみである。

<script>
arrangeData() {
  const reader = new FileReader()
  const load = () => {
    const lines = reader.result.split('\n')
    console.log(lines)
    // 文字列データを必要な形に整形する
  }
  reader.onload = load
  reader.readAsText(this.dropFile)
}
</script>

まとめ

これまでは、Excelデータを一度CSVデータに変換してから読み込みをさせていたので、直接Excelデータをアップロードすることができ、作業効率は格段に向上した。 vue-js-xlsxのライブラリに感謝しかない。本当に必要最低限のみのメソッドではあるが、お陰様で無事に実装できて一安心である。

【Ruby】cairoで画像作成を実装する(画像にスタンプと文字を追加する)

Railsアプリケーションにおいて、バックエンド側で以下のような画像作成をしたいと思い、サンプルアプリケーションを作ってみたので備忘録として残しておく。

ラジオ体操のスタンプみたいw

Ruby/Railsでどのライブラリを使うか

画像編集をしたいと思った場合、以下のライブラリが見つかった。

画像編集ソフトであるImageMagickは本当に色々な編集が可能である。今回の既存の画像にスタンプを追加するぐらいであれば、ベクター画像処理で十分であると判断し、rcairoを選定することとした。

cairoとは

cairo は 2 次元ベクター画像を描画するためのライブラリである。

cairographics.org

使い方としては、SVG(Scalable Vector Graphics)によく似ている感覚であった。(ベクター形式なので当たり前かw)

Docker設定

今回、Rails環境をDocker(Alpine Linux)で構築していたため、cairoをDocker imageにインストールすることにした。

cairo-devが用意されていたので、その他必要なパッケージも含め以下の通りインストールを行なった。

RUN apk add --update --no-cache \
  make \
  g++ \
  jpeg-dev \
  cairo-dev \
  giflib-dev \
  pango-dev \
  libtool \
  autoconf \
  automake \
  font-noto-cjk

文字の描画で日本語フォントが必要だったため、「font-noto-cjk」を追加した。

Rails設定

Ruby で cairo を使うには rcairo を使う必要がある。rcairo は Ruby と cairo のインターフェースである。

gem 'cairo'

Gemfileに上記を追記して、bundle installを行う。

今回、画像作成処理に関してはRakeタスクで実行することを想定した。

$ bundle exec rails g task calender_image

lib/tasks配下にcalender_image.rakeというファイルが生成される。

画像作成(描画処理)

スタンプを追加する下地の画像は一時的に以下のディレクトリに入れておいた。

lib/
└── tasks/
    ├── calender_image.rake
    └── img/
        └── calender_Aug.png

実際に作成したコードは以下の通りである。今回は、カレンダー画像の8/1の部分に星形のスタンプを追加し、カレンダーの下部にメッセージを追加した。

namespace :calender_image do

  desc "create calender image"
  task :create do
    open("#{Rails.root}/lib/tasks/img/calender-add-stamp.png", 'wb') do |output|
      open("#{Rails.root}/lib/tasks/img/calender_Aug.png", 'rb') do |input|
        new_image = draw_image(input, "継続は力なり。今年の夏も成長できる、ステキな夏になりますように。")
        output.write(new_image.read)
      end
    end
  end

end

def draw_image(io, message)

  surface = Cairo::ImageSurface.from_png(io)
  context = Cairo::Context.new(surface)

  # 星の座標
  star_left_top_x = 187
  star_left_top_y = 257

  context.set_source_color(Cairo::Color.parse("#FFC700")) # 星の色設定
  context.set_line_width(5) # 星の線幅設定

  star_points = [
    [ 54.32, 0 ],
    [ 71.09, 34 ],
    [ 108.63, 39.46 ],
    [ 81.47, 65.92 ],
    [ 87.89, 103.3 ],
    [ 54.32, 85.67 ],
    [ 20.74, 103.32 ],
    [ 27.16, 65.94 ],
    [ 0, 39.46 ],
    [ 37.54, 34 ],
    [ 54.32, 0 ]
  ]

  # 星の描画処理
  context.move_to(star_left_top_x + star_points[0][0], star_left_top_y)
  for i in 0..10 do
    context.line_to(star_points[i][0] + star_left_top_x, star_points[i][1] + star_left_top_y)
  end

  context.stroke

  context.set_source_color(Cairo::Color.parse("#000000"))
  context.select_font_face("Noto Sans CJK JP", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL)
  context.set_font_size(26)
  context.move_to(40,810)
  context.show_text("今日の一言")
  context.select_font_face("Noto Sans CJK JP", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD)
  context.move_to(70,860)
  context.show_text("#{message}")

  io = StringIO.new
  surface.write_to_png(io)
  io.pos = 0
  io
end

以下のコマンドを実行することで、Rakeタスクを動かすことができ、実際に画像作成を行うことができる。

$ bundle exec  rails calender_image:create 

rcairoの各種メソッドは、rcairoのGitHubのDocを参考にしてほしい。(日本語の説明もあって感謝しかない。)

画像描画の流れは以下の通りである。

  1. サーフェス (Cairo::Surface) を作成する
  2. 作成したサーフェス用のコンテキスト (Cairo::Context) を作成する
  3. コンテキストに対して描画処理を行う
    • 星の描画
    • 文字の追加

(注意事項)

  • 座標系は、左上を(0,0)とする
  • コンテキストについても、座標系は左上が原点(0,0)となる

まとめ

cairoを使って画像作成処理を簡単に実装することができた。

あとは、スタンプの位置を日付ごとにどの位置とすべきか算出するロジックを追加することで、任意の日付にスタンプを追加することができる予定である。

参考資料

「アジャイルサムライー達人開発者への道」を読んで

新しい開発チームに変わり、会社の大先輩のシステム開発手法のバイブルになっているとオススメされた本書。アジャイル開発の言葉や定義は理解していたものの、本質をあまり理解できていなかったので、お盆休みに手に取って読んでみたので感想も含めて備忘録として残しておく。

要約

アジャイル開発で期待される成果を出すための秘訣として、方法論やツールを導入するだけではなく、考え方の規範となる「Manifesto for Agile Software Development(アジャイルソフトウェア開発宣言)」のマインドセットや原則を網羅的にわかりやすく説明してくれる。アジャイル開発は万能な方法論ではないが、マインドセットを胸に顧客を含めたONE TEAMでソフトウェア開発に情熱を注ぐことが大切と諭してくれる。

Manifesto for Agile Software Development(アジャイルソフトウェア開発宣言)

引用元:アジャイルソフトウェア開発宣言

左記に価値があることを認めながらも、もっと右記に焦点を当てる。とても大事な考え方だと思えた。左記はもちろん欠かせないし、エンジニアが自ら焦点を当てる部分であろう。だからこそ、右記にこそ焦点を当てるべきであるというのはすごく納得できた。個人的に咀嚼して、以下に焦点を当てていきたいと思う。

  • コミュニケーションによる相互理解でONE TEAMに。
  • 技術の進歩やサーバレス環境などPoC等での実証実験が手軽になった現代。ドキュメント上での仮説検証ではなく、実社会での実証実験によるフィードバックでPDCAを回す。
  • 顧客と一緒に最高のソフトウェア開発ができるよう、Win-Winな関係構築を。
  • 技術や市場の変化は脅威的な速さである、変化を受け入れ、より良い成果を掴み取れ。

期待をマネジメントせよ

ソフトウェア開発プロジェクトでは、チーム内外にさまざまな期待が存在する。しかも、そうした期待には明示的な期待と暗示的な期待がある。 ー p6 ー

プロジェクトマネージャー(PM)の仕事はまさに「各方面の期待をマネジメントすること」であると最近思うようになった。明示的な期待は調整がしやすいものの、暗示的な期待が本当に難しい。暗示的な期待を理解するためには、本気で相手の立場に自らが立つ必要がある。客観的に俯瞰することも大事であるが、本当に自分が相手の立場であれば、どうしたいのか、どうすべきなのか考えを巡らせる必要がある。そうでもしない限り、真の意味での暗示的な期待に応えることはできないと思う。反対にそうすることで初めて信頼貯金を増やすことができ、顧客とも信頼関係が構築できるものだと思う。

本書では、現場現物主義や、顧客からの仕様変更への対応スタンス、チームビルディング手法など、様々な手法を提示してくれた。

3つの真実を受け入れよ

3つの真実

  1. プロジェクトの開始時点に全ての要求を集めることはできない
    • 要求とは発見されるものである。
  2. 集めたところで、要求はどれも必ずといっていいほど変わる
    • 変化は起きるもの。起きた変化を受け入れること。
  3. やるべきことはいつでも、与えられた時間と資金よりも多い
    • やれることをやるだけ。優先順位を付け、高いものからこなしていく。 ー p13 ー

要求とは、人の中にある、目標達成や問題解決をするためにありたい姿であり、主観的で曖昧なものである。曖昧なために、正しく伝わりにくく、時と場合により変化しやすい。そのため、正確な記述が困難であり、抜け漏れが発生し、管理がしにくいものである。

引用元:ニコニコ大百科 顧客が本当に必要だったもの

これは以前一時期話題になった、ITビジネスにおける多難なシステム開発プロジェクトの姿を風刺した絵である。

要約すると、顧客が期待した通りのシステムが完成しなかった原因は、開発側の勝手な思い込みや都合の押し付けだではなく、そもそも「最初に顧客が説明した要件からしてズレていた」、ということを表している。

そのため、上記に戻るが、期待をマネジメントしながら、要求という主観的な曖昧なものを受け入れ、咀嚼して、要件という客観的に落とし込む必要がある。それには顧客と開発側の双方の歩み寄りが不可欠である。

そして、要求は変わりやすいものである。実際にやってみないとわからないは大いにある。わからないからこそ、やってみるという部分もある。だからこそ、やってみてダメであれば、即座に方向転換し、プロジェクトを進むべき方向に舵取りできるチームでありたい。

要求は膨らみやすく、あれも、これもと追加したくなる。ただし、時間も予算も有限のものである。だからこそ、きちんと本質を見極めて、優先順位をつける必要がある。焦る必要はない。ただし、本当に解決したい問題は何か、その問題の本質を見誤らないように、刻一刻と変わる現在の状況を直視しつつ、判断をする必要がある。

選択と集中」で的を絞る

ソフトウェアの64%の機能は、ほとんどあるいは全く使われていない。(システムの価値の80%がシステムの機能の20%からもたらされている。)このことからも、まずは本当に重要なことだけに集中すべきである。 ー p158 ー

ソフトウェア開発において、つい便利にしたくて、様々な機能を実装したくなってしまう。「この機能あったら便利だよね」と。ただし、それは本当に必要か?と今一度考える必要がある。実際に64%の機能は、殆どあるいは全く使われていないという事実をしっかりと心に留めておけば、余計な機能ではなく、システムの価値に注力できるはずである。

様々なシステムで溢れている現代。他社と比較して、あれもこれもと機能として「あるべきである」という固定概念もあると思う。だからこそ、本当に必要な機能か、このシステムに不可欠であるかは、今一度立ち返る必要があるように思えた。

感想・まとめ

タイトルである「アジャイルサムライ」って何?正直、最初は半信半疑で本書を手に取った。ただ、実際に読んでみて非常にステキな本であった。

アジャイルイテレーション、TDDなどキーワードのみで理解をしていた自分が浅はかだった。本書を読むことで、改めてアジャイルの素晴らしさを知ることができた。顧客と深い関わりを持ち、顧客の期待をマネジメントしながら、ONE TEAMで大きな成果をあげていけるように、これからも精進していきたい。

先輩がバイブルであるという意味が理解できたし、オススメしてくれた先輩に感謝の意を述べたい。

この本は数ヶ月後に、また読み直してみたいと思えた。きっとまた新しい発見があると思う。そして、要求を要件に落とし込む手法、期待のマネジメント方法などに関して、他にも書籍を読んでみたいと思えた。何かオススメの本があれば教えてもえると大変嬉しい。

書籍情報

Macのターミナル設定「Starship」導入

Macのセットアップの際に、ターミナルが初期設定のままでは味気ないので、設定を変更したので備忘録として残しておく。

ターミナルではなく、iTerm2を使う選択肢もあったが、コーディングの際にはVSCodeを使って、VSCode内のTerminalを開いて使うことが多く、ターミナルで十分と判断した。

bash or zsh?

MacOS X 「Catalina」から標準のシェルが「zsh」に変更された。

そのため、「Catalina」以上にアップデートしてbashのままで使っていると、以下のメッセージが表示されていた。

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.

zshbashよりも圧倒的にカスタマイズができる点とプラグインの豊富さがメリットであるが、使いこなせないので、Linuxと同様のbashのままで使うようにしている。

そのため、デフォルトをbashに変更。

chsh -s /bin/bash

bashの設定ファイル

bashには以下の3つの設定ファイルがある。

  • .profile
    • General setups for login
    • ログイン設定
  • .bash_profile
    • bash-specific setups for login
    • bash 固有のログイン設定
    • bash起動時に.profileは読み込まれなくなるので、.profileを読み込んでおく
    • ログイン時には.bashrcは読み込まれないので、.bashrcを読み込んでおく
  • .shrc
    • bash-specific setups for interactive shell
    • 対話シェル設定
    • 関数やaliasを記載
  • .bashrc
    • bash-specific setups for interactive shell
    • bash 用の対話シェル設定
    • bash 固有の関数やaliasを記載

そのため、.bash_profileには以下の設定を書き込んでおく。

# .bash_profile
# Get general setups
if [ -f ~/.profile ]; then
    . ~/.profile
fi

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

テーマを「iceberg」に変更

配色が淡い感じになって好きなので、「iceberg」を使っている。

Iceberg - dark blue color scheme for Vim / Neovim

設定方法

  1. 上記のページの一番下にある「Download」ボタンからファイルをダウンロードする
  2. Macのターミナルの「環境設定」を開く
  3. 「プロファイル」のタブを選択する
  4. 左下のメニューから「読み込む...」を選択し、ダウンロードしたファイルを読み込ませる
  5. 「Iceberg」のテーマが表示されるので、選択して「デフォルト」ボタンを押す

以上でテーマの設定が完了。

プロンプトを「Starship」に変更

プロンプトは「Starship」を使うことにした。

  • アイコンが可愛いw
  • 互換性優先でどのOSでも使用可能
  • Rust製で高速&信頼性が高い
  • 自由にカスタマイズ可能

インストール

brewで簡単にインストール可能。

brew install starship

初期設定

bashで使えるように、設定情報を書き込む。

echo 'eval "$(starship init bash)"' >> ~/.bashrc
source ~/.bashrc

NerdFontフォントのインストール

macOS (OS X) では Homebrew Cask Fonts を通じて全てのフォントが利用できる。

Nerd Fontの中でも、公式の例として使われていたのは「FiraCode Nerd Font」であった。その他では、「Hack Nerd Font」が有名のようであったので、Hackの方を採用した。

brew tap homebrew/cask-fonts
brew install --cask font-hack-nerd-font 
# or 
# brew install --cask font-fira-code-nerd-font 

インストール後、ターミナルの環境設定 > プロファイル から、フォントを「Hack Regular Nerd Font Complete 11(Hack Nerd Font)」に変更して、設定完了である。

カスタマイズ

mkdir -p ~/.config && touch ~/.config/starship.toml

上記で設定ファイルを作成し、少しカスタマイズをしてみた。

add_newline = true

[character]
success_symbol = "[>](bold green)"
error_symbol = "[✗](bold red)"

[username]
style_user = "white bold"
style_root = "black bold"
format = "user: [$user]($style) "
disabled = false
show_always = true

[directory]
truncation_length = 10
truncate_to_repo = false

[memory_usage]
disabled = false
threshold = -1
style = "bold dimmed green"
format = "via [${ram_pct} / ${swap_pct}]($style) "

[package]
disabled = true

[time]
disabled = false
format = '[\[ $time \]]($style) '

まとめ

ターミナルがカラフルになり、断然見やすくなりました!
使い勝手を確認しながら、引き続きカスタマイズしていきたい。

【Docker】node_module の volume 設定に関して(Rails / ruby,node環境)

RailsのDocker開発環境を構築している際に、node_module の volume 設定が上手くいかず、改めて Docker について調べた内容を備忘録として残しておく。

結論:Dockerfile, docker-compose.ymlの設定

まずは、結論から示す。Railsの開発環境として、最終的に以下の設定を行なった。

# ディレクトリ構成
.
├── rails-app/
├── Dockerfile
└── docker-compose.yml
FROM ruby:3.1.2-alpine3.16

RUN apk update && apk upgrade \
  && apk add --no-cache build-base \
  libxml2-dev libxslt-dev \
  mysql-client mysql-dev \
  git bash less curl

RUN curl -fsSL https://deb.nodesource.com/setup_16.16 | bash - && \
  apk --no-cache add nodejs npm && \
  npm install --global yarn

RUN apk --no-cache add tzdata && \
  cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
  apk del tzdata

ENV APP_ROOT /app
RUN mkdir $APP_ROOT
WORKDIR $APP_ROOT

COPY ./rails-app/Gemfile $APP_ROOT/
COPY ./rails-app/Gemfile.lock $APP_ROOT/
RUN bundle config set --local path vendor/bundle
RUN bundle config set force_ruby_platform true && \
  bundle install -j4

COPY ./rails-app/package.json $APP_ROOT/
COPY ./rails-app/yarn.lock $APP_ROOT/
RUN yarn install
version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    image: ruby-node
    volumes:
      - node_modules:/app/node_modules
      - gem_data:/usr/local/bundle
      - ./rails-app:/app:cached
    ports:
      - 3000:3000
    depends_on:
      - db
    stdin_open: true
    tty: true

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      TZ: Asia/Tokyo
    ports:
      - 3306:3306
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  node_modules:
    driver_opts:
      type: none
      device: ${PWD}/rails-app/node_modules
      o: bind
  gem_data:
  mysql_data:

コンテナ内のnode_modulesが消える

最初に発生した事象は「コンテナ内のnode_modulesが消える」である。

まず、Dockerfileをビルドして、imageを作成する。--no-cacheをつけておくことで、キャッシュを使用せず、初めからビルドを実行することができる。

docker compose build --no-cache

Dockerfileで作成したimage 「ruby-node」からコンテナを起動して、node_moduleがインストールされているかを確認する。

# docker run -it ruby-node:latest bash
bash-5.1# ls
Gemfile  Gemfile.lock  node_modules  package.json  yarn.lock
bash-5.1# cd node_modules/
bash-5.1# ls
@ampproject                       buffer-from                       datatables.net-rowgroup-bs4       ev-emitter                        is-arguments                      object-inspect                    shallow-copy
@babel                            call-bind                         datatables.net-rowreorder         eve-raphael                       is-core-module                    object-is                         source-map
~途中省略~
buffer-equal                      datatables.net-rowgroup           esutils                           ion-rangeslider                   node-releases                     setimmediate
bash-5.1# exit

node_moduleはインストールされており、imageに問題がないことを確認することができた。 ゆえに、docker-compose.ymlの設定に問題があることになる。この当時の設定ファイルは以下であった。

services:
  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    image: ruby-node
    volumes:
      - gem_data:/usr/local/bundle
      - ./rails-app:/app:cached
(以下、省略)

原因は、バインドマウントであった。./rails-app:/app:cachedの部分で、ホスト側の/rails-appをコンテナ側の/appにマウントしている。このバインドマウントでは、常にホスト側が優先されるため、ホスト側にnode_modulesが存在しない場合、コンテナ内のnode_modulesが削除されてしまう。バインドマウントの詳細は以下の公式ドキュメントを参照してほしい。

対策として、node_modulesをボリュームに保存するように、volumes- node_modules:/app/node_modulesを追加した。

services:
  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    image: ruby-node
    volumes:
      - node_modules:/app/node_modules
      - gem_data:/usr/local/bundle
      - ./rails-app:/app:cached
(途中、省略)

volumes:
  node_modules:
  gem_data:
  mysql_data:

ボリュームとは、簡単に言うとDocker上に保存されるストレージである。 Docker上の/var/lib/docker/volumes/<VOLUME-NAME>/_dataに、ボリュームに設定したデータが保存される。そのため、ホストからデータを参照することができない。以下で確認することができる。

# ボリューム一覧
docker volume ls
# ボリュームの詳細確認
docker volume inspect <VOLUME NAME>
# ボリューム削除
docker volume rm <VOLUME NAME>

詳細は公式ドキュメントを参照してほしい。

バインドマウントとボリュームを同時に設定した場合、ボリュームが優先される。

そのため、node_modulesに関しては、ホスト側の影響を受けることがなくなり、node_modulesが削除されません。 ただし、この方法では、node_modulesはバインドマウントから除外されることになり、ホスト側のnode_modulesが空になってしまう。

ホスト側のnode_modulesが空になる

上記に記載したが、バインドマウントとボリュームの優先順位の問題で、コンテナ側のnode_modulesは削除されることがなくなるものの、node_modulesがバインドマウントの対象から除外されることにより、ホスト側のnode_modulesが空になってしまうのである。

対応方法として、ボリュームに保存しているnode_modulesをホスト側にバインドする設定をdriver_optsで行う。

(上記、省略)
volumes:
  node_modules:
    driver_opts:
      type: none
      device: ${PWD}/rails-app/node_modules
      o: bind
  gem_data:
  mysql_data:

driver_optsでは、ボリュームが使うドライバに対して、オプションをキーバリューのペアで指定することができる。ドライバのデフォルトは、Docker Engineで使用するように設定されているドライバであり、多くの場合はlocalである。localドライバーは、Linuxmountコマンドと同様のオプションを受け付ける。(o: bindと設定している根拠はこれのようだ。詳細はこちらから確認してみて欲しい。)

このようにな設定で、Dockerの名前付きボリュームをホスト側の任意のファイルパスに作成することができるようだ。

参照元

コンテナ起動時に、ホスト側にnode_modulesをマウントするので、少し時間がかかったが、無事にホスト側でnode_modulesの中身を確認することができた。

ただし、ホスト側にnode_modulesのフォルダがない場合、エラーになるのでコンテナ起動前にフォルダを作成しておく必要がある。 (.keepを入れてフォルダのみを作成しておこうとしたが、その場合にはホスト側のnode_modulesでコンテナ側を上書きしてしまうことになったので、ダメだった。node_modulesのフォルダ作成はマニュアルで対応することにした。)

まとめ

Docker について調べて改めて理解が深まった気がした。
無事にnode_modulesの最適な構築方法を模索できてよかった。

gem_dataに関しても、通常bundleの設定を変更しない場合、/usr/local/bundleにgemがダウンロードされる。しかしながら、/usr配下はDockerのLinuxマシンを参照するので、他のプロジェクトと競合する可能性もあるので、RUN bundle config set --local path vendor/bundleでコンテナ内部のパスにしておくほうがいいと思えた。