Turbolinks、時々Vue.js

こんにちは、@mugi_unoです。

今年は雪がスゴいですね。
暖かい我が家でリモートワークのありたがみを日々噛み締めています。

先日は雪だるまを作りましたが、想像とは違う仕上がりになりました。

娘は「これじゃない!」と不満そうでした。

f:id:mugi1:20180129173108p:plain

新しいRails&フロントエンドの環境構築

さて、最近Misocaでは、新規Railsアプリケーションのフロントエンド環境を整える機会がありました。

チームで検討した結果、Turbolinks&Vue.jsを採用しており、
その際に得られた知見を紹介したいと思います。

Turbolinksを使う理由

TurbolinksはGoogleで検索するとサジェストがこんな感じになるほど無効化されがちです。

f:id:mugi1:20180129173204p:plain

  • 利用するために理解すべき独自の挙動がある
  • ゴリゴリにフロントエンドを触っている人からすると
    「そこまでやらないで〜〜!」といった気持ちになる

といった具合に避けられがちだと予想していますが、上手く扱うと非同期通信をベースとした軽快に動くUXをお手軽に実現できます。

同様のものをSPAで構築するには、 フロントエンド周りで多くの知識が必要となります。

フロントエンドエンジニアが中心となってメンテナンスをしていけるのであれば問題ありませんが、サーバサイド/フロントエンドの双方を多くのエンジニアが面倒を見るような状況では、最低限のコード量で実現できることが大きいメリットになってきます。

Turbolinksで気をつけるべきこと

特に注意すべきなのが初期化周りです。

一般的にはDOMContentLoadedイベントやjQuery.readyなどで、適宜必要な初期化スクリプトを実行することが多いかと思いますが、Turbolinksの場合はbodyを差し替えるだけになるため、イベントが発火せずに上手く動作しなくなります。

画面遷移時に適宜documentに対してturbolinks:loadイベントが発火されるため、そちらをハンドリングしてあげることで上手く初期化できるようになります。

このあたりは、TurbolinksのREADMEにも説明があります。

https://github.com/turbolinks/turbolinks#full-list-of-events https://github.com/turbolinks/turbolinks#observing-navigation-events

画面ごとの初期化をどのようにするか?

実際には画面に応じて実行したい初期化処理が異なるケースが多く、都度適切なスクリプトを実行する必要があります。

今回は、RailsのController/Actionを元に、適宜必要な初期化スクリプトを判別してロードするような仕組みとしました。

実際のコード例は以下です。(要点のみ抜粋しています)

  • application.html.haml : bodyにControllerとActionを元にしたキーを埋め込む
%body{ 'data-js-initializer': "#{controller.controller_name}_#{controller.action_name}" }
  • application.js : headに埋め込み、ページ全体のロード時一度だけ実行する
import Turbolinks from 'turbolinks';
import initialize from './initializers';

Turbolinks.start();

// Turbolinksで遷移した場合の初期化処理
document.addEventListener('turbolinks:load', initialize);
  • initializers/index.js : 画面ごとの初期化処理を実行する
import * as initializers from './';
import camelcase from 'camelcase';

export default function () {
  // bodyに埋め込まれたキーを取得
  const initializerName = $('body').data('js-initializer');

  // 対応する初期化スクリプトを取得し、存在すれば実行する
  const initializeScript = initializers[camelcase(initializerName)];
  if (initializeScript) {
    initializeScript();
  }
}

これにより、たとえば OrdersController#editに遷移した場合には、
initializers/ordersEdit.jsがコールされるようになります。

#new, #edit などで同じスクリプトとなる場合は、 共通ファイルを1つ作成し、別名でエクスポートすることで対応しています。

export {
  ordersForm as ordersNew,
  ordersForm as ordersEdit,
} from './ordersForm';

Controller/Actionを元に初期化するのは好みが別れるところもありそうですが、

  • 初期化時にファイルをどこに置くか考えなくていい
  • 命名規則が強制的に揃う
  • どの画面でどの初期化スクリプトが実行されてるかすぐにわかる

といったメリットがあるため、今回はこのような形としました。

Vue.jsと組み合わせる

Vue.jsと一緒に利用する場合は、Turbolinksによる画面遷移時のキャッシュとの兼ね合いや、インスタンスの破棄を適切に行えるよう考慮してあげる必要があります。

このあたりについては、Turbolinksリポジトリwikiに説明と対処用のコード例が記載されており、そちらを参考にすることで実現できます。

VueJs and Turbolinks · turbolinks/turbolinks Wiki · GitHub

内容的には、TurbolinksAdapterというmixinを作り、turbolinks:visitのタイミングで1度だけイベントが発火するような仕組みのようです。

TurbolinksAdapter の適用し忘れが怖い

当然と言えば当然ですが、上記TurbolinksAdapterは Vue.jsに適用してあげないと正常に動作しません。つまり、TurbolinksAdapterを適用し忘れて、単純にVue.jsのみをimport/requireしてしまった場合には期待通りの動作とはなりません。

かといって、利用する箇所すべてで意識したくはないですよね。

ローカルパッケージ化しておく

というわけで、Vue.jsをローカルでパッケージ化し、単純にvueをimport/requireした場合にはTurbolinksAdapterが適用されたものが得られるようにしました。

このあたりは、以前Vue.jsのバージョンを0.12→2.4にアップグレードした際と同様の手法です。

tech.misoca.jp

  • 内部にパッケージを作ります。

frontend/private_modules/vue/package.json

{
  "name": "vue",
  "version": "1.0.0",
  "description": "Vue with turbolinks",
  "main": "index.js",
  "dependencies": {
    "vue": "^2.5.13"
  }
}

frontend/private_modules/vue/index.js

const Vue = require('vue').default;
// https://github.com/turbolinks/turbolinks/wiki/VueJs-and-Turbolinks
const TurbolinksAdapter = require('./TurbolinksAdapter');

Vue.use(TurbolinksAdapter);

module.exports = Vue;
  • アプリケーション側からのVue.jsの参照をローカルパッケージに向けます

package.json (抜粋)

{
  "dependencies": {
    "vue": "file:./frontend/private_modules/vue"
  }
}

これで、Turbolinksでの画面遷移時のVueインスタンスの処理を、開発時に特に意識せずに行えるようになりました。

