scramble cadenza

技術ネタのガラクタ置き場

初めての人がなんとなく Go で CLI を作ってみた時のまとめ

イントロ

ちょっと昔に 「みんなのGo言語」が良かったので、自分のためだけのCLIツールを作ってみた - えいのうにっき を読んで、なんとなく何か作りたい気分になっていた。

Go 言語の知識が皆無の状態から 自作 CLI 作ったので、その時のメモをまとめてみました。
効率が良いとは思いませんが、こういう学習パスを通ればものは作れるぞ、という一例として参考になれば。

まず最初に読む

Go 言語で CLI 作るときの知見

「Go」 で「CLI」というと、過去に良い記事があったような記憶があったので、
探し出して、読み直してみた。

どちらもすごい良い記事で、更に気分が高まる。

package を探す

Go で CLI 支援パッケージ絶対あるやろ、と思ったらやっぱりあった。
めんどくさがりな自分でもできそうな気がしてきて、更に気分が高まる。

開発環境

作業ディレクトリ(Workspace って言うらしい)

Goコードの書き方 - The Go Programming Language GO:Workspace と GOPATH - Web系開発メモ

多くの Go言語プログラマは、1つの GOPATH(1つの Workspace)で、複数(or 全て)の ソース・パッケージ・リポジトリ・依存性を管理しているようです。

好きなディレクトリで作業できないのはちょっと意外。
回避方法はあると思うが、深くは追わず Go ってそういうものなんだ、で一旦飲み込んだ。 経験者の意見を伺いたいところ。

GitHub で公開することを想定すると、$GOPATH/src/github.com/{repo} みたいなところで作業をすることになる。

Emacs

go-mode + go-autocomplete を導入。
先人の知恵は素晴らしい。

学習

A Tour of Go

一通り流した。なんとなくイメージが見えてきた。

A Tour of Go

CLI 支援パッケージまわり

早速調べてあったパッケージを使い始める。
私が触ったのは以下の 2 つ。

  • spf13/cobra
  • urfave/cli

spf13/cobra

spf13/cobra: A Commander for modern Go CLI interactions

有名コマンドも採用実績があり、サブコマンドを作りやすいのが嬉しいところ。 gif 見ればなんとなく使い方がわかる、というところも良いと思った。

が、作っていくうちに「今回のやりたい内容を考えると、サブコマンド要らないのでは」と思い直し、方向転換した。 残念ながら軽く触る程度で終わってしまったが、次のチャンスがあればまた触ってみたい。

urfave/cli

urfave/cli: A simple, fast, and fun package for building command line apps in Go

cobra を止めた後に選んだのはこちら。 star 数と README のわかりやすさが決め手。

他のパッケージも眺めてみたが、大体これと似ていたので、どれでもいいなら star 数で選ぶというミーハーっぷりを発揮。

依存関係の解決

自分の GOPATH には既に別のパッケージが散乱していて、汚れまくっていたので、rubybundler 的なやつが欲しかった。

そこで Big Sky :: golang オフィシャル謹製のパッケージ依存解決ツール「dep」 を発見。

なんとなくの気分で使ってみるが、まだ使いこなせていない。 まだ Go 言語に慣れてなくて、dep の全体を把握するのが難しいのもある。

それと、この時に Go の依存関係解決についてよく理解していないことに気づき、以下の資料を見て学んだ。

Goと依存パッケージ管理

それ以外の学習

後は思いついた疑問を赴くままにググりまった。効率が悪いがしょうがない。
以下はまだ読んでないけど、時間がある時にまとめて読みたい。

感想

  • 恥ずかしながら型言語を触って何かを作ったのは初めての経験。
    • 戻り値の型がわかるとコードが読みやすい。
    • 書き方がオブジェクト指向っぽくはならず、右往左往している。
  • すぐ panic になってしまう Go は、ちょっとかわいい。
  • 配列やマップの扱いが難しかった。もっとこう… 何かあるだろ、と思う場面が多い。
    • 慣れてないだけかも。
    • こういう時、型が無い方がいいなーと思う。
  • わかっていないことは非常に多い
    • 依存関係の解決
      • dep で解決できるのは、開発環境で使用するパッケージの指定。
      • リリースして go get すると失敗する可能性は残されている。
    • reflect パッケージ周り
      • まだ腹落ちしてなくて、もうちょっと慣れが必要
    • 作業ディレクト
      • 今は $GOPATH/src/github.com/{repo} で開発してる
    • ソースコードのファイル分割と import まわり
  • 結局は A Tour of Go を一通りやって、後は勢いで押し切っただけ
    • そういう意味では「何でもいいから作りたいものがあるかどうか」が一番大事で、難しいのだよなーと思った
    • 日頃の仕事を不便だと感じる気付きが重要となる

できあがったもの

完全に自分用ですが、terminal からコードレビューを依頼する CLI を作った。

仕事環境が変わり、レビュアーが曜日ごとに変わるようになってしまったので、レビュアーを自動特定できるようにした。
「今日は誰にレビュー依頼するのだっけ」「その管理シートどこにあるのだっけ」みたいなことをコードレビューのたびにやっていて、スーパー非効率だったのを改善できて良かった。
(レビュー体制をなんとかしたほうがよいのでは、という論点はあるのだが…)

mgi166/review-request: Request code review in terminal.

とはいえ、毎回固定のメッセージで依頼を投げてしまって bot っぽい。
バレないようにするためにも、ランダムでメッセージを変えるとか、人間っぽさを出していきたい。

terraform quick start 的なメモ

イントロ

terraform の初期化を行うのはプライベートでは二回目。
以前まとめていた自分用メモを、少し整形して公開してみる。

terraform はなんぞや、というところはすっ飛ばし、もう一度手元で terraform を使う環境を構築する人向けです。
内容は今更感がありまくりで、目新しいことは何もなし。

(なお、個人的な趣味により、provider は aws で、backend は s3 にしていますし、その前提で記事を書いています。)

terraform を始める

まず terraform init コマンドを実行する。

実行するとカレントディレクトリに .terraform というディレクトリが掘られていて、ここに terraform.tfstate というファイルが作成される。

この terraform.tfstateterraform が管理するリソースを json 形式で書いたもの。
commit するのは注意が必要。
Command: init - Terraform by HashiCorp

terraform init -backend=s3 \
-backend-config="bucket=terraform-bucket" \
-backend-config="region=ap-northeast-1" \
-backend-config="key=terraform.tfstate" \
-backend-config="profile=profile_name" .

