an odd fellow

仕事のメモ

formikを使ってみる

前提

$ node --version
v.9.11.1

React, ReduxでSPAを作っている。

背景

connectで渡されたpropsをComponentで加工してFormを作っている場合、Formを触って変更された値をStoreまで持っていっても、その値はあくまでもFormのみで有効な値であるから、Storeに反映させる変更は無いことに気付いた。

例えば、read・write・ execの3つの権限をチェックボックスで付けたり外したりできるFormを考えるとわかりやすい。connectでは3つの権限のうち、既に付いている権限だけが ["read", "exec"] のように配列で渡されてくる。Componentではこれを {read: true, write: false, exec: true} のような辞書に変換してチェックボックスを作る。ここからが重要で、チェックボックスをクリックすると onChange に登録されたイベントハンドラには {read: false} のような辞書が引数として渡されることになる。しかし、これをStoreまで引きずっていってもどうしようもないのである。Storeが欲しいものはread・write・execの全ての状態がどうなったかという、Formで最終的に生成された状態であって、「readがfalseになりました」というような断片的な情報には興味が無い。もちろん {read: false} を受け取って ["read", exec"]["exec"] に変更することはできる。しかし、今回はたまたま簡単にハンドリングできるだけであって、より複雑なFormでは現実的でないだろう。

解消方法としては2つ思いついた

  1. StoreにFormごとのstateを持たせる
  2. reactのstateを使う

1は論外だろう。あり得ない。というわけで2番を採用する方向でReduxを利用しているときのReactのstateの利用有無について調べていたら、どうやらこれがReact界隈でいうところの Global State vs Local State という議論らしい。

ReduxにおけるGlobal stateとLocal stateの共存 - LIVESENSE ENGINEER BLOG

まあ現実的にはLocal Stateを解禁するしかないだろうと思ったが、一応社内のフロントエンジニアに聞きに行ったら今は「Ephemeral Stateという概念があるよ」と言ってFromikを教えてくれた。

GitHub - jaredpalmer/formik: Build forms in React, without the tears 😭

3行でどういうものか説明して、と頼んだらこういうものだと言う。

  • formのような一時的なstate管理はreduxで管理しなくていいよ
  • 代わりにpropsをformのstateにmappingしてくれる機能を用意したよ
  • formを送信するタイミングでformのstateをreduxに流し込むよ

つまり、ComponentのstateでもなくStoreのstateでもない中間層を用意してそこにFormの中間状態を保持してsubmitのタイミングでStoreに渡してくれるようだ。

欲しかったものっぽいので試してみる。

BasicForm

公式のデモのBasicがシンプルなフォームを作っていてわかりやすかったので、適当に不要な所を省いて写経した。

formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub

使ってみた感じは彼が教えてくれた3行そのままだった。その他に気付いたことは、handleChangehandleResetなど良く使うハンドラは既に定義されていて一般的な振る舞いをやってくれる。また、特殊な動きがしたければ簡単に上書きも可能というもののようだった。中間状態はFormikの定義しているvaluesが担うようだ。

背景で説明したread・write・execのFormを実装するならば、wirhFormikの mapPropsToValues で配列から辞書に変換して、handleSubmitvaluesから辞書を取り出して配列に戻してActionCreatorを呼び出すようになるかなと思った。

PermissionSelectorForm

背景に書いた、read・write・execのFormにFormikを適用してみたい。このFormをPermissionSelectorFormと呼称することにする。

Reactだけで実装

多分こんな感じになる。

formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub

Formikを適用する

多分こうなる。

formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub

ハマりどころ

handleChangeかsetFieldValueか

最初はinputのonChangehandleChangeを渡していたが動かなかった。handleChange<input type=text>のような逐次変わる値を追従させるときに使うようだ。<input type=checkbox>みたいなやつはsetFieldValueを使う。 また、setFieldValueの第一引数は厄介だった。withFormicmapPropsToValuesで作っているpropsの構造を文字列で書く必要がある。propsの構造を複雑にしていると大変だ。

setFieldValueの使い方を良くよむこと。

GitHubのissueにcheckbox使う場合の例がcodesandboxで置いてくれているのが参考になった。 How to populate array with checkboxes? · Issue #360 · jaredpalmer/formik · GitHub

上書きできるのはhandleSubmitだけ?

↑でハマっていたときにhandleChangewithFormicで上書きしてしまえばいいのでは?と思ってためしたができなかった。上書きできるのはhandleSubmitだけらしい。

というわけでFormikでやりたいことができていい感じだった。おすすめ。

prettierがいい感じ

背景

WebStormのフォーマッターがいけてない。例えば

import React from 'react'

としたら

import React from 'react';

セミコロン付けて欲しいが付けてくれない。 ほかにもダブルクオーテーションとクォーテーションを統一してくれない、とか、細かい不満がいっぱいあった。

Prettier

適当にググったらこれが出てきた。

https://qiita.com/furuk4wa/items/5f5a28e81a15653af0c8

名前だけ分かれば後は公式を見る。

https://prettier.io

install

$ yarn add prettier --dev --exact

https://prettier.io/docs/en/install.html

WebStormとの連携

公式に説明がある。

https://prettier.io/docs/en/webstorm.html

WebStormのバージョンが2018.1以上なら Alt-Shift-Cmd-P でフォーマットがかかるらしい。確かにかかった。上の ; の補完もやってくれる。クォーテーションの統一もしてくれる。いい感じ。

1028.07.12 追記

Alt-Shift-Cmd-Pを押しても何もおこらないとき

prettierの実行ファイルへのパスを指定しないとダメみたい。↓にやり方が書いてある。

blog.jetbrains.com

JavascriptのExportについて

前提

$ node --version
v.9.11.1

背景

import React from 'react';
import {render} from 'react-dom';

前者のように{}無しで書く場合と後者のように{}有りで書く場合があって、よくわからなかったから調べる。

名前付きエクスポートとデフォルトエクスポート

エクスポートの方法によってインポートの方法も変わるようだ。

名前を付けてエクスポートする場合はインポート側もその名前で束縛しなければならない。デフォルトエクスポートの場合はインポート側で束縛する名前を指定できる。

以下はlib.jsでaとbを定義してindex.jsでaをxとして、bをbとしてインポートしている。

lib.js

const a = 'this is a';
const b = 'this is b';
export default a;
export {b};

index.js

import x from './lib';
import {b} from './lib';

console.log(x);
console.log(b);

参考

名前付きエクスポートとデフォルトエクスポートについてはMDNを良く読む。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/export

nodeでimport/exportを使う方法

ついでにnodeでimport/export文を使うには .js でなく .mjs として保存し、 node --experimental-modules index.mjs と実行する。

参考

適当にググってこのQiitaが出てきた。

https://qiita.com/ryohji/items/93f5050b9af6fc15693c

公式はこのへん。

https://nodejs.org/api/esm.html#esm_enabling

dockerの-vオプション

なんかいつも忘れるからメモ

書き方

docker -v ホストのディレクトリ:コンテナのディレクトリ image名

ホストのディレクトリは絶対パスで記述する。

カレントディレクトリをマウントしたいとき

docker -v $(pwd):/path/to/directory image

覚え方

前と後ろのどっちがホストのディレクトリだっけ?っていつも悩んでしまう。

Dockerは基本的に基本的にホスト:コンテナの順で書く。だから大抵source => destinationの順になってる。

-v $(pwd):/path/to/directory だったらホストのカレントディレクトリをコンテナの /path/to/directory にマウントする。

portもそう。-p 80:8080 だったらホストの80番をコンテナの8080にフォワードする。

展開のされかた

以下のようなディレクトリ構造がホストに構築されているとする

  • /app/build/
    • index.html
    • style.css

で、-v /app/build:/usr/share/nginx/html と書くとコンテナでは以下のように展開される。

  • /usr/share/nginx/html/
    • index.html
    • style.css

以下のようにはならない。

  • /usr/share/nginx/html/
    • build/
      • index.html
      • style.css

逆に↑のようにしたければ以下のようにコンテナのパスの方に書くしか無い。

-v /app/build:/usr/share/nginx/html/build

またコンテナのパスのほうに存在しないディレクトリを書いても勝手に作ってくれる。

TAMRON 28-75mm F/2.8 Di III RXD

おれのα7にはオートフォーカスするレンズが着いたことが一度もない。初代α7が発売されてすぐに買ったから、もう4年間も使っていることになるが、全てフルマニュアルで撮ってきた。カメラマンだったらしい祖父の遺した古いNikkorCanon FDレンズだけがレンズ資産だった。レンズがそんなだから、ピントも絞りもレンズを直接いじくって決めるし、「どうせなら」と思って測光もマニュアルでやっていた。撮影には手間と時間がかかるけど、それくらい時間をかけられるような余裕を持って被写体を眺めた方が発見があるんじゃないかなと思っている。

f:id:roronya:20180527202934j:plain

この桜の写真は AI Nikkor 50mm f/1.4Sで撮った。風が吹くとすごい量の花びらが舞うので、その様子を撮りたくて、1時間くらい待った。それくらいの時間感覚で写真とは向き合うのがおれは楽しいと思っている。

しかし、この時間のかけ方は趣味で一人でやるにはいいけど、人と旅行に行くときはそうもいかないので、やはりオートフォーカスするレンズが欲しいなあと去年頃から実は思っていた。それで、今年のCP+でTAMRONがEマウントのフルサイズ向けに標準ズームを出すと発表していたから、発売したら買おうと思っていた。

www.tamron.jp

それがこれ。今日はこれを持って旧古河庭園に行った。

f:id:roronya:20180527123914j:plain f:id:roronya:20180527120216j:plain f:id:roronya:20180527122231j:plain

解像度が高い。おれは今までα7の本気を知らなかったんだなあと思った。洋館の壁面の写真なんか、構図も意識せずに適当にパシャッとやったんだけど、それなりの写真に見えてしまう。被写体の質感を正確に伝えるだけで表現になるというのは知らなかった。1枚目も3枚目も木々の葉の質感だけで訴えられる。

この下の桜の写真はさっきの桜の写真と同じレンズで撮ってるんだけど、上に比べるとまったく解像してなかったんだとわかる。撮ったときは結構撮れてるじゃんと思ったんだけどな。

f:id:roronya:20180527194243j:plain

とにかく解像度に驚いた。しかしなんでも解像すれば良いというものでもないだろうとも思う。表現の幅としてどれくらい解像させるか?という軸が自分の中に加わった。

信州大学工学部生に行って欲しい飲食店リスト(千石劇場周辺編)

今日は信州大学工学部に在学する後輩たちに長野市街地で行って欲しい飲食店を紹介します!

Twitterで適当なこといったら反応があったので書くことにした。おれは24年という長いあいだ長野に居たわけだが、去年とうとう就職で東京に引っ越してしまった。東京の生活も1年も経つと慣れてきて美味い店、居心地の良い店なんかも見つかってきたが、それでも思い出す長野の店についてここに書く。

あじ

https://tabelog.com/nagano/A2001/A200101/20001074/tabelog.com

一店舗目だし、景気良く焼肉屋を紹介したい。蕪村と千石劇場の間にある老舗焼肉屋だ。良い肉は量を食べられないということをおれはこの店で知った。口コミや評価が食べログに全く無くてビビるだろうが、若者が行かない高級店はインターネットで正しく評価されにくいのだろうか。"高級店"と書いたが、前述の通り、良い肉は多くは食べられないのでひとつひとつのメニューは高く感じても5千円も飲み食いすれば「今日は贅沢しちゃったナ」という気持ちになる。インターネットに口コミは無くとも、ここは人気店なので予約は必須。おすすめ。東京でこの肉を食べようと思ったら5千円じゃすまないぞ。

かかし

https://tabelog.com/nagano/A2001/A200101/20006460/tabelog.com

あじ源を紹介したのだし、隣のかかしも紹介しよう。こっちは老舗串カツ屋だ。この店に来たら食べ放題をやってほしい。35本以上食べると「短冊に名前とメッセージを書け」と言われる。その短冊は店に飾られるので、店の中は壁から天井までびっしりと短冊が飾られている。おれはこの短冊に自分のサークル名と他人のTwitterアカウントを書いてきた。ぜひ見つけて欲しい。

35本はなかなかたいへんだった。食べ放題には飲み放題も付いているが酒を飲んでいる場合ではない。値段は4,950円で普通だ。体調を整えて挑戦して欲しい。予約は要らないだろう。普通にふらっと行けば入れる。店長の耳が遠くなっているので注文は大声でする必要がある。

三本コーヒーショップ

https://tabelog.com/nagano/A2001/A200101/20005282/tabelog.com

老舗の通りを攻めているので、また近場の老舗を紹介したい。"みつもと"コーヒーショップと読む。外観からして最高。入ると更に最高で、大抵の場合はウェイター制服の爺ちゃんがカウンターに座りタバコを更かして新聞紙を読みながら客を待っている。注文をとると科学の実験器具みたいな道具でコーヒーを落とす。ブレンドコーヒーが美味いが、カフェオレを頼むと楽しい。爺ちゃんが右手にミルク左手にコーヒーでビャーっと高いところからコップに落として作ってくれる。値段は普通だ。スタバと変わらない。

アルデンテ

https://tabelog.com/nagano/A2001/A200101/20000202/tabelog.com

また老舗か?と思っているだろうが、また老舗だ。40年以上ここで店をやっているらしい。パスタ麺が丸くて太いのが特徴で食べごたえがある。ナポリタンやポモドーロなど一般的なメニューもあるが、ここは和風のオリジナルパスタが数種類あって、おれはここのアサリしめじ納豆がすきだ。パスタに納豆入れると美味いということをここで知った。値段はラーメン一杯分くらいで普通だ。食後にはコーヒーが付いてくる。コックさんがいかにもな白いコック帽をかぶっていて最高。

うらん

https://tabelog.com/nagano/A2001/A200101/20008090/tabelog.com

なんとなく書いていたら千石劇場周辺に固まってしまった。最後にうらんも紹介しよう。店の名前にもなっているとおり卵料理がめちゃめちゃ美味しい。種類もたくさんあるので、前行ったときは卵ばかり食べてしまった。魚も美味い。地酒も種類がある。外から店の中を除くとカウンターしかなくてビビるだろうが二階に座敷もある。研究室の打ち上げとかでも使えるんじゃないかな。

疲れた。まだまだあるけど一旦千石劇場周辺編ということにしたい。反響があればまた書く。

「Ruby on Rails 5アプリケーションプログラミング」の環境をdocker-composeで作る。

背景

仕事でrailsを使うことになったがきちんと触ったことが無かったのでGWに試してみることにした。

railsの入門書はこれが良いらしいので買った。書籍ではローカルに環境を構築しているが、dockerを使ったほうが良いだろう。

Ruby on Rails 5アプリケーションプログラミング

Ruby on Rails 5アプリケーションプログラミング

docker-composeを用いたrailsの環境構築は公式にドキュメントが用意されているが、rubyrailsのバージョンが本で解説されている動作環境とは異なるので、合わせた環境をdocker-composeで構築してみる。また、本だとDBにSQLiteを使っているが現実的にはMySQLのほうが良いのでここも変えたい。

クイックスタート・ガイド:Docker Compose と Rails — Docker-docs-ja 17.06.Beta ドキュメント

動作環境

本の動作環境はこのようになっている。

Docker HubのrubyのイメージはCentOSが無いので妥協する。DBはMySQLを使うことにする。

https://hub.docker.com/_/ruby/

Dockerfile

上にも貼ったdocker-composeの公式のドキュメントを見ながら本の動作環境にバージョンを揃える。

FROM ruby:2.4.0
RUN apt-get update -qq && apt-get install -y build-essential mysql-client nodejs
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
ADD . /myapp

差分

  • rubyのバージョンを2.2.0から2.4.0へ変更。
  • apt-getでインストールするパッケージをlibpq-devからmysql-clientに変更。

メモ

  • apt-get update -qq-qq はエラー以外表示しないオプション。
    • ダウンロードやインストールのログは大量に出るから、もしコケたときのデバッグがしにくくなるから表示しないようにしてるのかな。

Gemfile

source 'https://rubygems.org'
gem 'rails', '5.0.1'

空のGemfile.lockもドキュメントと同様に用意する。

$ touch Gemfile.lock

差分

  • railsのバージョンを4.2.0から5.0.1へ変更。

docker-compose.yml

version: '2'
services:
  db:
    image: mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

差分

  • dbのimageをpostgresからmysqlに変更。
  • MYSQL_ALLOW_EMPTY_PASSWORDを追加。

メモ

  • Dockerfileで Add . /myapp しているから、docker-compose.ymlで改めて volumes: .:/myapp としなくてもいいんじゃ?と思ったが、そんなことなかった。
    • 前者は bundle install するためだけのファイルコピーで、後者はローカルのファイルの変更をコンテナに渡すために必要だった。
  • depends_onディレクティブは「dbを先に立ち上げよ。」という意味になるようだ。ただし、コンテナの立ち上がる順序は指定できても、立ち上がりシーケンスが終了したかどうかは確認してくれないようだ。これによって起こる問題は後述する。
  • MYSQL_ALLOW_EMPTY_PASSWORDは設定しないとMySQLが立ち上がってくれない。
    • 最初はこれを環境変数に設定せずにいたら立ち上がらなかった。docker logs でログを確認したところこれを設定しろというメッセージが出ていた。

rails new

$ docker-compose run web rails new . --force --database=mysql --skip-bundle

差分

  • --databaseオプションで指定するデータベースをpostgresqlからmysqlへ変更。

メモ

  • --force は上書き許可オプション。
  • --skip-bundleは bundle installを行わないオプション。
    • bundle installはDockerfileでやるからここではスキップするのかな。

docker-compose build

ここはドキュメントと変わらない。

rails new したことによってrailsのファイルがローカルに振ってきて、Gemfileも更新されている。これを編集してJavascriptのランタイムを入るようにしてからdocker-compose buildする。

ここわかりづらかった。最初の docker-compose run web rails new . --force --database=postgresql --skip-bundle ではrailsしかインストールされてないミニマルなコンテナがビルドされる。そしてこれはrails newするためだけのコンテナになる。二回目の dockee-compose build でさっきrails newして降ってきたファイルも取り込んだコンテナが出来上がる。

差分

  • なし

データベース接続

ドキュメント通り、docker-compose uprails serverが立ち上がる。ブラウザで http://localhost:3000 を確認すると、上手く行っていれば、 Unknown database 'myapp_development' というエラーが表示される。また、ブラウザで直ぐに確認した場合は Unknown database servber 'db' というエラーも見える。これは前述した立ち上げ処理の終了まで面倒を見てくれないため、MySQLの起動よりも先にrails serverが立ち上がってしまうことが原因。こっちが出た場合はしばらく待つと前者のエラーに切り替わるはず。

差分

  • なし

データベース作成

ドキュメントどおり、 docker-compose run web rails db:create とすることでデータベースが作成される。改めて docker-compose up して http::/localhost:3000 にアクセスすると繋がるようになる。

f:id:roronya:20180430190754p:plain

差分

  • なし

メモ

なんでdocker-compose runの結果が、改めてdocker-compose upしても保持されたままなの?

まず、docker-compose upしてCtrl-Cで止めた後、再度docker-compose upするとコンテナを作り直す。よって、docker-compose runの結果が保持されることは無いはず。しかし、コンテナにマウントされたボリュームは作り直さないようだ。

マウントしているボリュームは、そのまま保持します

引用元: up — Docker-docs-ja 17.06.Beta ドキュメント

ということなので、MySQLのイメージはどこかにボリュームを持っているらしいことが推測できる。それで調べるとやはり持っているようだ。

実はデフォルトで永続化されるように設定されています。

引用元: Dockerの公式MySQLイメージの使い方を徹底的に解説するよ · DQNEO起業日記

というわけなので docker-compose run web rails db:create でデータベースが作成されるが、その変更はホスト側のボリュームに書き込まれ、改めて docker-compose up でコンテナを作り直してもデータベースは作成されたままというわけだった。

データベースを永続化する。

この手順はドキュメントに無い。

以上までやっても docker-compose down して再度 docker-compose up すると、 Unknown database 'myapp_development' というエラーが出る。上述の通り、デフォルトで永続化されてはいるので、そのパスをボリュームにすれば解消される。しかし、ホスト側からは見えづらいパスにボリュームがある(docker inspectするとパスがわかる)ので、明示的にマウントするボリュームは指定しておいた方が使い勝手が良い。

version: '2'
services:
  db:
    image: mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
    volumes:
      - ./tmp/db:/var/lib/mysql
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

差分

  • ボリュームを./tmp/dbに指定した。

これで開発環境は整った。