Turbolinksの外し方

Turbolinksを使うのは少し冒険だな〜と感じる方もいるかもしれませんが、上記構成の良いところとして、「Turbolinksを外そうと思えば、わりと簡単に外せる」という側面があります。

「外すのかよ!」という声が聞こえてきそうですが、出来る限り依存を浅くしておくのはTurbolinksに限らず大事なことだと思ってます。

もし外したい場合には以下の手順を踏むだけです。

  • application.jsの初期化処理をDOMContentLoadedのバインドに変える
  • TurbolinksのimportとTurbolinks.start();を消す
  • package.json上のvueの参照をローカルパッケージから、通常のVue.jsに戻す

これで安心して使うことができます。


というわけで、TurbolinksとVue.jsを使った環境構築時の話でした。

興味が湧いた方は、一度試してみてはいかがでしょうか?


Misocaでは、いろんなフレームワークにチャレンジしたいエンジニアを募集中です!

デプロイ時に Bugsnag にソースマップをアップロードして人に優しい Stacktrace にする

こんにちは、 Misoca 開発の @lulu-ulul です。

ニュースを見ていると各地で例年以上の積雪を記録しているみたいですね。皆様も引き続き御気を付けください。

私もそれなりに雪が降る場所に住んでいて当然積雪しているのですが、今年は雪が積もっても早起きして車が出られるまで行う雪かき・倍以上になる通勤時間から解放されてほぼ普段通りの生活ができています。リモート勤務の良さを改めて実感しています。

背景

Misoca ではクラッシュレポートの蓄積・解析サービスとして Bugsnag を使っています。

www.bugsnag.com

Rails のエラーログだけでなく、JavaScript のエラーログも対象にしています。

しかし JavaScript ファイルは minify されていますので、 bugsnag の stacktrace はデフォルトではこんな感じになってしまいます。

f:id:lulu-ulul:20180126104231p:plain

つらい(つらい)。

解決方法

Bugsnag 側でソースマップをアップロードする機能が提供されています。

Bugsnag docs › API › JS source map upload

デプロイ時にソースマップファイルを自動的にアップロードさせれば幸せになれそうですね。

方針

Misoca では本番環境には map ファイルを出力しない様にしていました。

また、デプロイ処理は CodeBuild を利用しています。

それを踏まえると以下の様なステップで進める事になります。

  1. 本番環境でのビルド時にソースマップ(以下 map ファイル)を生成させる様にする
  2. CodeDeploy で実行させる以下の処理を行うスクリプトを実装する
    1. map ファイルのパス一覧を取得する
    2. map ファイルを Bugsnag にアップロードする
    3. map ファイルを削除する

1. 本番環境でのビルド時にソースマップを生成させる様にする

Misoca では現在 Webpacker で管理されているものと、 gulp で管理されているものの2つが共存しています。

Webpacker

Misoca では環境毎に分割しているので本番環境の設定ファイルを変更するだけです。

ソースマップの生成に関しては いくつか種類がありますが、今回は map ファイルを別ファイルとして生成したいので source-map を指定する様にしました。

gulp

gulp の方は Browserify のファイルストリームを vinyl オブジェクトにして扱っています。 構成は少し違うのですが、 gulp が提供している recipe があるので参考にして設定します。 ソースマップ生成にあたってストリームを一度バッファに貯める必要がある様でなるほど!という感じです。

gulp/browserify-uglify-sourcemap.md at master · gulpjs/gulp · GitHub

実際は Babel 使う様にしたり条件に応じて watchify で wrap したりと色々いい感じにしてるのですが、煩雑になるので省くと以下の様な形になります。

var gulp = require('gulp');
var util = require('gulp-util');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var sourcemaps = require('gulp-sourcemaps');

function build_js(development) { // devlopment でどの環境かを管理
  var bundler = browserify({
    // options
  });; 

  return bundler
    .bundle()
    .pipe( source( 'main.js' )) // vinyl のオブジェクトに変換
    .pipe(development ? util.noop() : buffer()) // buffer を使う
    .pipe(development ? util.noop() : sourcemaps.init())
    .pipe(development ? util.noop() : sourcemaps.write('./'))
    .pipe( gulp.dest( '../public/js/' ));
}

gulp.task('build:js', function() {
  return build_js(false);
});
 
gulp.task('build:js:dev', function() {
  return build_js(true);
});

gulp-utilnoop() を使うと「何もしない」という処理で chain できるよ、と教えてもらったのですっきり書けました。

2. CodeDeploy で実行させるスクリプトを実装する

EC2インスタンスで実行させるための準備がほぼ不要ですし、既存のスクリプトと統一したかったのでシェルスクリプトで記述する事にしました。

ここで気をつけたのは以下の点です。

  • ソースマップのアップロードで失敗してもデプロイ自体は失敗させない
    • AppSpec 側の設定では難しそうなので、シェルスクリプト側で例外処理を行い失敗時に非常終了のシグナルを外に投げない様にします
  • 上記の場合に map ファイルを残したままアプリケーションを立ち上げない様にする
    • 例外処理の周りで必ず削除される様にする
  • CodeDeploy で複数ノードを立ち上げるがその内の一つでのみ実行する様にする
    • EC2 のインスタンスのタグを使い特定のノードのみでアップロード処理が行われる様にする

以上を踏まえて作成したシェルスクリプトは以下のようになります。マスクしたり削ったりしている場所もあります。

#!/bin/bash

source ~/.bash_profile
export AWS_CONFIG_FILE=AWS_CONFIG_FILE_PATH
cd APPLICATION_ROOT_PATH

# trap で終了のシグナルを拾って map ファイルを削除する
trap 'find ./public/{js,packs} -name "*.js.map" | xargs -n 1 rm -f --' EXIT

# 環境変数からBugsnag の API key が取得できない場合はスキップ
BUGSNAG_JS_API_KEY=$(bundle exec dotenv bash -c 'echo $BUGSNAG_JS_API_KEY')
if [ -z "${BUGSNAG_JS_API_KEY}" ];then exit 0;fi