terraform init コマンドの補足

backend とは

tfstate 置き場の事。
-backend=s3terraform.tfstate の置き場所を s3 にする、という設定。

tfstate の管理について

terraform はローカルにある terraform.tfstate を元にコマンドが実行される。 したがって、複数人で開発する時、各個人のローカルで持っている terraform.tfstate の状態に差があると、terraform の実行結果に差が出てしまう。

ファイルの状態によっては、A さんは EC2 が作られていて、B さんは EC2 が作られていない、という矛盾した状況にもなり得る。こうなると、どちらが正しいかわからなくて困ってしまう。

現在実際に構成されている AWS のリソースが常に正であり、その状態は常に tfstate は反映されてなければならない。

なので、複数人で開発する場合は、全員が最新の terraform.tfstate を参照する必要がある。

State - Terraform by HashiCorp

tfstate を s3 で管理する意味

全員が最新の tfstate を参照する方法の一つとして、s3 に terraform.tfstate を置く、というやり方がある。

A さんが AWS の状態が更新したら terraform.tfstate をローカルで上書きして、s3 に保存する。
B さんは s3 の terraform.tfstate をローカルに反映させて、terraform.terraform を実行する、というみたいな仕組み。(多分)

なお、terraform init 以外でも、terraform remote config コマンドを実行することで terraform.tfstate を s3 で管理する指定ができる。

Remote State Backend: s3 - Terraform by HashiCorp

terraform remote config \
    -backend=s3 \
    -backend-config="bucket=terraform-bucket" \
    -backend-config="key=terraform.tfstate" \
    -backend-config="region=ap-northeast-1"

provider の設定

Provider: AWS - Terraform by HashiCorp

どちらかを選択する

  • access_key, secret_key セットする。
  • profile をセットする

access_key をセットする

provider "aws" {
   access_key = "${var.aws_access_key}"
   secret_key = "${var.aws_secret_key}"
   region = "us-east-1"
}
  • access_key or 環境変数 AWS_ACCESS_KEY_ID をセットする
  • secret_key or AWS_SECRET_ACCESS_KEY をセットする
  • region or AWS_DEFAULT_REGION をセットする

profile をセットする

provider "aws" {
  region  = "ap-northeast-1"
  profile = "profile名"
}

最小限の構成

環境変数を適切にセットしている前提であれば

provider "aws" {}

に落ち着く。

これからどうするの?

後は普通にドキュメント見ながら resource を定義していくだけで OK。
aws の resource はこちらにまとめられている。

Provider: AWS - Terraform by HashiCorp

terraform import 手順

terraform には、現在 AWS に構築されている環境を terraform.tfstate に落とし込む機能がある。
それを terraform import と呼ぶらしい。

Import: Usage - Terraform by HashiCorp

terraform import resource名.名前 {id}

というコマンドを叩く。
resource 名は import 対象の resource。名前は terraform で管理したい名前。{id} は resource によって異なる。
{id} は勘でなんとかなる気がします。

例えば aws_route53_zone という resource の場合は、以下のコマンドで import できた。

terraform import -backup=backup aws_route53_zone.mgi166 {zone_id}

このコマンドで何が起こるかというと、ローカルの terraform.tfstate に、aws_route53 の設定内容が反映される。

後は、自前で resource を定義しながら、terraform plan との差分が無くなるまで、 tf ファイルを試行錯誤すれば良い。
というのも、差分が無くなるということは、import コマンドを叩いた後の terraform.tfstate と、tf ファイルに書いたコードの内容が同じであることを意味するからです。

しかし、terraform import は完全ではないようで、差分をなくすのは難しい場合がある。
具体的には一部の属性が変わったりしてしまう。
私はどうしようもないと諦めてしまったけど、このあたりの良い解決方法があれば教えていただきたいです。

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but
will not be persisted to local or remote state storage.

aws_route53_zone.mgi166: Refreshing state... (ID: xxxx)
aws_route53_record.www: Refreshing state... (ID: xxxxx CNAME)

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.


# force_destroy が `""` だったのが、"false" になっている
~ aws_route53_zone.mgi166
    comment:       "HostedZone created by Route53 Registrar" => "Managed by Terraform"
    force_destroy: "" => "false"

まとめ

  • terraform init or terraform remote config で backend の設定を作る
  • 必要に応じて terraform import で既存のリソースを import する
  • 後は document を読んで頑張る

参考

ローカル環境構築を MItamae で自動化した話

イントロ

お仕事用 MacMac book pro に変わってしまった。
環境構築をやるのも久々だったのだけれども、dotfiles のボリュームが増えたのと、実はコード化出来ていない部分が多かったりして、かなり面倒だった。

ちゃんともう一度やり直そう、と思って選んだのが MItamae

  • ansible は活用例が多いっぽいけど、yaml だと表現力が気になる(食わず嫌い)
  • Itamae は良さそうだけど、gem をインストールすることで、環境汚すのはちょっと嫌だ。ruby に依存するし。
  • となると chef も同様
  • puppet は思った以上にデカかった
  • boxen はへんじをしない。ただのしかばねのようだ
  • go で環境構築したら、Mac 以外の環境構築も楽になるのでは説を思いつく
  • itamae-go を発見
  • そういえば mrubyItamae ってなかったっけ
  • Itamaeのmruby実装「MItamae」が大体いい感じになった話 - k0kubun’s blog を発見
  • MItamae にしてみるか

みたいな流れで行き着きました。

今まではどうしていたか

rake で色々やってた。
自前で rake task を定義していて、rake コマンド叩くことで各種 dotfile の配置が完了する。
shell よりは書きやすいと思ったのと、ruby の勉強のために rake を使っていた、という経緯がある。

実際にやってみてどうだったか

mruby がほんの僅かに理解できた

結論から言うと MItamae を使うだけなら、mruby の学習自体は殆ど要らなかった。
ただし mruby でいう gem の取扱いは、CRuby と全然違うので、ここを知らないと大分混乱する。

mruby と mgem と rbenv と - Qiita

MItamae の install は簡単

Mitamae のバイナリが github で配布されているので、curl でダウンロードでおk。

ここが mruby のすごいところで、CRuby とは違う点。
今までは gem を install する時には、ruby を入れて、gem install コマンドを実行していたけど、mruby の gem は、gem 自体それぞれ binary になっているようだ。
(正しい表現かはわからない)

ディレクトリ構成は chef っぽく落ち着く

シンプルな Chef なので、書き方は自由。
だが、試行錯誤していくうちに、結局 Chef みたいな構成に落ち着いていった。
Itamaewiki にも(Chef みたいな構成とは書いてないが)ベストプラクティスが載っている。

Best Practice · itamae-kitchen/itamae Wiki

レシピの実行順序は ruby で担保する

chef で言う run_list 的なやつは MItamae には存在しないっぽい。

どうやるのが良いかわかってないが、今のところ mitamae local xxx.rb コマンドで渡す ruby のコードの中で、include_recipe を順番に並べて管理している。

MItamae で使っている gem を見る

mitamae の DSL の中で、どの程度自由にやれるのかというと、以下のファイルを見れば良いっぽい。

mitamae/mrbgem.rake at master · k0kubun/mitamae

add_dependency に追加されている gem は使える。
DSL の中からやりたいこと、という意味では大体揃っている。

結果

dotfile がすごいすっきりした。嬉しい。
.emacs.d とか .bundle 等の実体は cookbooks の中に押し込んでいる。

mgi166/dotfiles: dotfiles

おわり

k0kubun/dotfiles: Configuration management of development environment がよく出来すぎていて、大体まねっこになってしまった。

MItamaemruby もすごい便利です。

参考

esa.io クライアントを kotori から Quail に乗り換えた

イントロ

職業柄、自分のフィーリングとマッチしているというのもあるのだと思うけど、esa.io を一年運用していて全く困らないし、本当に助かってる。

しかし一点だけ、esa.io というより、OSXesa.io クライアントに不満があった。

それは kotori というクライアントを使っていたのだけど、「リンクをクリックするとクライアント内で画面遷移し、二度と戻れない」という凶悪な仕様だ。

kotori で書いた記事の中でリンクをクリックするの図はこちらです。

f:id:mgi:20170106183808g:plain

これが超辛い。

esa.io では Qiita みたいなリアルタイムプレビュー機能がついていて、右側で markdown の変換結果が見れる。
ところが記事書いている途中にうっかりリンクをクリックしたらもうゲームオーバーで、編集画面には二度と戻れない。

じゃあどうするかというと kotori を強制終了してやり直すしかない。

長文を途中保存していないと全て消えてしまう。これで何回か枕を濡らした。

今までの暫定策

とはいえ、それ以外の kotori に不満は一切なくて、運用でカバーしてた。

  • ⌘ + S をなるべく頻繁にタイプする
    • このショートカットで一次保存できる(素晴らしい)
  • スクロールする時、リンクのそばにカーソルを動かさない

とか。
kotori 先輩に鍛えられたおかげで、「これは危ない」という操作が直感でわかるようになってきて、かなり誤操作は少なくなったきた。

半分あきらめてたけど、最近知ったこと

help/サードパーティーesaツール - docs.esa.io を今更見つけて読んだ。
そこで 1000ch/quail というアプリがあるようなので、試してみたのがきっかけ。

インストール方法

1000ch/quail に書いてあるとおり。 zip 落として Applications にコピーするだけ。

electron 製ですね。

使ってみた感想

リンクをクリックすると、ブラウザで開いてくれる。

f:id:mgi:20170106184427g:plain

涙が出るほど嬉しい。初めて触った時嬉しくて声が出ました。

正直 kotori のほうが軽い気がするし、画面遷移も早い。
それに Quail でボタンを押した時に一瞬白くなるのは気になる。
けど、この機能には代え難い。

終わりに

私しかこの件で枕を濡らしてないのかもしれないけど、長年辛かったことが解決した事が本当に嬉しいので、記録しておく。

昔は kotori しか存在しなかった気がしたけど、今は iOS 版もあるみたいだし、みんな徐々に esa.io の良さに気づき始めているのでしょうか。
インターネットのすみっこから応援しています。

しかし、Quail になった今でも、リンクをクリックするのはドキドキするという。。

serverless + webpack + babel で AWS Lambda をナウく書く

イントロ

最近 AWS Lamdba を仕事で触っています。
serverless やら Apex なり色々あるわけですが、沢山ありすぎてよくわかりません。

今までは Apex の手軽さに甘えていましたが、そろそろ serverless が本気を出してきたという噂を聞き、serverless を使い始めています。

どうせ serverless を使うなら、もう全部使い倒してやろうということで、babel も意識高く使っていきたいわけです。

この記事では serverless + babel を使うところまでをまとめました。

環境

  • serverless@1.1.0
  • serverless-webpack@1.0.0-rc.2
  • webpack@1.13.3

serverless

まずは雛形作成

# template 指定は必須
# `sls` は `serverless` コマンドの alias 
$ sls create -t aws-nodejs

serverless.yml というファイルが設定ファイル。
これを早速いじっていく。コメントや公式のドキュメントがわかりやすい。

# serverless.yml を編集していく
$ emacs serverless.yml
$ git diff serverless.yml
 provider:
   name: aws
   runtime: nodejs4.3
+  region: ${env:AWS_REGION}
 functions:
-  hello:
-    handler: handler.hello
+  imageResizer:
+    handler: index.handle
# service の deploy
$ sls deploy

# AWS Lambda 関数の deploy
$ sls deploy function -f imageResizer

# AWS Lambda 関数の実行
$ sls invoke -f imageResizer

# Log の tail
$ sls logs -f imageResizer -t

まず最初に sls deploy で Service の deploy を行う。
この Service というのは serverless 用語の一つで、AWS Lambda を実行するための AWS インフラ全体を指す名称。

この Service を管理するために serverless は CloudFormation を暗黙的に使用している。
うっかり region 指定を忘れると、sls deploy を実行した時、serverless デフォルトの region である us-east-1 に、 CloudFormation の Stack が出来上がってしまうので要注意。

region はコマンドラインの option で渡すか serverless.ymlprovider で指定する。

serverless-webpack

serverless を使って deploy はできるようになった。後は書いたコードを babel を使っていく。
serverless-webpack プラグインがあって、これを使えば OK。

elastic-coders/serverless-webpack: Serverless plugin to bundle your lambdas with Webpack

外部の npm パッケージを使う場合

serverless-webpack を使用している場合、任意の package を handler の中で require しようとすると、 webpack コマンドで失敗してしまう。

$ git diff
--- a/index.js
+++ b/index.js
@@ -1,5 +1,6 @@
 'use strict';

+import gm from 'gm';