# 指定のROLE-DEPLOYMENT_ROLEの時だけ実行する
# EC2 のインスタンスメタデータの中からタグの情報を取得する
INSTANCE_ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`

TAG_ROLE=` aws ec2 describe-instances --instance-ids ${INSTANCE_ID}  --query 'Reservations[].Instances[].Tags[?Key==\`Role\`].Value' --output text`
TAG_DEPLOYMENT_ROLE=` aws ec2 describe-instances --instance-ids ${INSTANCE_ID}  --query 'Reservations[].Instances[].Tags[?Key==\`DeploymentRole\`].Value' --output text`

if [ "${TAG_ROLE}" == "Job" ] && [ "${TAG_DEPLOYMENT_ROLE}" == "Utils" ]
then
  # 別のシェルスクリプトでアップロード処理を行う
  # タイムアウトもしくは他の例外が発生すると 0 以外のシグナルが飛ぶ
  timeout -sKILL 300 bash ./script/codedeploy/upload_sourcemaps_to_bugsnag.sh

  # 0 以外のシグナルの時 = タイムアウト等による失敗した時
  if [ $? != 0 ]
  then
    # slack にエラー通知
    MESSAGE="BugsnagへのSourceMapアップロードに失敗しました…"
    RAILS_ENV=production bundle exec thor utils:slack:ping --channel "#target_channel" --message "${MESSAGE}"
    # 0 でシグナルを投げる事で例外発生時も trap で拾える様にする
    exit 0
  fi
fi

アップロード処理部分はタイムアウト処理を行いたいため別ファイルに逃がしています。

EC2 インスタンス内から http://169.254.169.254/latest/meta-data/instance-id を叩く事で INSTANCE_ID を取得しています。 それを元に ec2 にリクエストを投げ、クエリにより指定したタグの値を取得します。

$? で直前のコマンドの終了シグナルを取得できるので、正常終了の 0 以外の場合は Slack への通知を行い、 exit 0 で正常終了のシグナルを投げることで trap で検知される様にしました。 これにより Ruby における ensure の様な処理を実現し、このスクリプト内で完結する様にしています。


次に実際にアップロード処理を行っているスクリプトです。

upload_sourcemaps_to_bugsnag.sh

#! /bin/bash

source ~/.bash_profile
export AWS_CONFIG_FILE=AWS_CONFIG_FILE_PATH
cd APPLICATION_ROOT_PATH

# dotenv で管理している API KEY を取得
# dotenv が引数にコマンドかファイル以外を想定していないため `bash -c` により実行
API_KEY=$(bundle exec dotenv bash -c 'echo $BUGSNAG_JS_API_KEY') 

# EC2 インスタンスのIDを取得
INSTANCE_ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`

# EC2 インスタンスのタグから現在のインスタンスのサービス名等を取得
SERVICE_NAME=` aws ec2 describe-instances --instance-ids ${INSTANCE_ID}  --query 'Reservations[].Instances[].Tags[?Key==\`ServiceName\`].Value' --output text`
URL_PREFIX="https://${SERVICE_NAME}/"
SOURCE_DIR="./public/"