# sls コマンドは serverless コマンドの alias
$ sls webpack —out .webpack
...
ERROR in ./~/thread-sleep/~/node-pre-gyp/package.json
Module parse failed: /Users/argerich/dev/serverless-image-resizer/node_modules/thread-sleep/node_modules/node-pre-gyp/package.json Unexpected token (2:8)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (2:8)
    at Parser.pp$4.raise (/Users/argerich/dev/serverless-image-resizer/node_modules/acorn/dist/acorn.js:2221:15)

この場合、webpack-node-externals を使って、設定ファイルを調整する必要がある。
具体的には以下のような修正をする。

# webpack.config.js
+const nodeExternals = require('webpack-node-externals');
+
 module.exports = {
   entry: './index.js',
   target: 'node',
@@ -5,5 +7,6 @@ module.exports = {
     libraryTarget: 'commonjs',
     path: '.webpack',
     filename: 'index.js'
-  }
+  },
+  externals: [nodeExternals()]
 };
# serverless.yml

 custom:
   webpack: webpack.config.js
+  webpackIncludeModules: true

webpack を通した deploy は serverless deploy function -f では行われない。
serverless deploy で行う。

$ sls deploy

Serverless: Deprecation Notice: Starting with the next update, we will drop support for Lambda to implicitly create LogGroups. Please remove your log groups and set "provider.cfLogs: true", for CloudFormation to explicitly create them for you.
Serverless: Bundling with Webpack...
Time: 578ms
   Asset     Size  Chunks             Chunk Names
index.js  2.17 kB       0  [emitted]  main
Serverless: Packing external modules: babel-polyfill@^6.16.0, gm@^1.23.0
Serverless: Packaging service…
Serverless: Uploading CloudFormation file to S3…
Serverless: Uploading service .zip file to S3…
Serverless: Updating Stack…
Serverless: Checking Stack update progress…
.....
Serverless: Stack update finished…
Serverless: Removing old service versions…

Service Information
service: serverless-image-resizer
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  None
functions:
  serverless-image-resizer-dev-imageResizer: arn:aws:lambda:ap-northeast-1:xxxxxx:function:serverless-image-resizer-dev-imageResizer

✨  Done in 27.52s.

まとめ

  • region のデフォルトは us-east-1 なので、指定を忘れないこと。
    • serverless.yml に region を書いておく(ドキュメントの隅っこ にサラッと書いてある)
  • serverless-webpack で外部 package 使う場合は、webpack-node-externals を使う
  • deploy は serverless deploy function ではなく、serverless deploy を使う

という話でした。

Apex と Serverless 両方使ってみての感想

個人的には Apex のほうが学習コスト少なくて、機能もシンプルなので好き。

Serverless はもれなく CloudFormation が付いてきて、管理運用の面で色々思うところがある。AWS Lamda だけ触りたいんや! と思ってる人にとって、いきなり CloudFormation が出てくるとびっくりする。

けどその分 serverless は設定ファイルを柔軟に書けるし、プラグインは充実している。Apex で微妙だと思った環境変数周りを、うまくカバーしているところは好印象。

一方で Apex でしか出来ないことは Terraform との連携だけ。この機能にどれだけ期待するかが、一つの採用基準でもあると思う。

しかし、ただ単に AWS Lambda を使いたい、というだけであれば Apex で十分だし、落とし穴は少ない。 Apex で不満に思うことが増えてきたら serverless を検討する、で良いのかな。

と、当たり前の結論に至りました。

参考になる

おまけ

$ sls webpack —out .

とやると、現在のディレクトリが全て消されて途方にくれるので、 良い子のみんなは絶対に真似をしないでください

ファイル名で使えない文字

イントロ

web アプリケーションで、ユーザーにファイルをダウンロードさせる機能を追加したいとする。
例えばダウンロードさせるファイルの名前をアプリ側で自動決定する場合、その名前にはどのような制限があるのだろうか。

何回か調べてる気がするので、まとめてみる。

前提

そもそも、ファイル名の制限は、OS ではなく、基本的にはファイルシステムで決まる。

ファイルシステム上の限界

ファイルシステムの限界はどこまでなのかというと Comparison of file systems - Wikipedia, the free encyclopedia にまとめられている。

古いファイルシステムでは、ファイル名は 8 文字、拡張子が 3 文字、ドットも含めて合計 12 文字までしかダメ、という時代もあったらしい。8.3形式 と呼ばれているのがそれだ。

有名どころのファイルシステム(NTFS, FAT系, ext4, xfs)では

  • ファイル名の長さは 255 バイト or 文字まで
  • NUL だけがファイル名として使えない(一部のファイルシステムでは / も禁止されている)

という仕様が多いようだ。

Windows で禁止されている予約語

ただし Windows は例外がある。
ファイルシステムだけでなく、OS レベルで予約されている文字があり、これらはファイル名として使えない。

Naming Files, Paths, and Namespaces (Windows) によると、以下の文字が予約されている。

< (less than)
> (greater than)
: (colon)
" (double quote)
/ (forward slash)
\ (backslash)
| (vertical bar or pipe)
? (question mark)
* (asterisk)

手元に win 機が無かったので、残念ながら検証はしていない。
本当にダメなのか試してみたい。

結論

対応するファイルシステムのカバー範囲により、ファイル名に使える文字は異なる。
だが、現実的な落とし所として

  • windows を視野にいれるのであれば、windows で禁止されている予約語は使わないほうが良いと思う。
  • NULL文字も使えないファイルシステムが多いため、止めたほうが無難。
  • 長過ぎるファイル名は NG なので、そのあたりも調整する。
    • マルチバイト文字のときが面倒くさいが、より小さい方に合わせ 255 byte まで のほうががいいのかな。

ぐらいを考えておけば良さそうだろうか。

おまけ

手元の MacファイルシステムHFS+ だった。

f:id:mgi:20161010132555p:plain

https://en.wikipedia.org/wiki/Comparison_of_file_systems によると、制限は以下の通り

File system Maximum filename length Allowable characters in directory entries Maximum pathname length Maximum file size Maximum volume size
HFS Plus 255 UTF-16 characters Any valid Unicode Unlimited slightly less than 8 EiB slightly less than 8 EiB
# 制御文字のファイル名は作れる
`touch "\a"`
=> ""

# null 文字はダメだった
`touch "\x0"`
#  ArgumentError: string contains null byte
#        from (irb):1:in ``'
#        from (irb):1
#        from /Users/argerich/.rbenv/versions/2.3.0/bin/irb:11:in `<main>'