for ORIG_PATH in $(find ./public/js ./public/packs -name "*.js.map")
do
  MAP_PATH=${ORIG_PATH#*./public/}
  JS_PATH=${MAP_PATH%.map}
  # fingerprint の部分をワイルドカード指定に変更
  MINIFIED_URL=$URL_PREFIX${JS_PATH/-*.js/-*.js}

  curl https://upload.bugsnag.com/ \
   -F apiKey="$API_KEY" \
   -F minifiedUrl="$MINIFIED_URL" \
   -F sourceMap="@$SOURCE_DIR$MAP_PATH" \
   -F minifiedFile="@$SOURCE_DIR$JS_PATH" \
   -F overwrite=true
done

find コマンドで map ファイルを検索し、それぞれを以下のドキュメントに従ってアップロードしています。 ローカルのファイルパスから URL を生成しパラメータを用意します。

https://docs.bugsnag.com/api/js-source-map-upload/

URL にはワイルドカードが指定できますので、fingerprint の部分は置換えてすっきりさせています。こうすることで bugsnag の Uploaded source maps に fingerprint 違いのファイルが別々に登録されていくのを防ぐ事ができます。

ステージング環境等複数の環境から同じ Bugsnag の JSプロジェクトを使いまわしている場合は fingerprint を残しておくとそれぞれに対応した map ファイルが参照されます。

結果

f:id:lulu-ulul:20180126104259p:plain

上がソースマップが反映されたもので、下がソースマップが適用されてない状態のものです。

人間に優しい表示にすることができました!


Misoca 社では、日々開発環境を改善していく事に興味があるエンジニアを募集しています。

Ruby 2.5.0 までの道のり

こんにちは、id:eitoball です。年末・年始は、特別なことをすることなく、自宅で家族とまったりと過ごしていました。正月らしいことは、歩いて5分ぐらいの近所の神社へ初詣に行ったことでした。

昨年2017年12月25日に Ruby 2.5.0 が リリース されました。コミッタの皆様ありがとうございます。Misocaは、翌日26日に2.5.0を使うように更新をしました。今回は、2.5.0 への更新について何かを書こうとしていたのですが、特に苦労することなく更新することができてしまったので、Misoca で Ruby のバージョンの変遷を調べてみました。

f:id:eitoball:20180112100730p:plain

Ruby 1.9.3の時代(2013年から2014年頃)

サービス開始当初、2013年頃は、Ruby 1.9.3-p194 を使っていたようです。1.9.3-p194 は、2012年04月20日に リリース されていました。Misoca の開発が始まった頃の最新のバージョンで、しばらく、このバージョンを使い続けていたようです。

次は、2014年01月頃に Ruby 1.9.3-p484 へ更新しました。2013年12月25日には、バージョン 2.1.0 が リリース されていました。しかしながら、とりあえず、1.9.3 系の最新バージョンへ更新を選択しました。

Ruby 2.x へ(2014年から2016年まで)

Misoca が、Ruby 2.x を使うようになったのは、2014年04月24日に Ruby 2.1.1 へ更新したときでした。2.1.1 の リリース は、2014年02月24日で、この頃に 2.0 系や1.9.3 系もリリースされていましたが、最新の Ruby を使うことになりました。

2014年12月05日には、2.1.5 を使用するようになりました。2015年03月10日に 2.2.0 を使うようになりました。この頃には、2.2.1 が リリース されてたので、2.2.1 を使おうとテストをしていたら、 GC 周りに問題があることがわかり、2.2.0 を使うようにするか、 2.2.2 がリリースするまで待つか迷いましたが、2.2.0 を使うことにしました。

Ruby 2.2.3 を使うようになったのは、2015年08月20日でした。これ以降、以下のように最新のRubyを使うように追従していきました。

  • 2015年12月17日 2.2.4
  • 2015年12月26日 2.3.0
  • 2016年05月06日 2.3.1
  • 2016年11月21日 2.3.2
  • 2016年11月28日 2.3.3

Ruby 2.4 (2017年)

前述のように Ruby 2.2 後半から 2.3 を使うように順調に更新していましたが、2.4.0 を使うようになったのは、2017年03月03日でした。遅くなった理由は、Misoca 社では、前年から rrrspec を使用してテストを実行するようになりましたRuby 2.4.0 を使うように変更してから、ローカルで rspec を使って成功する spec が、時々、失敗するようになり結果が不安定でした。この原因の調査などに手間取ったためでした。

以降は、以下のように新しい Ruby を使うように更新していきました。

  • 2017年03月27日 2.4.1
  • 2017年10月03日 2.4.2
  • 2017年12月15日 2.4.3

Ruby 2.5(2018年)

最初に書いたように昨年末の2017年12年26日にリリースされた翌日に 2.5.0 を使うように更新しました。2.5.0 への更新のために2017年10月頃から、その頃リリースされた2.5.0-preview1 を使ってテストをして準備をしていました。

現在のところ、安定して稼働しており、この記事 で説明されているような改善による効果も確認されているようです。

さいごに

今回、初めて、Misoca が使用する Ruby の更新を振り返ってみました。振り返るにあたり、git のコミットログや GitHub のプルリクエストから、Misoca 社の変化を垣間見ることができ、楽しい経験でした。

Misoca 社では、Ruby の更新などにも興味があるエンジニアを募集しています。

CI実行時間を11分→5分に短縮する

メリークリスマス!🎄

Misoca開発の@kokuyouです。

Misoca Advent Calendar 2017、遂に最終日です。 昨日の記事は@eitoball開発の小ネタ劇場でした。

本日 Ruby 2.5 が出る予定ですが、特に関係ないCIの話をしていきたいと思います。

どうでもいいんですが、CIという略語を見ると Continuous IntegrationなのかCANDY ISLANDなのか迷う今日このごろです。

⏳ ビルド時間の推移

以前の記事で紹介したとおり、MisocaではCIでのRSpec分散実行にrrrspecを使用しています。

これにより20分ほどかかっていたビルドを11分程度まで短縮することができたのですが、それでも11分は結構待たされるな―、という気持ちになります。スピーチとCIのビルド時間は短いほうが良いのです。

そんなわけで、頑張って短縮してみました。

以下は7月~9月ごろのビルド実行時間推移を表したグラフです。

f:id:kokuyouwind:20171225142005p:plain

青線がビルドごとの実行時間、赤線が直前10ビルドの移動平均になっています。

y軸の上限を1000秒(=16分40秒)に取っていますが、初期はこの上限を軽々と突き抜けていることがわかります。 全体的にも600秒(=10分)はまず下回らない感じです。

一方で、対処を始めた8月ごろからグラフが右肩下がりになり、終盤では400秒を下回っています。

直近(10月~12月)では以下の通り、ほとんどのビルドで400秒(6分40秒)を下回り、軽いビルドでは300秒(=5分)程度で完了しています。 なお、たまに飛び跳ねてるのもDependabotで一気にビルドキューが積まれたときのものでした。

f:id:kokuyouwind:20171225142135p:plain

ここまで実行時間が短縮されたことで、待たされていると感じることがだいぶ少なくなりました。

💪 やったこと

💰 金の弾丸

最初は「rrrspecの並列数増やせばスケールするでしょ」と思いmax_workerを5から10に上げてみたのですが、ほとんど短縮せず悲しい気持ちになりました。

仕方がないので、ここからは時間がかかってる処理をひとつずつ調べて潰していく戦略にシフトしました。

🖥 MySQLのローカル同居

当初は レシピに組み込むのがめんどい MySQLの起動オーバーヘッドやメモリ量などを考慮して、rrrspec workerノードとは別にMySQLノードを立てていました。

しかしMySQLノードを固定で立ち上げているとスケールしないことや、接続数・転送量が逼迫していることから、MySQLをrrrspec workerノード内に同居させることにします。

datadirをtmpfsに向けるのに苦戦したり、AppArmorにMySQLプロセスの起動を阻まれたりしたのですが、なんとか完遂し、なぜかこれだけで1分程度実行時間が短くなりました。

帯域とか転送速度の問題でこれまでの戦略がイケてなかったという話なんですが、まぁ結果オーライです。

(この時点でjob全体10min、うちrrrspecが8min)

🔗 rsyncの設定見直し

さらにrrrspecの実行ログを確認したところ、実行対象をrrrspec master -> rrrspec workerへ同期するrsyncが1min以上かかっていました。

以下の設定を見直し、rsyncにかかる時間を5sec程度まで短縮しました。

  • Jenkinsではjobごとにworkspaceを持ちタイムスタンプが異なるため、 --timesではなく--checksum を使う
  • node_modulestmp/cacheなど不要なディレクトリが同期されていたため、--exclude指定に追加する

(この時点でjob全体9min、うちrrrspecが7min)

🔪 specの分割

MySQLがスケールするようになったためmax_workerを再度上げていったのですが、やはりあまり実行時間は短縮されませんでした。

個別に実行時間を見てみると、なんと単独ファイルで7minかかっているfeature specが……

rrrspecはファイル単位で分散実行するため、そりゃ7minは下回れないなぁ…… という気持ちになり、そのspecを幾つかのファイルに分割しました。

その後もボトルネックになったspecを分割したり不要なテストを消したりと地道に潰していった結果、10workerでのrrrspec実行時間を5minまで短縮することができました。

(この時点でjob全体7min、うちrrrspecが5min)

🏗 package install & build

オーバーヘッドの比率が上がってきたため、そちらで時間がかかっている処理に目を向けていきます。

オーバーヘッドのうちbundle installが20sec、yarn installが30sec、yarn buildが1min程度で、依存パッケージ解決と静的ファイルビルドに殆どの時間を使っていました。

ここで依存パッケージは Gemfile.lockyarn.lockが一致していれば同じものを使いまわせますし、yarn buildJavaScriptのソースなどに変更がなければ同じものを使えます。

そこでCircleCI 2.0のキャッシュ機構を参考に、Jenkinsfileでキャッシュを行う関数を用意しました。

def yarn_install() {
  cache(
    ['node_modules'],
    get_cache_key(['package.json', 'yarn.lock']),
    { bash "yarn install --no-progress --pure-lockfile" }
  )
}

def cache(paths, key, callback) {
  if (!fileExists(cachedir(key))) {
    callback()
    for(def path: paths) {
      // node_modulesのrequireの仕様上、ディレクトリ構造の保持が必要なので path/to/file ような場合に path/to までを作成したい
      sh("dirname ${cachepath(path, key)} | xargs mkdir -p")
      sh("mv ${path} ${cachepath(path, key)}")
    }
  }
  for(def path: paths) {
    sh("rm -rf ${fullpath(path)}; ln -s ${cachepath(path, key)} ${path}")
  }
}

cache関数では依存ファイルのパスを元にキーを生成しており、そのキーを持つキャッシュが存在する場合はsymlinkを貼るだけで処理を終わります。

これにより、Gemfileなどに変更がない場合はこれらの処理を合わせて10secちょっとで終わるようになりました。

なおキャッシュを利用するのはfalse positiveが怖いのですが、古いキャッシュはfalse negativeのほうが起こりやすいはずですし、定期的に削除して再生成させているため大きな問題はないだろうと思っています。

✅ 結果

これらの改善処理によって、全体で5min、オーバーヘッドは10sec程度まで短縮することができました🎉

あとは不要なspecを削ったり統合したり、あるいはfixtureを減らしたりcreateからbuildに変えたり、といった地道な改善を続けて少しずつ実行時間を削ることになります。

こういった地道な改善を進める上でも、ひとまずそれ以外の部分で実行時間を大幅に削れたのは効率が良かったと思います。

🎅 Misoca Advent Calendar 2017

最終日がだいぶギリギリになってしまいましたが、無事Misoca Advent Calendar 2017を完走することができました。

個人的には社長の記事が一番バズってるという事実に微妙な気持ちを抱いていますが、全員でいろいろな記事を書くことができて楽しかったです。

来年も気が向いたらアドベントカレンダーを立てたいなーと思います。

📣 宣伝

Misocaでは2018年のアドベントカレンダー記事を一緒に書く仲間を募集しています!!!

開発の小ネタ劇場

こんにちは、id:eitoball です。

この記事は、Misoca Advent Calendar 2017 の24日目の記事です。

今日は、クリスマス・イブですね。明日は、ruby 2.5.0 のリリースが予定されています。もう少し気の利いたことが書くといいのですが、さらっと始めます。

今回は、様々なMisocaのメンバーにエディタやシェルの使い方などといった主に開発に関する小ネタ(ティップス)を紹介してもらいます。

RubyMine で Neovim を使う

by id:eitoball

先日、RubyMine を使うようになったという記事を書きましたが、まだ、vi 系のエディタを使いたいときがあります。RubyMine の「ターミナル」から起動してもいいのですが、ANSI エスケープを正しく処理できないなど若干の問題があるので、正しく処理する iTerm 2 などのターミナルプログラム上で起動する方が快適に編集ができます。

そこで、RubyMine の "External Tools" neovim-remote を使って、快適に Neovim を使うようにします。設定方法は以下のようになります。

  1. このページ で説明されているように neovim-remote を設定します。
  2. RubyMine の "External Tools" で neovim-remote を起動するようにする。以下は設定例です。

screenshot.png (139.1 kB)

これだけになります。後は、ターミナルを開いて NVIM_LISTEN_ADDRESS=/tmp/nvimsocket nvim のように Neovim を事前に起動しておいて、RubyMine でファイルを開いておいて、メニューから "External Tools" -> "neovim" を指定すれば、 Neovim でファイルを開きます。"Keymap" にてショートカットを割り当てておくと便利です。僕の場合は、Shift-Ctrl-Command-V を割り当てています。

Neovim で変更する場合は、RubyMine で自動的に反映されます。RubyMine で変更する場合は、 Neovim では、:e で再読み込みをして反映します。

test.gif (1.1 MB)

注意するのは、事前に Neovim を開いておく必要ことです。開いておかないと RubyMine で「Vim: 警告: 端末への出力ではありません」のようなメッセージが出ます。この場合は、一旦、プロセスを止めて(Stop Process)、NVIM_LISTEN_ADDRESS で指定したファイルを削除してからやり直すようにして下さい。

Happy Editing with RubyMine and Neovim !!!

マルチカーソルLOVE

by id:mugi_uno

普段VSCode/SublimeText/Atomの全てを使っていますが、全てに共通して使える機能であるマルチカーソルが無いと、もう生きていけない体になっています。 (マルチカーソル = カーソルをたくさん作れる機能と思ってください)

例えば、jsonの値を全て初期化時にインスタンス変数に持ちたいな〜みたいなコードを書こうと思った時、こんなことができます。

cap.gif (159.5 kB)

あっ、やっぱキーの名前全部 item_ から始まるんだったわ!みたいなケースでも、検索・置換を行うことなく、サクッと行えます。

cap2.gif (90.2 kB)

使いこなせると手放せなくなるので、使ったことの無い人はぜひお試しください!

AtomのTeletypeで爆速リモートペアプロ

by renyamizuno_

先月末頃にTeletype for Atom というAtomプラグインがでました。 GitHubが「Teletype for Atom」リリース。開発者向けエディタ「Atom」でも、複数プログラマが同時にコード編集可能 - Publickey 複数人でコードを一緒に編集出来るようになりました。 このため、キーボード配列が違うとか、キーバインドが違うから…とかそういった問題でペアプロが難しいといった問題から解消されます。 最高。 主にテストを書く時に、「こういうテストがいるよねー」ということを言いながら雑にテストコードを書いている時に、 ペアがすでに必要なパラメータ等を準備してくれていて、瞬間でテストコードが仕上がる。という事案が何度も発生して、脳が追いつきませんでした。 とても新鮮で不思議な体験だと思うので、みなさんも味わってください。

完全食COMP

by 洋食

皆様、昼食はどうしていますか?Misoca名古屋オフィスは名駅近くにあるので、ランチには事欠きません。が、逆にドコで何を食べるかを考える必要があります。距離と時間(店の行列・料理が出てくるまでの時間・食べる時間)から最適解を求める必要があります。贅沢な悩みではありますが、ちょっと考えるのが面倒になってきました。そこで、完全食COMPです。プロテインみたいなもので、好きな飲み物で溶かして飲むだけです。味はほぼ豆乳と同じです。私は、カフェインを摂取したいので、コーヒーで溶かしています。朝にCOMPを作って、水筒に入れて持っていっています。ドコで何を食べるかを考える必要もなく、お弁当を用意する必要もない。私の趣味は読書ですが、昼休みがまるまる空くので、本とか論文が読み放題です。流石に夕方にお腹が空いてきますが、Misocaは残業しない主義なので、帰宅して夕食たべればOKです。最高です。

RubyMineでもマルチカーソルが使えるぞ〜

by ころちゃん

こんにちは、RubyMine(IntelliJ)勢です。記事上のほうで、Sublimeおじさんがマルチカーソルを絶賛していますが、RubyMine でもマルチカーソルは使えます。

Shift + Alt + Click または Alt + Alt 押しながら↑↓ です。

実は前者だけ知っていて、クリックするの面倒だな〜って思ってたけど後者があればマウス使う頻度も減りますな。あと同じ単語にマルチカーソル生やすときは、Ctrl + g でいけます。

普段、息を吸う用にShift + Shiftサーチ(Search Everywhere)してましたが、Alt + Altがあるとは思いませんでした。。灯台下暗しでしたね。ちゃんと覚えよう。

最後にさらっとRubyMineを使う際の知見/感想を書いておくと

  • IdeaVim等を入れると、デフォルトのショートカットと競合するので、Vimキーバインドを使うのをやめた。
  • ショートカット数が膨大なので、慣れるまではデフォルトをおすすめする。
  • 画面分割した際のSplitter間の移動ショートカットがデフォルトで設定されてないので、Tab to Next Splitterを入れると良い。
  • 外観が気に入らない貴方は、Material Theme UI を入れると気にいるかも。
  • ボクはMaterial Themeは行間がちょっと広くて気に入らなかったので、アイコンだけカッチョ良くしたくて Atom File Icons IDEAを入れてます。

みたいな感じです。参考にしてみてください。

コマンド履歴を検索する

by @kosappi_

bashzshCtrl + R を打つと、コマンド履歴を検索できます。 もはや常識のように感じてますが、無いと困る、という機能。 ss.gif (1.6 MB)

anyenvでいろんな言語の環境を一括管理

by @kokuyouwind

Rubyの環境はrbenvで管理してる人が多いと思います。 同じようにNode.jsの環境管理にndenv、Pythonはpyenv……と、この手の実行環境管理ツールは言語ごとに増えていきますね?

そうなると…… まぁ、.bashrcなどの初期化スクリプトはこうなりますよね。

# rbenv
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

# ndenv
export PATH="$HOME/.ndenv/bin:$PATH"
eval "$(ndenv init -)"

# pyenv
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"

はい。 初期化スクリプトが汚れるだけではなく、環境更新もいちいち個別にGit Pullが必要です。

こんなときにanyenvを使うと、*envを一括して管理してくれます。

# .bashrcには以下の2行だけを書けばいい
export PATH="$HOME/.anyenv/bin:$PATH"
eval "$(anyenv init -)"

# *envコマンドをinstallできる
$ anyenv install rbenv
$ anyenv install ndenv
$ anyenv install pyenv

# プラグインを入れると、*envをまとめて更新できる
# (anyenv自身も含む)
$ anyenv update

ちなみに、現在入れられる*envは以下の通りです。

$ anyenv install -l 
Available **envs:
  crenv
  denv
  erlenv
  exenv
  goenv
  hsenv
  jenv
  luaenv
  ndenv
  nenv
  nodenv
  phpenv
  plenv
  pyenv
  rbenv
  Renv
  sbtenv
  scalaenv
  swiftenv

これだけ色々な言語(と一部ビルドツール)が簡単に入れられて、バージョンも切り替えられるのは便利ですね。 brew派のみなさんも、試しに使ってみてはいかがでしょうか。

RubyMine はじめました ~ From Vim to RubyMine ~

こんにちは、id:eitoball です。

この記事は、Misoca Advent Calendar 2017 の21日目の記事です。

やっと、Amazon.co.jp から、

Echo の招待者に選ばれました。

というメールがきました!もちろん、購入しました。ちょっと早いクリスマスプレゼントです。

はじめに

Misocaの開発環境2017冬Misocaの開発環境 で、

普段どのエディタ/IDEでコード書いてる?

*  (neo)vim

と回答していたのですが、ここ10日間ほど RubyMine を使ってコードを書いています。vim や neovim など vim 系のエディタを20年以上愛用しているのですが、RubyMine でコードを書くようになった経緯を書いていきたいと思います。

実は、6年前から所有していました…

f:id:eitoball:20171220094558p:plain

最初は29ドルとかだったんですね…最初に購入したのは正式リリースの前でセール価格だったと思います。今まで、ろくに使わずにお布施だけしていました…

新しいバージョンが出るたびに使ってみていたのですが、最初は rvm とか rbenv に対応していなかったり、ターミナル上で操作すると同じように git とかの他のツールにアクセスするのが大変だったので、半日程度ですぐ諦めていました。

ヨーヨーでつられた

今年の9月に広島で開催されたRubyKaigi2017 にて、JetBrains さんがスポンサーをしていてブースを出されていました。*1

ヨーヨーももらいにブースへ行った際、「驚愕するデモ」*2に感動して、もう1回使ってみようかなぁという気持ちになりました。単純ですね…

Vim/Neovim に不満はあるのか?

全くないです。Sun OS で vi を使い始め、Vim、そして、Neovim と vi 系のエディタをメインの開発用エディタとして愛用し続けています。プラグインはとても充実しており、基本的な開発は Neovim 上で完結することができます。git などの CLI なプログラムも特に不満無く使うことができています。

ただ、 First 10 minutes of TDD intro episode 1 (unedited)Refactoring in IntelliJ IDEA, Live by Robert C. Martin (Uncle Bob) を見て、Kent Beck や Bob Martin が、さっそうと Eclipse や RubyMineを使いこなしながらリファクタリングしているのを見るとアジャイルな開発者を目指すものとして同じようにモダンな IDE を使いこなせるようになりたいなぁと思い続けてはいました。

RubyMine ことはじめ

いつも諦めるのは普段やっていることと同等なことができないのがもどかしいためだったので、新しい Rails プロジェクトで使ってみることにしました。気をつけたのは、可能な限りショートカットを使って操作することでした。最初は、この "Mastering RubyMine keyboard shortcuts" を読みながら進めるようにしました。この記事で紹介されている Key Promoter X というプラグインはある操作をするときショートカットが使える場合は提案してくれるのでやり直せる場合は元に戻して、提案されたショートカットを使うようにしました。リファレンスカード公式日本語版)は、常に開いていつでも参照できるようにしておきました。

Qiita にて RubyMine のタグから、ショートカット関連の記事を探して参考にさせて頂きました。

まず、使うようにしたショートカット(Mac OS X 10.5+)は以下の3つでした。

  • Shift+Ctrl+A - Find Action... (アクションの見つける…)
  • Ctrl+v - VCS Operations Popup... (バージョン管理の操作のポップアップを表示…)
  • Shift+Command+F Find in Path... (指定したパスから検索する…)

一番、重宝していたのは「Find Action...」です。こういうことがしたいと思ってショートカットが分からないときは、とりあえずこれで探すようにしていました。ここで git fetchgit rebase などの操作が ターミナルに戻らなくても、RubyMine 内でできることを知り、もどかしさが一気になくなりました。

本格導入

このように少しずつ操作を覚えながら、週末に簡単な Rails アプリを作って、一通りの事を RubyMine 上でできるようになったので、仕事での開発でも使うようにしてみました。

まずは、心機一転するため、レポジトリ内の .idea~/Libraries/Application Support/~/Libraries/Preferences 内にある設定ファイルといった関連ファイルを全部、削除して初期状態から使うようにしました。

あと、以下の3点を気をつけたのは以下の3点でした。

spec をショートカットで実行する。

仕事では、テスト駆動開発をするように努めています。テストを常に1キーストロークで実行できるのは、僕にとってとても重要なことです。最初、Alt-Ctrl-R で spec を実行して、同じ spec を実行するときは、 Ctrl-R で実行しています。