# 256 byte は長すぎる
`touch #{"a" * 256}`
touch: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: File name too long

# 255 byte なら OK
`touch #{"a" * 255}`
=> ""

# 日本語で 255 文字はアウト
`touch #{"" * 255}`
touch: あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ: File name too long

>> "".bytesize
=> 3

# 3 * 85 = 255 なので、85 文字まで OK
>> `touch #{"" * 85}`
=> ""

# 86 文字目からアウト
>> `touch #{"" * 85 + "a"}`
touch: あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああa: File name too long

# UTF-16 が OK ならば、絵文字でもいけるはず。
`touch #{"\u{26C4}" * 85}`
=> ""

f:id:mgi:20161010132646p:plain

絵文字ファイル名で作れているっぽい。ls すると ? になるけど。

f:id:mgi:20161010132710p:plain

それにしてもWikipedia 先生。ファイル名の長さ、255 byte までなのでは?
Any valid Unicode の中に null 文字は入ってないのかな?

一部の文字は互換性維持のため、OS の API で制御しているとの噂もあるけど… うーむ。
情報が少なすぎて、これ以上調べるのは難しそうだった。一次情報どこにあるの。

試してみたものの、Wikipedia を引用してまとめたので、すごい微妙になってきた…
この記事は用法用量を守って、正しくお使いください。

参考

AppStore でアプリのアップデートができなくなった件

イントロ

「この製品のディストリビューションファイルを検証できませんでした。
破損しているか、署名されていない可能性があります」

f:id:mgi:20160919201738p:plain

と出て、update できない。

状況

  • 上記のようなメッセージが出て、更新通知があるアプリを更新できない
  • 何故か更新できるアプリもある
  • キャッシュを消しても出る
    • 試したのは % sudo rm -rf ~/Library/Caches/* /Library/Caches/*
    • 乱暴なので、良い子は真似しないように。
  • /var/log/system.log に以下のようなメッセージが出力される
    • Assertion failure in -[CheckPreflightOperation verifyDistributionAtURL:allowsDevSign:allowsUnsigned:osVersionToBeInstalled:error:distributionController
# /var/log/system.log
Sep  2 11:54:11 --- last message repeated 1 time ---
Sep  2 11:54:11 argerich-no-Mac-mini iTerm2[348]: Time to encode state for window <PseudoTerminal: 0x7f8544b54630 tabs=1 window=<PTYWindow: 0x7f8542d2b920 frame=NSRect: {{0, 74}, {1918, 972}} title=1. Horowitz (tmux) alpha=1.000000 isMain=0 isKey=0 isVisible=1 delegate=0x7f8544b54630>>: 0.003149032592773438
Sep  2 11:54:15 argerich-no-Mac-mini storedownloadd[583]: *** Assertion failure in -[CheckPreflightOperation verifyDistributionAtURL:allowsDevSign:allowsUnsigned:osVersionToBeInstalled:error:distributionController:], /Library/Caches/com.apple.xbs/Sources/Commerce/Commerce-463.9/CommerceKit/CheckPreflightOperation.m:291
Sep  2 11:54:23 argerich-no-Mac-mini iTerm2[348]: Time to encode state for window <PseudoTerminal: 0x7f8544b54630 tabs=1 window=<PTYWindow: 0x7f8542d2b920 frame=NSRect: {{0, 74}, {1918, 972}} title=1. Horowitz (tmux) alpha=1.000000 isMain=0 isKey=0 isVisible=1 delegate=0x7f8544b54630>>: 0.004208028316497803
Sep  2 11:54:27 argerich-no-Mac-mini iTerm2[348]: Time to encode state for window <PseudoTerminal: 0x7f8544b54630 tabs=1 window=<PTYWindow: 0x7f8542d2b920 frame=NSRect: {{0, 74}, {1918, 972}} title=1. Horowitz (tmux) alpha=1.000000 isMain=0 isKey=0 isVisible=1 delegate=0x7f8544b54630>>: 0.004567980766296387

解消法

ゲストユーザーでログインし、アップデートしたら成功。
恒久対応ではない気がする。

アップデートを完了できませんでした。 | 公式 Apple サポートコミュニティ

Java 歴 23 分の Ruby エンジニアが Effective Java を読んで感動した話

イントロ

例外処理を書くことはよくやっているのだけれど、その時の主軸となる考え方について、今までなんとなくで行っていた部分が多かった。

毎回考えるポイントは例えば以下のような疑問。

  • どこのレイヤーで、どこまで例外処理を行えばよいのだろうか?
  • どの例外をキャッチし、どの例外を伝搬させればよいだろうか?
  • 前提条件をチェックし、失敗した場合、例外を出したほうがよいか、nil, false を返すほうがよいか?
  • 例外をどういう単位でラップさせるのが良いだろうか?
  • 例外をチェインし過ぎると却って煩雑になる気がする。どうすれば良いのだろうか。

しかし、この辺りの話って、API の設計だったり、仕様の影響もあるので、都度対応が異なってしまうもの。
したがって抽象化して理解することが難しく感じた。

とてもよく使ってるし、とても大事な事なことなのに。

そんな今更な事で悩んでいた時に、Effective Java という良い本を紹介してもらったので、例外処理の章を読んでまとめてみました。
(5 章まで読んで力尽きていたが、勇気を振り絞り、また読み進めてみた)

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)

読書メモ

項目.57 例外状態の時だけ例外にする

// 行ってはならない
try { 
  int i = 0;
  while(true) 
    range[i++].climb();
} catch(ArrayIndexOutOfBoundsException e) {
}
  • 例外に基づくイディオムはパフォーマンスが悪い
    • そのため、例外の目的がわからない場合は使ってはいけない
  • 例外は例外的条件の時のみに使用する。通常の制御フローに対しては使ってはいけない
    • コードの保守性を落とすだけでなく、パフォーマンスも悪くなるので
  • 通常の制御に例外を使用することをクライアントに強制してはならない
    • 例えば、予測不能な状態でのみ呼び出されるメソッドは「状態依存 」である
      • e.g) Iterator#next()
    • 一般には状態を判別する状態検査メソッドを持つべき
      • e.g) Itereator#hasNext()
    • 状態検査メソッドが呼ばれること無く、状態依存のメソッドが呼ばれた場合は、そうとわかるような特別な値を返す

状態検査メソッドを使うか、区別できる戻り値を使うか

区別できる戻り値を使う場合

以下の条件を満たすとき。

  • 並行してアクセスされる場合
  • 外部要因で状態遷移する場合

状態検査メソッドと、その変更メソッドを呼び出す間に、状態が変更になる場合があるから。

状態検査メソッドを使う場合

上記以外のとき。

  • 若干読みやすい
  • 状態検査メソッドを呼び出すことを忘れると、例外が投げられるため、バグに気づきやすい。

から。

項目.58 回復可能な状態にはチェックされる例外を、プログラミングエラーには実行時例外を使用する

チェックされる例外(checked exception)

  • 回復可能な状態であるときに発生する例外
  • 回復に役立つ情報を提供するメソッドを持つと良い

実行時例外(runtime exception)

  • 回復可能でない状態である場合に発生する例外 = チェックされない例外
  • 実行を続けても役にたたない、むしろ害になるような場合に発生する例外
    • RuntimeException をサブクラス化する
  • 「回復可能かそうでないか」を判別できない場合も「実行時例外」として扱うべき

エラー(error)

  • 基本的には使わない
    • API のユーザーの混乱を招くだけだから
    • 得られるものは殆ど無いから

項目.59 チェックされる例外を不必要に使用するのを避ける

チェックされる例外は、API の使用者に例外処理を強制させ、信頼性を上げる

しかし、チェックされる例外を過剰に増やすと逆に API を使いにくくする。 API を使う側は、以下の2つのどれかを強いられるため、多少の負荷がかかるから。

  • 少なくとも1つ以上の catch が必要
  • 例外を外側に伝搬させる

チェックされる例外を使う場合

  • API の適切な使用では例外処理を防ぐことができない
  • API を使用しているプログラマが例外に直面したときに、何らかの有効な処理を行える

この2つの条件をみたす場合に、上記の負荷が正当化される。(ただし CloneNotSupportedException は例外) それ以外の場合はチェックされない例外のほうが適切。

チェックされる例外を、チェックされない例外に変更する方法

上記で述べたように、チェックされる例外を使用し過ぎると、負荷が高くなる。 その方法が「例外を投げるメソッドを分割して、状態検査メソッドと、それ以外の処理に分割する」というもの

ただし、以下の場合は NG。

  • 外部同期なしに平行してアクセスされる場合
  • 外部要因により、状態遷移する場合
  • actionPermitted メソッドが action メソッドの処理を行う必要がある場合
    • (パフォーマンス上の関係で、この分割を行う必要がないので不要となる)
// チェックされる例外を使用する場合の呼び出し
try {
  obj.action(args);
} catch(TheCheckedException e) {
  // 例外処理
}
// 状態検査メソッドとチェックされない例外を使用する場合の呼び出し
if (obj.actionPermitted(args)) {
   obj.action(args);
} else {
  // 例外処理
}

項目.60 標準例外を使用する

既存の例外を再利用する

  • プログラマが熟知している確立された慣例と一致するため、使用が容易になるから
  • 見慣れない例外でごちゃごちゃしないので、API を使用するプログラムが読みやすい
    • 例外クラスが少ないことは、クラスのロードに費やされる時間が少ないことを意味する

よくある具体例

  • IllegalArgumentException
    • 不適切な引数を渡した時に発生する例外
  • IllegalStateException
    • レシーバーのオブジェクトが不正な状態ならば発生する例外
  • NullPointerException
    • null が禁止されているパラメータに対して、null を渡した場合など
  • IndexOutOfBoundsException
    • index を表すパラメータで、範囲外の値を呼び出した場合など
  • ConcurrentModificationException
    • 並行して変更されようとしている
  • UnsupportedOperationException
    • 行おうとした操作をオブジェクトがサポートしていない場合

また、エラーに関連する情報を付与したい場合は、自由に例外をサブクラス化すること。 再利用する時の例外をどう選択するか、は必ずしも厳密でない場合がある

項目.61 抽象概念に適した例外をスローする

例外翻訳とは

「上位レイヤは下位レベルの例外をキャッチし、上位レベルの抽象概念の観点から説明可能な例外をスローする」こと。例えば以下の様な例を指す。

// イディオム
try {
} catch (LowerLevelException e) {
  throw new HighLevelException(...)
}
// AbstractSequentialList からの一例
public E get(int index) {
   ListIterator<E> i = listIterator(index);
  try {
    return i.next();
  } catch(NoSuchElementException e) {
    throw new IndexOutOfBoundsException("Index: " + index);
  }
}

上位レベルの例外から、下位レベルの問題をデバッグするためには、例外連鎖を用いる

  • 上位レベルから下位レベルの例外を取り出すためのアクセサを用いる
  • 具体的には、スーパークラスの連鎖可能なコンストラクタに引き渡す
    • 殆どの標準例外は連鎖可能なコンストラクタを持っている
try {
  // Do something
} catch (LowerLevelException cause) {
  throw new HighLevelException(cause);
}

class HighLevelException extends Exception {
   HighLevelException(Throwable cause) {
    super(cause);
  }
}

例外翻訳は何も考えないで例外を伝搬させるよりは優れてるが、乱用すべきではない

  • 可能であれば、下位レベルのメソッドを呼び出す前に、それが成功することを保証する
    • (状態検査メソッドのこと?)
    • 引数を引き渡す前に、正当性を検査する
  • 不可能であれば、上位レイヤに黙って処理させる
    • 下位レベルの問題から、上位レベルのメソッド呼び出し元を隔離する事が次善策
  • 呼び出し元を隔離出来ない場合は、例外連鎖を使う

項目.62 各メソッドがスローする全ての例外を文章化する

例外がスローされる条件を Javadoc@throw タグで正確に文章化する

  • スローする例外に気をつけているプログラマの手引にならないから
  • クラスやインターフェイスを効果的に使用することが困難だったり、あるいは不可能だったりするから

チェックされない例外についても、注意深く文章化するほうが懸命

  • ただし、強制はしない
  • 書くことで、メソッドが首尾よく実行されるための事前条件を記述できる
  • これは必ずしも達成可能であるとは限らない
  • throw タグを使わない
    • チェックされない例外と、チェックされる例外を視覚的に区別するため

項目.63 詳細メッセージにエラー記録情報を含める

例外の toString メソッドが例外の文字列表現である

  • 文字列表現 = クラス名 + 詳細メッセージ
  • エラーの原因を表す文字列で、できるだけ多くの情報をかえす事が重要

例外の原因となった全てのパラメータとフィールドの値を含んでいるべき

  • 例) IndexOutOfBoundsException は下限範囲、上限範囲、範囲内の収まらなかった実際の index を返す。これは診断にとても役立つ情報

例外の文字列表現と、エンドユーザーに対してわかりやすいメッセージを混同すべきではない

  • プログラマにとっては、エラーを解析する情報の内容のほうがはるかに重要
  • 以下の様にコンストラクタを作成することを推奨
    • プログラマがエラーを記録することが容易になるし、逆に記録しないことが困難になるから
    • 高品質な文字列表現を生成するコードを一箇所にまとめる事ができるから
    • アクセサを提供することで、エラーからの回復に役立てることができるかもしれないから
/*
IndexOutOfBoundsException を生成する
@params lowerBound 最も小さな正当なインデックス値
@params upperBound 最も大きな正当なインデックス値に 1 を足した値
@params index 実際のインデックス値 
*/
public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
  super("Lower bound: " + lowerbound +  ", Upper bound: " + upperBound + ", Index: " + index);
  this.lowerbound = lowerBound;
  this.upperBound = upperBound;
  this.index = index;
}