IdeaVim プラグインを使わない。

IdeaVim では基本的な Vimキーバインドしか対応していません。僕は、vim-surround や vim-unimpaired、fugitive などのプラグインキーバインドを多用しているので、Vimキーバインドを使っていると反射的にプラグインキーバインドを押してしまうので RubyMine では初期状態で推奨されている Mac OS 10.5+ (Mac OS Xの場合)を使っています。最初は、もどかしさもありましたが、Alt-上矢印 での Extend Selection や Alt-Shift-上下矢印 での Move Line Up/Down などのショートカットを使うようにして、RubyMine での編集方法に慣れていきました。

RubyMine の Terminal を使う。

どうしても、ターミナルでコマンドを実行したいときは、iTerm 2 などターミナルプログラムを開かず、Alt-F12 で内蔵のターミナルを使うようにして、RubyMine からできるだけ離れないようにしました。編集に戻るときには、Shift-Esc や Shift-Command-F12 を使っています。

現在の状況とこれから

現在まで、1週間程度使い続けて、仕事の開発などメインで RubyMine を使っていこうかなぁと思っています。Ctrl-V でのポップアップから作業用のブランチの作成して、Alt-Ctrl-R や Ctrl-R で spec を回して、都度、Command-K でコミットして、最後に Command-Shift-K でレポジトリへのプッシュまで RubyMine で滞りなくできるようになっています。最近はリファクタリング機能やデバッグ機能を積極的に使うようにしています。