項目. 64 エラーアトミック性に努める

エラーアトミックとは

  • 「失敗したメソッド呼び出しは、オブジェクトをそのメソッド呼び出しの前の状態にしておく」という状態
    • なるべくエラーアトミックに保つことを意識する

エラーアトミックを保つための方法

  • 不変オブジェクトを設計する
    • 変更できないので、エラーが出ても同じ状態であるから
  • パラメータの正当性を検査する
    • オブジェクトの変更を行う前に例外をスローすることができるから
  • 失敗するかもしれない部分を、オブジェクトを変更する部分よりも前に行う
  • 操作の途中で発生する例外を捉えて、状態を戻す「回復コード」を書く
    • 一般的ではない
    • 主に永続的なデータ構造に対して使用される

ただし、エラーアトミック性は必ずしも達成できるとは限らない。例えば複数スレッドが同期なしに、同一オブジェクトを同時に変更する場合など。

メソッドがエラーをスローする場合は、一般的には回復不可能であり、エラーアトミック性を保持しなくてよい

  • たとえ回復可能でも、必ずしも望ましいとは限らない
  • 例外は回復可能である場合がある。対比に注意
  • メソッドの仕様の一部である例外は、エラーアトミック性を保持するべき
// 最初のサイズ検査が無くても例外を投げる
// しかし、それだと size フィールドを不正号な状態にしてしまい
// オブジェクトに対するその後の呼び出しを失敗させてしまう
public Object pop() {
  if (size == 0)
    throw new EmptyStackException();
  Object result = elements[—size];
  elements[size] = null; // 廃れた参照を取り除く
  return result;
}

項目.65 例外を無視しない

例外を無視してはいけない理由

  • 例外の目的が達成されないから
    • 例外の目的とは、例外的状態を処理させることを強制する、こと
    • 火災報知機を無視して、警報を切ってしまうのと同じ
    • 無視する場合はコメントに含んでいるべき
  • エラーをも無視してしまうから
    • 原因とは何も関係のないところでエラーになる可能性がある
    • 例外は、外に伝搬させるだけでも有益な情報を残してくれる
// ダメ、ぜったい
try {
} catch (SomeException e) {
}

感想

普段は ruby を書いているが、早速活かせそうな話ばかりだったので、試してみたい。

例外処理を書く時の指針が揃っていたし、その理由も納得できるものだった。特に「チェックされる例外」と「チェックされない例外」という考え方は自分が特に迷っていた部分だったので、参考になった。 例外連鎖を使うときにも指針ができた。

Java は型あるので、擬似コードが理解し易くていいですね。

こういうプログラミング全般?の話というか、プログラミングテクニックというか、そういう本って他にもあるのかな。
基礎力が無いのでもっと読んだほうが良い気がする。Effective Java も更に読み込んでいきたい。

2017/11/22 追記

かなりスペースを取ってしまってますが、冒頭に目次を追加しました。
忙しい人でも目次だけ読んで、気になったところだけ読めるようにする意図を込めてます。

rails 5 を今更触ってみた話

イントロ

rails5 の主要機能は色々と知られている通りだけど、業務に投入するとなると、やっぱり互換性だったり、細かいコードの違いが気になるもの。

今回 authlogic を使ったログイン機能を雑に作ったので、その限られた狭い範囲であるものの、ハマったところや違いをまとめてみた。

なんで authlogic ?

  • 昔業務で使ってたから。そしてまた使うかもしれない。
    • なお devise は初心者の頃使って見事に爆死し、それ以降使ってない。
  • そこそこ古く、歴史ある gem
  • シンプルな機能だけを提供してくれている
    • だから好き。
    • (ただし、中身はかなり癖のある実装)
  • 昔のおさらい。復習を兼ねて。

環境

  • rails (5.0.0.rc2)
  • authlogic (3.4.6)
  • rspec-core (3.1.7)

気づいたこと

authlogic は rails5 対応中

結論から言うと、今は使えなかった。
しかし対応 PR や issue も立っているので、しばらく傍観モード。

全くメンテナンスされていないわけではなさそうなので、少し安心した。

サンプルを作成する時には、vendor/bundle 以下に install されている authlogic を直接修正して、無理やり動かすことになってしまった。

トップページが….

初めて rails s して localhost:3000 にアクセスするとこんな感じ。かっこいい。

f:id:mgi:20160626180707p:plain

ログを見ると以下のようになっている。

Started GET "/" for 127.0.0.1 at 2016-06-25 22:55:12 +0900
  ActiveRecord::SchemaMigration Load (0.6ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by Rails::WelcomeController#index as HTML
  Parameters: {"internal"=>true}
  Rendering vendor/bundle/gems/railties-5.0.0.rc2/lib/rails/templates/rails/welcome/index.html.erb
  Rendered vendor/bundle/gems/railties-5.0.0.rc2/lib/rails/templates/rails/welcome/index.html.erb (4.5ms)
Completed 200 OK in 24ms (Views: 11.3ms | ActiveRecord: 0.0ms)

Rails::WelcomeController#index というのが呼ばれている。 書き換えたいときは今までどおり、root を設定すればいいっぽい。

controller のテストが rails-controller-testing に切り出されている

assigns が使えなくて、scaffold で作ったテストが NoMethodError で落ちる。 assigns を使いたければ rails/rails-controller-testing を各自で install して使う仕組みになったようだ。(対応したのはもっと昔なのだろうけど、知らなかった)

rspecminitest とは違って、全部まるごと入ってるのが嬉しいから、個人的にはちょっと残念。今後の方針がどうなるかは知らないけど、テストのために色々な gem を探したり install するのは大変だし、面倒だと思う。その面倒さが無いのが一番の利点だと思ってたので。*1

prepend_before_filter が deprecated

authlogic の内部で書かれている。 なので bundler で環境を読み込むたび出てくるので鬱陶しい。

$ rails c
DEPRECATION WARNING: prepend_before_filter is deprecated and will be removed in Rails 5.1. Use prepend_before_action instead. (called from require at /Users/argerich/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bundler-1.11.2/lib/bundler/runtime.rb:77)
Loading development environment (Rails 5.0.0.rc2)
>>

メッセージにも書いてあるとおり、prepend_before_action を使えば警告を解消できそう。

Fix deprecation warning: prepend_before_filter in Rails 5 by bparanj · Pull Request #491 · binarylogic/authlogic

use_transactional_fixtures が deprecated

rspec-railsuse_transactional_fixtures が deprecated になっている模様。 代わりに use_transactional_tests= を使えとあるけど、rspec-3.1.7 では対応されておらず、NoMethodError で弾き返される。

$ rspec
…
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead) (called from <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/controllers/top_controller_spec.rb:3)
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead) (called from <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/controllers/user_sessions_controller_spec.rb:3)
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead) (called from <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/controllers/users_controller_spec.rb:21)
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead) (called from <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/models/user_spec.rb:3)
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead) (called from <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/requests/users_spec.rb:3)
…

Deprecation warning: use_transactional_fixtures= is deprecated · Issue #1549 · rspec/rspec-rails を見ると、master branch では対応しているっぽい。

ActionController::TestCase

get url, parameter, session でお馴染みの、リクエストを送るメソッドの引数が、HTTP method だけになる模様。

get :show, params: { id: 1 }, session: { user_id: 1 }
process :update, method: :post, params: { id: 1 }
 (called from block (3 levels) in <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/controllers/users_controller_spec.rb:57)
DEPRECATION WARNING: ActionController::TestCase HTTP request methods will accept only
keyword arguments in future Rails versions.

具体的には、以下の様な修正を行えばおk。
引数に名前がついて、わかりやすくなった。地味に嬉しい。

しかし、これは多分運用しているテストを直さないといけなくなるのかなー。

--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -60,7 +60,7 @@ RSpec.describe UsersController, :type => :controller do

   describe "GET new" do
     it "assigns a new user as @user" do
-      get :new, {}, valid_session
+      get :new, params: {}, session: valid_session
       expect(assigns(:user)).to be_a_new(User)
     end
   end

ログが綺麗になってる

前はもっと asset 周りのログが沢山流れていたが、今は出ない。
すっきり。

Started GET "/" for 127.0.0.1 at 2016-06-26 00:09:06 +0900
Processing by TopController#index as HTML
  Rendering top/index.html.erb within layouts/application
  Rendered top/index.html.erb within layouts/application (11.4ms)
Completed 200 OK in 40ms (Views: 34.7ms | ActiveRecord: 3.1ms)


Started GET "/login" for 127.0.0.1 at 2016-06-26 00:09:12 +0900
Processing by UserSessionsController#new as HTML
  Rendering user_sessions/new.html.erb within layouts/application
  Rendered user_sessions/new.html.erb within layouts/application (3.9ms)
Completed 200 OK in 66ms (Views: 64.3ms | ActiveRecord: 0.0ms)

last_comment が deprecated

Rake.application.last_comment が deprecated らしい。 rspec-core3.1.7 では、ここRake.application.last_comment が使われていて、警告が出る。

 % rake
DEPRECATION WARNING: prepend_before_filter is deprecated and will be removed in Rails 5.1. Use prepend_before_action instead. (called from require at /Users/argerich/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bundler-1.11.2/lib/bundler/runtime.rb:77)
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.

last_description に書き換えると警告が止まる。 確認はしてないが、master なら治ってそう。

まとめ

休日の空いてる時間で、雑に作ったものですが、できあがったものはこちらです。

mgi166/authlogic-rails5-example: authlogic meets rails 5

*1:もっとも、最近では controller のテストではなく request のテストを書く事が多いだろうと思われるが

webpack-dev-server を使う

イントロ

webpack dev server

見ればだいたい書いてある。
webpack は少し触らないと忘れてしまうので、メモしておく。
それと何故か webpack の公式 Document は読みづらい(私だけ?)というのも理由の一つ。

webpack-dev-server って?

開発用の web サーバー。Express で作られてる。 おおまかに2つのことができる

  • Hot Module Replacement
    • module を変更したら変更を検知して読み込み直す
  • Automatic Refresh
    • react とかで、component を変更したら、自動で reload する

これだけできれば、わざわざ変更のたびにサーバーを再起動しなおさなくて済むので、とりあえずの開発には困らなくなる。

導入

まずは npm install

$ npm i --save-dev webpack-dev-server

webpack.config.js を編集。

module.exports = {
   context: path.join(process.env.PWD, 'frontend'),
-  entry: "./index.js",
+  entry: [
+    "webpack-dev-server/client?http://localhost:8080",
+    "webpack/hot/dev-server",
+    "./index.js"
+  ],
   output: {
     path: path.join(__dirname, 'dist'),
-    filename: 'webpack.bundle.js'
+    filename: 'webpack.bundle.js',
+    publicPath: "/assets/"
   },
@@ -14,7 +18,8 @@ module.exports = {
   plugins: [
     new webpack.DefinePlugin({
       'process.env': {
         NODE_ENV: JSON.stringify(process.env.NODE_ENV)
       }
-    })
+    }),[f:id:mgi:20160416232151g:plain]
+    new webpack.HotModuleReplacementPlugin()
   ],

やってることは 3 つ

  • entry を増やす
    • webpack-dev-server/client?http://localhost:8080, webpack/hot/dev-server の2つ
  • plugins に webpack.HotModuleReplacementPlugin を足す
  • output.publicPath: を設定

後は option つけてコマンドラインで起動すれば良い

$ webpack-dev-server —hot —inline

まとめ

f:id:mgi:20160416232151g:plain

簡単に導入できて、凝ったことしない限りは十分なので非常に助かる。