Live Edit 機能JavaScript のデバッグ なども試していたいと考えています。Ruby/Rails での開発のほとんどを RubyMine で完結するようにしたいなぁと思っています。


First 10 minutes of TDD intro episode 1 (unedited)


Refactoring in IntelliJ IDEA, Live by Robert C. Martin (Uncle Bob)

*1:ちなみに弊社、株式会社Misoca もスポンサーしていました。

*2:Live Edit 機能のデモを見せて頂きました。

Rails 5.2 の足音 ~ Active Storage を試してみる ~

こんにちは、 id:eitoball です。

この記事は、Misoca Advent Calendar 2017 の15日目の記事です。

Rails 5.2 は、beta1 と beta2 が出てきて、正式なリリースも近々のようですね。皆さん、更新の準備はできていますでしょうか?今回は、Rails 5.2 の 目玉機能の一つである Active Storage を試してみようと思います。

Active Storage とは?

Active Storage makes it simple to upload and reference files in cloud services like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage, and attach those files to Active Records. Supports having one main service and mirrors in other services for redundancy. It also provides a disk service for testing or local deployments, but the focus is on cloud storage.

Active Storage は、AWS S3 や Google Cloud Storage などクラウドストレージサービスへのファイルをアップロードをするための機能です。 carrierwavepaperclip といった gem と同じような機能を提供します。 Rails 標準なので、モデルとの連携がシームレスに行うことができるのが特徴だと思います。

Active Storage を試してみる

今回は、Active Storage プロジェクトの README.md に記載されているように User モデルに avatar という属性でファイルを添付するようにしていきます。添付したファイルは、 AWS S3 に保存するように設定したいと思います。

Rails アプリケーションの作成

はじめに rails gem (5.2.0.beta2) を追加します。今回は、せっかくなので、2017年12月25日にリリース予定の ruby 2.5.0dev を使用します。

$ ruby -v
ruby 2.5.0dev (2017-12-14 trunk 61215) [x86_64-darwin16]
$ gem install rails -v 5.2.0.beta2
…
Fetching: rails-5.2.0.beta2.gem (100%)
Successfully installed rails-5.2.0.beta2
28 gems installed

サンプルのアプリケーションを作成します。Rails 5.1 からの更新を想定したいので、--skip-active-storage で、後から、Active Storage を追加するようにします。*1

$ rails new --skip-active-storage -S --webpack=vue sample-application
…
* bin/rake: spring inserted
* bin/rails: spring inserted

作成したアプリケーション内に移動して、Active Storage をインストールします。config/application.rb 内で、 require "active_storage/engine" の行がコメントアウトされているので、コメントを外して、bin/rails active_storage:install を実行します。

$ cd sample-application
$ nvim config/application.rb
...
$ bin/rails active_storage:install
Copied migration 20171214014543_create_active_storage_tables.active_storage.rb from active_storage

データベースを作成して、マイグレーションを実行します。

$ bin/rails db:create db:migrate
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20171214014543 CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs)
   -> 0.0013s
-- create_table(:active_storage_attachments)
   -> 0.0014s
== 20171214004543 CreateActiveStorageTables: migrated (0.0028s) ===============

active_storage_blobsactive_storage_attachments というテーブルが作成されました。これらのテーブルにファイルのメタ情報などが記録されるようですね。

開発用サーバーを実行して、アプリケーションが動作することを確認します。

$ bin/rails server -b 0.0.0.0

ブラウザで、http://localhost:3000/ にアクセスして、”Yay! You’re on Rails!” と表示されていれば、インストールが成功しています。確認できたらサーバーは停止しておきます。

ユーザーモデルの作成

User モデルと関連するコントローラやビューを scaffold を使って作成します。あと、マイグレーションを実行します。

$ bin/rails generate scaffold user name:string address:string
      invoke  active_record
…
      create    app/assets/stylesheets/scaffold.css
$ bin/rails db:migrate

app/models/user.rb を編集して avatar 属性(has_one_attached :avatar)を追加します。

class User < ApplicationRecord
  has_one_attached :avatar
end

新しく追加した avatar 属性を表示したり保存したりできるようにします。

app/views/users/show.html.erb には、13行目あたりに次の行を追加します。

<% if @user.avatar.attached? %>
  <p>
    <strong>Avatar:</strong>
    <%= image_tag url_for(@user.avatar) %>
  </p>
<% end %>

app/views/users/_form.html.erb には、24行目あたりに次の行を追加します。

<div class="field">
  <%= form.label :avatar %>
  <%= form.file_field :avatar %>
</div>

最後に app/controllers/users_controller.rb では、72行目あたりを以下のように :avatar を追加します。

def user_params
  # params.require(:user).permit(:name, :address)
  params.require(:user).permit(:name, :address, :avatar)
end

開発用サーバーを立ち上げて、http://localhost:3000/users/new にアクセスして avatar のフィールドが追加されていることを確認してください。

f:id:eitoball:20171214130815p:plain

AWS S3 サービスの設定

ファイルを AWS S3 へアップロードするための設定を行います。Rails 5.2 では、秘密にしたい設定情報を暗号化することができるようになりました。AWS の認証情報はその機能を使って保存します。

bin/rails credentials:showconfig/credentials.yml.enc 内に保存されている情報を見ることができます。

$ bin/rails credentials:show
# aws:
#  access_key_id: 123
#  secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: abc123

bin/rails credential:edit で編集します。aws: からの部分のコメントを外して、AWS のアクセスキーIDとシークレットアクセスキーを記載します。EDITOREmacsVim などをコンソールベースのエディタを指定して編集して下さい。

$ EDITOR=nvim bin/rails credentials:edit
…
$ bin/rails credentials:show
aws:
  access_key_id: 123
  secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: abc123

Active Storage のストレージの設定は config/storage.yml に記載します。ここに AWS S3 の情報を追加します。amazon: からの行をコメントアウトして regionbucket を適宜修正して下さい。

$ nvim config/storage.yml
…
$ cat config/storage.yml
…
amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: us-east-1
  bucket: active_storage-test
…

そして、 config/environments/development.rb を編集して、開発環境で、AWS S3 ストレージを使うようにします。

$ nvim config/environments/development.rb
…
$ cat config/environments/development.rb
…
  # Store uploaded files on the local file system (see config/storage.yml for options)
  config.active_storage.service = :amazon
…

最後に aws-sdk-s3 gem を Gemfile に追加して、bundle install します。

$ nvim Gemfile
…
gem ‘aws-sdk-s3’
$ bundle install
…

開発用サーバーを起動して、http://localhost:3000/users/new にアクセスして実際にユーザーを作成して画像をアップロードします。

$ bin/rails server -b 0.0.0.0
=> Booting Puma
…
Use Ctrl-C to stop

f:id:eitoball:20171214130606p:plain

大きい画像をアップロードするとこのようになってしまいます。ですので、縮小できるようにします。

app/views/users/show.html.erbavatar 画像を表示している部分を変更します。

<% if @user.avatar.attached? %>
  <p>
    <strong>Avatar:</strong>
    <%= image_tag url_for(@user.avatar.variant(resize: '128x128') %>
  </p>
<% end %>

#variant には他にも色々な変換ができるようです。詳しくは、https://github.com/rails/rails/blob/master/activestorage/app/models/active_storage/variant.rb を参照して下さい。

Gemfile を編集して、 mini_magick を追加して、bundle install します。*2

$ nvim Gemfile
…
gem ‘mini_magick'
$ bundle install
…

サーバーを再起動して、 http://localhost:3000/users/11 の部分は、先ほど作成したユーザーのIDです)にアクセスすると画像が縮小されていることがわかります。

f:id:eitoball:20171214130609p:plain

さいごに

今回は、近々リリース予定の Rails 5.2 での新しく導入される Active Storage を使って、ファイルを AWS S3 へアップロードするようにしてみました。今回は試していませんが、Google Cloud Storage などの他のクラウドストレージサービスやローカルファイルシステムにもアップロードしたりもできます。また、複数とストレージに同時に保存(mirroring)もできるようです。また、画像以外にも動画やPDFを扱うこともできるようです。

正式にリリースされていないため文書などが少なく、 carrierwave や paperclip と比べると機能はまだ少なく導入・移行は大変そうです。しかしながら、Rails の他の機能とシームレスに連携できるのは、とても魅力的だと思います。

明日、16日目は、merotan ( @renyamizuno_ ) が、HTML と CSS を使って究極の何かを作ったことについて語ってくれるそうです。

*1:webpacker を使っているので yarn が必要です。

*2:別途、ImageMagick をインストールしておく必要があります。