o296.com について

@mmmpa のブログです。

rust-lang-nursery/mdBook を使用してビルドされています。

2019/10

CircleCI + Go 言語が楽になっていた

CircleCI の config.yml の様々が不要になっていた

従来の Go 言語プロジェクトの .circleci/config.yml では GOPATH を設定したり GO111MODULE を設定したり、様々なケアが必要でした。

しかし現在の circleci/golang:latest (1.13 ぐらい?) では Go Modules も有効になっているようで GO111MODULE さえも必要なく、普通に get -> test -> build が行えます。

---
version: 2
jobs:
  build:
    docker:
      - image: circleci/golang:latest
    steps:
      - checkout

      - restore_cache:
          key: mod-{{ checksum "go.sum" }}

      - run: go get
      - run: go test -v ./...
      - run: go build -o main main.go

      - store_artifacts:
          path: main
          destination: main

      - save_cache:
          key: mod-{{ checksum "go.sum" }}
          paths:
            - $GOPATH/pkg/mod

もちろん go mod init で Go Modules を使うプロジェクトが前提になるでしょうが、かなりお気楽に CI できるようになってよいですね。

2019/09

Rust mdbook をちょっと改造してブログにする

mdbook は所定の書式に従って指定した markdown ファイルをレンダリングし、一覧できる形で Web サイト用の HTML を出力します。ライブラリのリファレンスなどによく使われています。

非常に簡単にページが作成、公開できるので今回はちょっと見栄えを改造してブログとして使うことにしました。見栄えのみの改造なので mdbook 本体には手を入れません。CSS と JS の知識のみで可能な範囲で行いました。

公開に至るまでの手順を記録しておきます。

mdBook を手早く試す

Install

コンパイル済みのバイナリファイルが用意されています。Releases · rust-lang-nursery/mdBook

自分のシステムに該当するファイルを解凍してパスを通すかパスが通っている場所に配置します。

curl https://github.com/rust-lang-nursery/mdBook/releases/download/v0.3.1/mdbook-v0.3.1-x86_64-unknown-linux-gnu.tar.gz -L -o mdbook.tar.gz
tar -zxvf mdbook.tar.gz
mv mdbook /usr/bin/mdbook

mdBook を開始する

ディレクトリを用意して init コマンドを実行すると必要なソースファイル一式が作成されます。

mkdir o296.com
cd o296.com
mdbook init

Build

以下のコマンドで book ディレクトリに HTML 一式が出力されます。

mdbook build

編集していく

ファイルの変更を監視する

以下のコマンドで関係するファイルが更新されるたびにビルドしなおされます。

mdbook serve -p 3000

watch ではファイルのビルドのみが行われますが、serve では http://localhost:3000 経由で book を閲覧でき、更新のたびにリロードされます。

ポートは任意に変更が可能です。

目次 (ページの作成)

単なるリンクと、リスト形式のリンクが使えます。

リストはデフォルト設定では章番号つきのリストとして描画されます。ネストしたリストにはネストした章番号がつきます。

[チラシの裏](./paper.md)

- [mdbook メモ](./mdbook.md)
- [Vue メモ](./vue/index.md)
  - [Vuex](./vue/vuex.md)
チラシの裏
1. mdbook メモ
2. Vue メモ
  2.1. Vuex

作成していないファイルを指定した状態でビルドすると # チラシの裏 のように見出しのみが入ったファイルが作成されます。

prefix suffix

単なるリンクは prefix と suffix としてのみ使えます。リストで挟んで使うことは出来ません。

以下はビルドできません。

[チラシの裏](./paper.md)

- [mdbook メモ](./mdbook.md)

[プログラムの話](./program.md)

- [Vue メモ](./vue/index.md)
  - [Vuex](./vue/vuex.md)

font-awesome

使用している font-awesome は 4.7 です。

設定を変更する

ある程度の見栄えや挙動の調整は book.toml の調整のみで行なえます。 詳細は Configuration - mdBook Documentation を参照してください。

最終的には以下のような設定になっています。

[output.html]
additional-css = ["theme/custom.css"] # CSS の追加
additional-js = ["theme/custom.js"]   # JS の追加
mathjax-support = true              # Mathjax 有効
no-section-label = true             # ブログなので章番号は不要

見栄えを調整する

シンタックスハイライトの色を替える

highlightjs が使用されているので、highlight.js/src/styles at master · highlightjs/highlight.js からお好みの CSS を使えます。

theme ディレクトリをつくり highlight.css とリネームして配置すればビルド時に適用されます。

(src/theme/highlight.css ではなく theme/highlight.css です。themesrc と同階層につくりましょう)

CSS の追加

theme を用いての変更は全上書きとなるので大変です。そこで元の CSS を残しつつ追加 CSS を指定できます。

additional-css = ["theme/custom.css"]

アンカーリンクで見出しがヘッダーに隠れないようにする CSS

見出しをクリックすると見出し位置までスクロールしますが、ページ頂点までスクロールしてしまうため、見出しがヘッダーに隠れてしまいます。

そこでマージンを設けて隠れないようにしています。

.header {
  padding-top: 4em;
  margin-top: -4em;
}

その他

メニューのマージンなどを調整していますが、好みなので省略します。

挙動を変更する

JS の追加

CSS と同じく追加 JS が指定できます。

additional-css = ["theme/custom.css"]

script タグはファイル末尾に挿入されるので基本的に onload などは必要ありません。

アクティブになっている記事までサイドバーをスクロールする

デフォルトではリンクをクリックするたびにサイドメニューのスクロール位置がトップに戻るため、少し不便です。

そこでいま開いているページの場所までスクロールするようにします。

(function () {
  document
    .querySelector('.sidebar-scrollbox')
    .scroll(
      0,
      document.querySelector('.sidebar .active')
      .getBoundingClientRect()
      .top - 50
    );
})();

アクティブになっている記事のページ内アンカーをサイドバーに加える

記事が長く、アンカーがあった方が便利な場合は多々あります。

mdbook にはマッピング機能はないので、現在開いているコンテンツの DOM を見て動的に挿入します。

(function () {
  var as = document.querySelectorAll('a.header')
  var newList = document.createElement('ul');
  newList.setAttribute('class', 'innerLink');
  for (var i = 1, l = as.length; i < l; i += 1) {
    var a = as[i];
    var label = a.innerText;
    var href = a.getAttribute('href');
    var newAnchor = document.createElement('a');
    newAnchor.setAttribute('href', href);
    newAnchor.innerHTML = label;
    var newItem = document.createElement('li');
    newItem.appendChild(newAnchor);
    newList.appendChild(newItem);
  }
  document.querySelector('.sidebar .active').appendChild(newList);
})();

どこかにアップロードする

ビルドしたファイルをどこかにアップロードします。

mdbook build

serve で作成されたファイルは同期のために WebSocket を使うコードが含まれているので、アップロードすべきではありません。

おわりに

これで十分便利に使えるようになりました。

というわけで、この記事時点からこのブログは mdbook で作成されています。

2019/02

依存性の逆転について

現代のプログラミングでは Plugin という仕組みや Polymorphism が一般的になっているため + 動的型付け言語を使用してきたため、依存性の逆転 が具体的になにを解決しているのかよくわかっていませんでした。なにしろ雰囲気でプログラミングをやってきたので。

現在 Clean Architecture を少しずつ読んでいます。読みながら極端に悪い依存性を持つ状態をイメージし、そこに依存性の逆転が持ち込まれると何が解決されるのかを見ることによってようやく理解できました。

以下は自分が理解するために書いたものです。

実行処理の上流の module が下流の module の実装に依存する状態

実行処理の流れがそのまま依存の流れになっている (依存先の詳細を知らなければならない) 状態です。A -> B -> C という順に処理が呼び出される場合、A は B に依存し、B は C に依存するという構造です。

例では Processor は上流からの呼び出しに従い任意の device を使って readwrite を行います。

極端に悪い例

MicTabletTerminal が公開しているメソッド名が統一されていないため、使う device によって処理を切り替える必要があります。「依存先の詳細を知らなければならない」状態の悪さがわかると思います。

import Mic from './Mic';
import Tablet from './Tablet';
import Terminal from './Terminal';

export default class Processor {
  device: Mic | Tablet | Terminal;

  constructor (device: Mic | Tablet | Terminal) {
    this.device = device;
  }

  read (): string {
    switch (this.device.constructor) {
    case Mic:
      return (this.device as Mic).listen();
    case Tablet:
      return (this.device as Tablet).write();
    case Terminal:
      return (this.device as Terminal).in();
    default:
      return '';
    }
  }

  write (s: string) {
    switch (this.device.constructor) {
    case Mic:
      (this.device as Mic).say(s);
      return;
    case Tablet:
      (this.device as Tablet).display(s);
      return;
    case Terminal:
      (this.device as Terminal).out(s);
      return;
    default:
      //
    }
  }
}

Plugin 化された例

メソッド名を統一することで随分すっきりしたコードになりました。

import Mic from './Mic';
import Tablet from './Tablet';
import Terminal from './Terminal';

export default class Processor {
  device: Mic | Tablet | Terminal;

  constructor (device: Mic | Tablet | Terminal) {
    this.device = device;
  }

  read (): string {
    return this.device.in();
  }

  write (s: string) {
    this.device.out(s);
  }
}

しかし、未だに下流の module に依存している状態です。MicTabletTerminal のいずれかが変更されると、Processor のコンパイルしなおしが必要になってしまいます。

依存性の逆転

そこで MicTabletTerminalProcessor 用の Interface IDevice に依存するように変更します。つまり、下流の module が上流の module の実装に依存するように、処理の流れとは逆にするということです。

export default interface IDevice {
  out (s: string): void
  in (): string
}
import IDevice from './IDevice';

export default class Processor {
  device: IDevice

  constructor (device: IDevice) {
    this.device = device;
  }

  // snip
}
import IDevice from './IDevice';

export default class Mic implements IDevice {
  out (s: string): void {
  }

  in (): string {
  }
}

(他の Device は省略)

これですべての module が独立してコンパイルできるようになりました。また何か新しい Device が導入されたとしても、その Device は IDevice を実装すれば Processor にはなんの変更もせず使用することができます。

Architecture の観点から

この依存性の逆転はソースコードの依存性を一方向性を解決し、依存の方向をどちらへも向けることを可能にします。

Clean Architecture では、システム/ビジネスのポリシーやルールに基づいて、Open-Close の原則の方向を決めます。

そこでこのソースコードレベルの依存性によらない自由な依存の方向が必要になります。

まとめ

実際、Ruby などの動的型付け言語では plugin 化の段階ですでに依存性の逆転は達成されています。Duck Typing を満たす限りどんな Device も Processor で使用できるからです。

依存性の逆転について、粗結合に優れるコードを書くための概念としては把握していました。しかし今回、極端に悪い例と実際に逆転を意識しないと実現できないコードを知ることにより、具体的に何を解決していたかを知ることができました。

2018/12

IBM Bluemix (IBM Cloud - Cloud Foundry) をローカルで動作するように環境を準備する

Bluemix は IBM が提供する PaaS で、Heroku に似た仕組みを持つ Cloud Foundry をベースとして動作しています。専用の cli を使ってコードを push すると、コードにあわせて buildpack が選択され、コードに合わせた環境が準備されて自動的にアプリが起動します。

push から動作までが非常に速やかで簡潔な反面、環境がすべて自動で準備されているため、動作における不具合の調査とその調整が少ししづらい印象があります。幸い、Croud Foundry にはローカルで動作させるための PCF Dev (P は開発元であった Pivotal 社の頭文字) という plugin が用意されていますので、それを今回はインストールしました。

OS は Debian stretch で、手順は Introduction | Try PCF on your Local Workstation | Pivotal に従っています。

(なお windows と mac では cloudfoundry-incubator/cfdev: A fast and easy local Cloud Foundry experience on native hypervisors, powered by LinuxKit with VPNKit で簡単に準備できようです)

インストール

cf-cli のインストール

PCF Dev plugin は cf-cli 上で動作します。また、動作している Cloud Foundry へアクセスするためにも必要になるので、いずれにしてもインストールが必要になります。

# apt-get の https アクセスなどに必要なパッケージをインストール
sudo apt-get update
sudo apt-get install -y wget gnupg gnupg1 gnupg2 apt-transport-https

wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add -
echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list
sudo apt-get update
sudo apt-get install cf-cli

VirtualBox をインストール

仮想環境として内部的に VirtualBox を使用しています。VirtualBox から環境に合わせたパッケージをダウンロードしてインストールします。

素の Debian stretch だと多くの依存パッケージが足らず virtualbox-6.0 depends on Foo; however: Package Foo is not installed. といったエラーメッセージが複数行にわたって表示されます。したがって依存パッケージを先にインストールしなければなりません。

# 依存パッケージのインストール
sudo apt-get install -y libcurl3 libdevmapper1.02.1 libgl1-mesa-glx libgl1 libopus0 libpng16-16 libqt5core5a libqt5gui5 libqt5opengl5 libqt5printsupport5 libqt5widgets5 libqt5x11extras5 libsdl1.2debian libvpx4 libx11-6 libxcb1 libxcursor1 libxext6 libxml2 libxmu6 libxt6 psmisc

sudo dpkg -i virtualbox-6.0_6.0.0-127566~Debian~stretch_amd64.deb

PCF Dev をインストール

Download PCF Dev — Pivotal Network からダウンロードします。ダウンロードにはアカウントが必要なので作成します。

plugin のインストールは cf-cli に対して行うので以下のようになります。

cf install-plugin ./pcfdev-v0.30.0+PCF1.11.0-linux

補記

チュートリアルのように $ ./pcfdev-v0.30.0+PCF1.11.0-linux とした場合は Your cf CLI version is too old. Please install the latest cf CLI. とエラーになりインストールできませんでした。

起動

以下のコマンドを実行するだけです。

cf dev start

ただし初回はまず仮想環境イメージのダウンロードで時間がかかり、2 回目以降も起動自体にものすごい時間がかかります。それを知らないと固まったような印象を受けますが、気長に待つのが肝要です。

起動時ログ例

$ cf dev start

Less than 4096 MB of free memory detected, continue (y/N): > y
Please sign in with your Pivotal Network account.
Need an account? Join Pivotal Network: https://network.pivotal.io

Email>

Password>
Downloading VM...
Progress: |====================>| 100%  # まず仮想環境イメージのダウンロードが長い
VM downloaded.
Allocating 4096 MB out of 32140 MB total system memory (1371 MB free).
Importing VM...
Starting VM...
Provisioning VM...
Waiting for services to start... # 2 回目以降もここからの起動が長い
7 out of 58 running
7 out of 58 running
7 out of 58 running
7 out of 58 running
40 out of 58 running
56 out of 58 running
58 out of 58 running            # 気長に待ちましょう

 _______  _______  _______    ______   _______  __   __
|       ||       ||       |  |      | |       ||  | |  |
|    _  ||       ||    ___|  |  _    ||    ___||  |_|  |
|   |_| ||       ||   |___   | | |   ||   |___ |       |
|    ___||      _||    ___|  | |_|   ||    ___||       |
|   |    |     |_ |   |      |       ||   |___  |     |
|___|    |_______||___|      |______| |_______|  |___|
is now running.
To begin using PCF Dev, please run:
   cf login -a https://api.local.pcfdev.io --skip-ssl-validation
Apps Manager URL: https://apps.local.pcfdev.io
Admin user => Email: admin / Password: admin
Regular user => Email: user / Password: pass

アクセス

cf-cli

以下のコマンドで、cf-cli のアクセス先を立ち上がった PCF Dev に設定します。なお、*.local.pcfdev.io192.168.11.11 にポイントされているので、きちんとローカルの Cloud Foundry にアクセスすることになります。

cf login -a api.local.pcfdev.io --skip-ssl-validation

EmailPassword が要求されるので、起動時のログに表示されているものを使用します。

Regular user => Email: user / Password: pass

GUI

ブラウザからは https://apps.local.pcfdev.io でコンソールにアクセスできます。EmailPassword はおなじく起動時ログで出てきたものを使用できます。

PCF Dev の動作確認

単純な ruby を起動してみましょう。Cloud Foundry ではなく Bluemix から配給されているものですが IBM-Cloud/ruby-sinatra-helloworld: Sample Ruby application for Bluemix which uses the Sinatra framework. が簡単に起動します。

git clone https://github.com/IBM-Cloud/ruby-sinatra-helloworld
cd ruby-sinatra-helloworld
cf push

問題がなければ以下のようなログが最終的に出力されます。経路 に示された sample-ruby-sinatra-brave-okapi.local.pcfdev.io にアクセスして動作を確認しましょう。

$ cf push
user としてマニフェストから組織 pcfdev-org / スペース pcfdev-space にプッシュしています...

# build のログが続く

名前:                   sample-ruby-sinatra
要求された状態:         started
インスタンス:           1/1
使用:                   256M x 1 インスタンス
経路:                   sample-ruby-sinatra-brave-okapi.local.pcfdev.io
最終アップロード日時:   Mon 31 Dec 07:39:34 JST 2018
スタック:               cflinuxfs2
ビルドパック:           ruby 1.6.37
開始コマンド:           bundle exec rackup config.ru -p $PORT

     状態   開始日時               cpu    メモリー       ディスク       詳細
#0   実行   2018-12-30T22:39:58Z   0.0%   456K of 256M   6.7M of 512M

まとめ

これで Cloud Foundry をローカルで動作できるようになりました。

実は IBM Bluemix に乗せる平易なアプリケーションを作成していて、メモリを 64MB に設定すると落ちてしまう事象がありました。128MB にすると落ちるときと落ちない時があり、それのチューニングをする or チューニングの問題ではないのか調査するにあたって都度 push したのでは追いつかなくなってしまいました。

幸いローカル環境構築はきちんと提供されていたので大いに助かりました。 Pivotal 社及び「ローカル環境あるよ」 と教えてくださった Toshiaki Maki(@making)さん には感謝の言葉しかありません。

hosts でサブドメインをワイルドカードで指定したいができないので dnsmasq で対応する。

サブドメインをなんらかの key としたサービスがあります。その開発においてはローカルの開発環境においてもサブドメインを付与したアクセスが必要になります。しかしいわゆる /etc/hosts にあたるファイルではワイルドカードを指定できないため、key ごとに書き加えていく必要があります。

127.0.0.1       foo.example.com
127.0.0.1       bar.example.com
127.0.0.1       *.example.com # 機能しない

しかし、これは明らかに手間です。そこでローカルマシン上で DNS としての機能を提供する dnsmasq を用いて *.example.com へのアクセスを 127.0.0.1 にリダイレクトさせることで対応できます。

/etc/dnsmasq.conf

インストールは各環境に合わせて行い、設定ファイルにリダイレクト設定を書きます。

記法は単純で address=/a/b で a にマッチするアクセスがあった場合に b へリダイレクトします。これは必要なリダイレクト分だけ書けます。

address=/.example.com/192.168.0.1
address=/.o296.com/192.168.0.1

詰まりどころ

以下はわたしが dnsmasq を設定するにあたって詰まった部分です。比較的長い時間を溶かしたので、同じような状況に陥ったどなたかの助けになれれば幸いです。

Operation not permitted

起動時に以下のエラーが出る場合は /etc/dnsmasq.confuser=root を書き加えます。

dnsmasq: setting capabilities failed: Operation not permitted

リダイレクトされない

dnsmasq は DNS として振る舞います。どの DNS でドメインを解決するかというのは /etc/resolv.conf 書かれていて、大体の場合以下のようになっているようです。

nameserver 192.168.0.1
nameserver 2001:268:fd07:4::1
nameserver 2001:268:fd08:4::1

192.168.0.1 は自分自身に向いていることも多いのですが、わたしの場合はマシンの ip は 192.168.0.6 でした。これは確実に自分に向くように 127.0.0.1 に変更します。

nameserver 127.0.0.1
nameserver 2001:268:fd07:4::1
nameserver 2001:268:fd08:4::1

書きかえた /etc/resolv.conf がもとに戻ってしまう

NetworkManager が動作している場合 recolv.conf は再起動のたびに上書きされてしまいます。書き換えを防止するために、以下のように追記します。

[main]
plugins=ifupdown,keyfile
dns=none  # 追加

まとめ

これで無限にサブドメインアクセスができるようになりました。

業務でちょうどサブドメインを key とするサービスの作業を担当していたのでもっと早く dnsmasq を知れれば、都度 /etc/hosts を編集することもなかったのにな……と思いました。(その業務からは年内で担当をはずれました)

その他の手段

適当なドメインを所有している場合は *.local.o296.com などを 127.0.0.1 にポイントしておくという手もあります。サブドメインを使用するサービスでローカル開発環境を提供しているような場合、その会社がそういうローカル用のドメインを用意している場合もあるようです。

今年の洗濯回数は 8 回でした。

過去録

1 回目 2 月

2 回目 3 月

3 回目 5 月

4 回目 6 月

5 回目 7 月

6 回目 9 月

7 回目 11 月

8 回目 12 月

まとめ

今年は大規模な入れ替えもなかったので安定した洗濯生活でした。ただ、あたらしいボクブリを結構おろしたせいか、後半戦にはボクブリはあるのに T シャツの在庫がきれるという逆転現象が起こっていました。

来年は 6 億円を当てて自宅に 30kg 級の乾燥機を設置したいですね。

Ansible で rbenv をインストールする role を書いた

インフラ構成をできるだけ属人性を低くしつつ再現性を高くしたくて Ansible を触っています。Ansible は知識がない状態だと設定ファイルについて全くもって全景がとらえにくい === とてもとっかかりづらいという問題があります。しかし、そこを過ぎた処理設定自体はものすごく平易に書けるので素敵なツールだと感じています。

そこで、Ansible を使い慣れるのも兼ねて、rbenv がすでにある場合や ruby-version がすでにインストールされている場合はスキップ、ない場合は rbenv の update や ruby-version のインストールを行う role を書いてみました。

わたしの従来の rbenv インストール

rbenv について、従来は以下のような粗雑な shell ファイルを用意しインストールを行っていました。そしてバージョンの update が必要な場合は ssh で接続し、ruby-build のバージョンを上げ、ビルドしなおすという運用をしていました。

sudo yum -y install bzip2 gcc* openssl-devel readline-devel zlib-devel

git clone https://github.com/rbenv/rbenv.git ~/.rbenv
cd ~/.rbenv && src/configure && make -C src

echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

~/.rbenv/bin/rbenv install 2.5.3

rbenv/tasks/main.yml

上記 shell ファイルを単純にそのまま Ansible role に書き下すのは、shell モジュールを用いて置き換えればいいだけなので非常に簡単です。しかしこれを何度も行った場合は少し問題があります。rbenv は都度ビルドされ、export PATH="$HOME/.rbenv/bin:$PATH" がどんどん書き足されていってしまいます。

そこで Check rbenv Check installed ruby version といった tasks で状態を把握して処理を切り替えます。

- name: Check rbenv
  shell: ~/.rbenv/bin/rbenv --version
  register: rbenv_exists
  changed_when: False
  ignore_errors: yes

- name: Check installed ruby version
  shell: ~/.rbenv/bin/rbenv versions | grep {{ ruby_version }}
  register: has_ruby_version
  changed_when: False
  ignore_errors: yes

- name: Check ruby version
  shell: ~/.rbenv/bin/rbenv version | grep {{ ruby_version }}
  register: is_ruby_version
  changed_when: False
  ignore_errors: yes

- name: Install ruby requiring packages
  yum:
    name:
      - bzip2
      - gcc*
      - openssl-devel
      - readline-devel
      - zlib-devel
  become: yes
  become_method: sudo
  when: has_ruby_version is failed

# install rbenv

- git:
    repo: https://github.com/rbenv/rbenv.git
    dest: ~/.rbenv
  when: rbenv_exists is failed

- git:
    repo: https://github.com/rbenv/ruby-build.git
    dest: ~/.rbenv/plugins/ruby-build
  when: rbenv_exists is failed

- name: Update rbenv
  shell: git pull
  args:
    chdir: ~/.rbenv
  when: has_ruby_version is failed

- name: Update ruby-build
  shell: git pull
  args:
    chdir: ~/.rbenv/plugins/ruby-build
  when: has_ruby_version is failed

- name: Make rbenv
  shell: src/configure && make -C src
  args:
    chdir: ~/.rbenv
  when: has_ruby_version is failed

- name: Register rbenv
  shell: echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
  when: rbenv_exists is failed

- name: Register rbenv
  shell: echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
  when: rbenv_exists is failed

# install ruby

- name: Install Ruby
  command: ~/.rbenv/bin/rbenv install {{ ruby_version }}
  when: has_ruby_version is failed

- name: Set default ruby version
  shell: ~/.rbenv/bin/rbenv global {{ ruby_version }}
  when: is_ruby_version is failed

まとめ

register changed_when ignore_errors でフラグを設定し when で切り替えるという単純な組み合わせで十分な role を書くことができました。ruby-version のダウングレードについても一瞬で終わるので便利ですね。

Amazon linux 2 docker image を ec2 で使われているものと似た感じにする

Amazon linux は AWS で使われている Cent OS をベースとした Amazon 謹製の linux ディストリビューションです。おおむね Cent OS と同じですが、Amazon 専用パッケージがインストールされていたり、Amazon linux 特有のコマンドでしかインストールできないパッケージなどがあり、微妙な差異があります。Ansible や Capistrano などの構成ツールの設定をする際には、その差異が問題になります。

とはいえ、構成ツールの初期調整の段階で EC2 インスタンスを立ち上げるのもおおげさです。そこで Docker の出番となるわけですが、配給されている Amazon linux イメージ amazonlinux:2 は EC2 では使用できる基本的なパッケージも含まれておらず、ものすごくミニマルな構成になっています。(これはおそらく ECS での使用を想定されているせいです)

そこで、EC2 のイメージには入っていると思われるパッケージをインストールし、Ansible でのインフラ構成のテスト環境とします。と言っても、Dockerfile で必要なパッケージをインストールし、普通の SSH でアクセスできるようにするだけなのでむずかしいことはありません。

公開鍵と秘密鍵

公開鍵と秘密鍵は前もって準備しておきます。ローカルでしか使わないので雑でかまわないでしょう。

ssh-keygen -t rsa

Dockerfile

FROM amazonlinux:2

# `shadow-utils` `procps` には基本的なコマンドが多く含まれています
RUN yum install -y sudo shadow-utils procps wget
RUN yum install -y openssh-server openssh-clients

# easy_install を使うためです
RUN wget https://bootstrap.pypa.io/ez_setup.py -O - | sudo python

# SSH の設定
RUN systemctl enable sshd.service
RUN echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
COPY ./id_rsa.pub /home/ec2-user/.ssh/id_rsa.pub
RUN cat /home/ec2-user/.ssh/id_rsa.pub >> /home/ec2-user/.ssh/authorized_keys

# no password で sudo を可能にします
RUN useradd ec2-user
RUN echo "ec2-user ALL=NOPASSWD: ALL" >> /etc/sudoers

CMD /sbin/init

足りないパッケージがある場合は一つ一つ調べるのではなく、EC2 で立ちあげたインスタンスで yum list installed を行い、出たパッケージをすべてインストールしてしまうという手もあります。

docker-compose.yml

version: '3'
services:
  web:
    build: .
    tty: true
    privileged: true
    ports:
      - "2222:22"

起動と接続

docker-compose up --build -d
ssh ec2-user@localhost -p 2222 -i id_rsa_for_docker

簡単な Webpack plugin を作成して Webpack と仲良くなる (ビルド時情報を console.log に表示する)

JavaScript Advent Calendar 2018 - Qiita 5 日目のエントリーです。未投稿だったので差し込みました。

このエントリーは Webpack 4.26.1 を基準に書かれました。このエントリー用に書いたコードは mmmpa/create-webpack-plugin-practice にあります。

開発者以外の方々に改修後の JavaScript の動作確認を行っていただくことは多々あります。そこでよく問題になるのが、ブラウザキャッシュにより、改修以前の状態を確認されてしまうという事態です。

見栄えに関わる単純な改修だとキャッシュが効いていると察しがつく場合もありますが、ロジック関係の修正だと非常に気づきにくくなり「なおってません」という連絡をいただくことになります。

そこで意図したビルドを確認していただいているかを明確にするために、 JavaScript にビルド日時や Git のコミットハッシュ値を埋めこみ、それを双方からの連絡時に併記するという方法があります。

自動化のために Webpack plugin を作成する

手作業で埋めこむのは現実的ではありませんから、ビルドを行う段で自動的に JavaScript に埋めこまれるようにします。ソースをみていただくようなことはできないので、起動時にコンソールに表示されるようにします。

今回はこの単純な plugin を作成する手順を書いていきます。

Webpack config に plugin を設定する

まだ plugin を用意していませんが、設定は可能です。一般的に plugin はクラスとして実装され、インスタンス化された形で Webpack 設定ファイルの plugins に挿入されます。plugin の名前を BuildingInformationPlugin とし、そのように設定を変更します。

const path = require('path')
const BuildingInformationPlugin = require('./building-information-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index.js',
  },
  module: {},
  plugins: [new BuildingInformationPlugin()],
}

下記のような index.js を読みこみ、index.js を出力します。今回 loader は主題ではないので一つも設定していません。

console.log('Start my main process.')

Start my main process. が出力される前にビルド情報が表示されるように plugin を書いていきます。

plugin のコードを書く

plugin には Webpack から引数として compilation オブジェクトを受けとる apply メソッドが必要です。

class BuildingInformationPlugin {
  apply(compiler) {
  }
}

module.exports = BuildingInformationPlugin

apply は起動時に一度だけ Webpack から呼びだされます。その時に compilation オブジェクトに対して、この plugin がどのタイミングでどのような作用をするか設定します。

表示用のコードを埋めこむタイミングを決める

Webpack plugin はビルドにおける各段階にフックして様々な介入を行えます。

ビルド前のファイルは loader で処理されることを前提としているため、下手に手を加えると処理の内容を変えてしまうかもしれないため、タイミングとしては不適切です (lint 系の回避など無駄な手間がある)。そこで今回は assets として JavaScript ファイルが書き出された段階で、そのファイルの先頭に console.log を挿入したいと思います。

Compiler hooks

各段階のフック一覧 を見ると afterEmit というフックがありますので、これを使います。

After emitting assets to output dir

フックをかける

compiler.hooks[hookName].tap(name, callback) とすると任意の段階にフックすることができます。callback の引数は各メソッドの Parameters に記してあるもので、afterEmit の場合は compilation が渡されます。compilation は compile におけるさまざまな情報が入ったオブジェクトです。

  apply(compiler) {
+   compiler.hooks.afterEmit.tap('BuildInformationPlugin', (compilation) => {
+   })
  }

加工するファイルのありかを知る

前述したとおり compilation は compile におけるさまざまな情報が入っています。なので、Webpack の output 設定を参照すれば最終的に出力されるファイルを知ることができます。

  apply(compiler) {
    compiler.hooks.afterEmit.tap('BuildInformationPlugin', (compilation) => {
+     const { options: { output: { path: dir, filename } } } = compilation
+     const at = path.resolve(dir, filename)
    })
  }

情報を表示する console.log を埋めこむ

ここまでくればあとはファイルを読みこみ、console.log を追加して上書きするだけです。

  apply(compiler) {
+   const hash = childProcess.execSync('git rev-parse HEAD').toString().replace(/[\n\r]/, '')
+
    compiler.hooks.afterEmit.tap('BuildInformationPlugin', (compilation) => {
      const { options: { output: { path: dir, filename } } } = compilation
      const at = path.resolve(dir, filename)
+
+     const log = [
+       '##################################################',
+       `commit:   ${hash}`,
+       `built at: ${new Date().toLocaleString()}`,
+       '##################################################',
+     ].join('\n')
+
+     fs.writeFileSync(at, [
+       `console.log(${JSON.stringify(log)});`,
+       fs.readFileSync(at),
+     ].join('\n\n'))
    })
  }

ビルドする

console.log("##################################################\ncommit:   93cd2cfa63730e7641988bf169aada2bac8b0382\nbuilt at: 2018-12-3 11:26:33\n##################################################");

!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t){console.log("Start my main process.")}]);

これを HTML に読みこむと、無事、ビルド情報が起動前に表示されました。

##################################################
commit:   93cd2cfa63730e7641988bf169aada2bac8b0382
built at: 2018-12-3 11:26:33
##################################################
Start my main process.

まとめ

このように Webpack plugin は非常に簡単に書くことができます。

パッケージとして配布するような汎用的な plugin ではこのように簡単には行きませんが、開発に合わせた使い捨ての plugin はカジュアルに書いていけるというのがわかっていただけると思います。今回の場合では、本体側のコードを変更することなく開発に有用な処理を埋めこむことができました。

難しい、複雑であると思われがちな Webpack ですが (実際設定で難航することはありますが)、こうやって付き合っていくうちにもっと仲良くなれたらいいなと思っています。

簡単な Webpack loader をいくつか書いて Webpack と仲良くなる

JavaScript2 Advent Calendar 2018 - Qiita 8 日目のエントリーです。

このエントリーは Webpack 4.26.1 を基準に書かれました。このエントリー用に書いたコードは mmmpa/create-webpack-loader-practice にあります。

Webpack の設定の複雑さには毎回目が回ります。ある目的があってその目的に合う設定を見つけられればコピペで済みますが、それに loader を加えたくなった途端に複雑さが襲ってくるような印象があります。

基本的に Webpack loader はあるファイルを加工して別の状態にするのが主な作用なので、ひとつひとつの働きは把握しやすいはずです。そこで簡単な loader をつくりつつ loader 何ができるのかを見ていきたいと思います。

基本的な入力/出力

基本的に Webpack は JavaScript を出力することを目的としています。loader が処理するファイルは特に設定がないかぎり utf-8 文字列で入力され、最終的には JavaScript に import できる形で出力されることが望まれています。

以下は対象のテキストファイルの文字列を反転して出力する loader です。

module.exports = function (content, map, meta) {
  let result = ''
  for (let i = content.length; i; result += content[--i]) {}
  return `module.exports = ${JSON.stringify(result)}`
}

module.exports = ... という JavaScript で処理できる形の文字列として出力されています。Webpack の処理により、以下のように JavaScript 内ではモジュールとして使用することができます。

# index.txt
abcde
import indexText from './index.txt'
console.log(indexText) //=> edcba

Webpack が loader に提供するメソッドは this 経由で使用できる

loader から Webpack が提供する機能を利用することができます。例えば content 以外の情報を返す loader の場合は this.callback (Loader API::callback) というメソッドを使用します。

this は Webpack の context でなくてはならないため、loader の定義でアロー関数を使うことはできないので注意が必要です。

module.exports = c => this.callback(null, c, {}, {}) //-> TypeError: this.callback is not a function

loader に与えられた options 見る

options として最大文字数 max と省略記号 ellipsis を受けとり、長い文字列を省略する loader を書いてみます。

loader には 2 通りの options の渡し方があります。

一つは config ファイルでオブジェクトとして渡す形式です。

use: {
  loader: './truncate-loader.js',
  options: { max: 10, ellipsis: '...' },
},

もう一つはクエリ文字列として渡す方式です。

use: './truncate-loader.js?max=10&ellipsis=...',

this.query で参照した場合、それぞれの生の値が loader に渡されます。前者は { max: 10 } として得られ、後者は ?max=10 として得られます。option の与えられ方によって値が変わるので、これを直接は使えません。

loaderUtils.getOptions を使う

これらを JavaScript から利用しやすくするように正規化する loaderUtils.getOptions (loaderUtils.getOptions) というメソッドが用意されています。

loaderUtils.getOptions を利用することで、常に同じアクセス方法で options を参照できるようになります。ただし、クエリ文字列として渡された値は型情報を持たないので 常に文字列 となることに注意しなければなりません (前記の例だと { max: '10' } として得られます)。

(厳密等価演算子 === などがないかぎり特に問題にならないところが JavaScript ですね)

const loaderUtils = require('loader-utils')

module.exports = function (content, map, meta) {
  const {
    max: raw = 20,
    ellipsis = ' (snip)',
  } = loaderUtils.getOptions(this)

  // クエリ文字列から渡される値は常に文字列になるので注意する
  // この例だとなくても動く
  const max = +raw

  const s = content.length <= max
    ? content
    : (content).slice(0, max - ellipsis.length) + ellipsis

  return `module.exports = ${JSON.stringify(s)}`
}

非同期な処理を行う

例えば Nodejs ではファイル IO は非同期に行うことが推奨されています。普通の関数宣言で loader を定義すると、読みこみ完了を待つことなく処理が進んでしまい意図した処理をすることができません。

以下のような json を読みこんで禁止語句を置き換える loader を書いてみます。

{
  "ass": "3 letters",
  "asshole": "7 letters"
}

this.async() を使う

this.async() (Loader API::async) で処理終了通知用の関数を得られます。非同期処理済ませ処理が終わった段階でその関数を呼ぶことで loader の処理の完了とすることができます。

得られる関数の引数は this.callback (Loader API::callback) と同一です。

const util = require('util')
const fs = require('fs')
const readFile = util.promisify(fs.readFile)
const loaderUtils = require('loader-utils')

function censor(content, list) {
  return Object.keys(list).reduce(
    (a, k) => a.replace(new RegExp(`\\b${k}\\b`, 'gm'), list[k]),
    content,
  )
}

module.exports = function (content, map, meta) {
  const callback = this.async()
  const { deniedList } = loaderUtils.getOptions(this)

  readFile(deniedList).then(s => {
    const result = censor(content, JSON.parse(s))

    callback(null, `module.exports = ${JSON.stringify(result)}`)
  })
}

async/await を使う

Webpack は loader の結果として Promise を受けつけます。よって await を使用できます。

//
// snip
//
module.exports = async function (content, map, meta) {
  const { deniedList } = loaderUtils.getOptions(this)

  const list = await readFile(deniedList)
  const result = censor(content, JSON.parse(list))

  return `module.exports = ${JSON.stringify(result)}`
}

ファイルを書き出す

loader は独自にファイルを出力することができます。アプリケーション実行時には行いたくないがリソースファイルには含めたくない、といったサブデータを先行して書き出しておきたい場合などに便利です。

this.emitFile を使う

this.emitFile (Loader API::emitFile) を使うと Webpack の設定に従った output 用のディレクトリにファイルが書き出されます。Writing a Loader::Guidelines にあるように、絶対パスの使用は避けましょう。

以下の loader は markdown の見出しを元に目次を作成します。markdown には目次用の <a /> を埋めこみ次の loader に渡します。一方で目次自体は別ファイルとして書き出しています。

const path = require('path')
const marked = require("marked");

module.exports = function (content, map, meta) {
  const reg = /^(#+)( *)(.+)/mg

  let replaced = content
  let match
  let indexing = ''
  while (match = reg.exec(content)) {
    const [r, n, , s] = match
    const { index } = match
    indexing += `${toSpace(n)}- [${s}](#anchor-${index})\n`
    replaced = replaced.replace(r, `${n} <a id="anchor-${index}"></a>${s}`)
  }

  const base = path.basename(this.resourcePath, '.md')
  this.emitFile(`${base}.with-index.html`, marked(indexing))

  return replaced
}

function toSpace(h) {
  let s = ''
  for (let n = h.length - 1; n--;) {
    s += '  '
  }
  return s
}

pitch を理解する

use: [
  './a-loader.js',
  './b-loader.js',
  './c-loader.js',
],

通常 loader は末尾から作用します。上記の例だと c -> b -> a の順に処理を進めます。しかし実際の処理を始める前に、まず先頭から pitch というメソッドを起動するサイクルがあります。

┌ pitch ┐                    ┌ default ┐
a -> b -> c -> load resource -> c -> b -> a

pitch に定義したメソッドでは loader で使うためのデータをセットできます。また undefined 以外の値を返すことで自分より後ろの loader を無視した結果を返せます。

例えば bpitch で値を返した場合は以下のようなフローになります。

┌ pitch
├─┐    ┌ default
a -> b -> a

本来発生するリソースファイルの読みこみも無視されることに注意して実装する必要があります。

censor-loader を無視する block-loader を書く

pitch の仕組みを使って、任意のファイルの場合は censor-loader の処理を無視する block-loader を書きます。

webpack config

指定のファイルでは検閲置換を行わない、というストーリーを想定します。

use: [
  './reverse-loader.js',
  {
    loader: './block-loader.js',
    options: { blockingList: ['pitch-block.txt'] },
  },
  {
    loader: './censor-loader.js',
    options: { deniedList: './deniedWords.json' },
  },
],

block-loader.js

指定ファイル以外では loader としての作用を持たなくてはならないので、受けつけた content をそのまま返すデフォルトメソッドを定義します。

指定ファイルが来た場合はリソースの読みこみが行われないことを踏まえ、独自にリソースファイルの読みこみを行いました。これで pitch-block.txt には検閲が行われなくなりました。

const path = require('path')
const util = require('util')
const fs = require('fs')
const readFile = util.promisify(fs.readFile)
const loaderUtils = require('loader-utils')

module.exports = function (content, map, meta) {
  return content
}

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  const { blockingList } = loaderUtils.getOptions(this)

  return blockingList.includes(path.basename(this.resourcePath))
    ? readFile(this.resourcePath)
    : undefined
}

ところで pitch で async/await を使うとあらゆる場合で Promise返した という扱いになり常にブロックされます。この場合だと、ブロックしていないつもりでも reverse-loader には censor-loader の処理結果ではなく undefined がわたってしまうので注意しましょう。

まとめ

loader に必要な機能を使いつつ、いくつかの loader を書きました。

基本的な処理を追うことによって、例えば、なぜ markdown-loader に必ず html-loaderraw-loader が必要なのかがわかりました。また、基本的な機能を把握することにより、アプリケーションに必要な適切な加工やサブファイルの書き出しが簡単に行えることがわかりました。

loader の設定変更だけではどうにもならないようなアプリケーション特有の特殊な処理を、独自の loader で手早く済ませることも可能になりました。検索とコピペを繰り返すのみで思うようにならず複雑さばかりを感じていたときよりも、より身近な存在になったと思います。

みなさまも一度簡単な loader を書いて Webpack と仲良くなってみてはいかがでしょうか。

loader をつくって学ぶ Webpack

このエントリーは Webpack 4.26.1 を基準に書かれました。

Webpack とは

Webpack は loader と呼ばれるモジュールを使って、任意のファイルやデータを JavaScript 内で使えるようにすることを主たる目的としたプログラムです。最新の文法を使った JavaScript や CSS などを一般に普及しているウェブブラウザで機能させるために、最近のフロントエンド開発でほぼ確実に用いられています。

Webpack の設定の難

Webpack で機能する loader が数多く存在します。そしてそれらを自由に組み合わせてファイルを加工できます。loader の多様さと組み合わせの自由さは大きな利点ですが、一方で、設定項目は多く、設定ファイルは入り組みがちです。設定項目を一から調べて書くということはあまりなく、コピペで組み立てていくというのが一般的な Webpack 設定ファイル作成方法ではないでしょうか。

そのような設定をしていると、いざエラーが起こった場合、このエラーの原因は何かを速やかに理解することが難しくなります。エラーメッセージで検索し、似たような問題に対する解決方を見つけられなければお手上げとなってしまいます。あるいは的はずれな検索に多くの時間を割いてしまうでしょう。

本エントリーの目的

そこで今回は自分の手で loader を造ることによって、loader が作用する仕組みを追ってみました。また loader では完結できない処理があったため、plugin も造ることになりました。最終的には以下のようなことを学べたと思います。

  • loader での入力出力
  • 結果ファイルを Nodejs で扱うために書き出しの方式を変更する方法
  • 最終的には plugin でどうとでもなるということ

markdown 内リンクを挿入する loader を造る

今回は題材として md-indexing-loader を造っていきたいと思います。

この loader は markdown を受けとり、その見出しへのリンクをリスト上に構成して markdown の任意の位置に挿入する、という作用を持ちます。

Webpack の最小限の設定とサンプルファイル

単純に index.md をエントリーポイントとし index.md を出力する設定をします。loader はパッケージ化せず、./md-indexing-loader/index.js に配置します。

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.md',
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'index.md',
  },
  module: {
    rules: [
      {
        test: /\.md$/,
        use: ['./md-indexing-loader/index.js'],
      },
    ],
  },
}
# Section 1

text

## Section 1-2

text

### Section 1-2-1

text

## Section 1-3

text

# Section 2

text

# Section 3

text

## Section 3-1

まず最小限の loader を造り、どのような処理が必要か知る

処理

何が入力されてどのように出力するのかを見るために、まずは最小限の loader を書きます。loader は入力として utf-8 文字列を受けとり、なんらかの値を返したり返さなかったりします。

module.exports = function (content, map, meta) {
  const json = JSON.stringify(content)
  return `module.exports = ${json}`
}

出力

一般的な loader では最終的に JSON.parse された上で JavaScript の式や文として出力されます。Webpack が最終的に出力ものは以下のようになります。

(Webpack のヘルパーメソッド部は省略しています)

/***/ (function(module, exports) {

module.exports = "# Section 1\n\ntext\n\n## Section 1-2\n\ntext\n\n### Section 1-2-1\n\ntext\n\n## Section 1-3\n\ntext\n\n# Section 2\n\ntext\n\n# Section 3\n\ntext\n\n## Section 3-1\n"

/***/ })

今回は index.md に markdown をそのまま出力することを目的としているので、本文の解析などの処理の他にも、結果の返し方も変更する必要があります。

なお、この最小限の loader の結果をそのまま JavaScript で扱いたい場合は、module.exports によりデフォルト値として読みこまれるので import を用いて以下のように使えます (これは raw-loader が行っていることとほぼ同じです)。

import md from './index.md' // module.exports = "# Section 1\n\n..."
console.log(md)             // => "# Section 1\n\n..."

必要な処理はなにか

ごく基本的な動きを知ることで、この loader に何が必要かを把握できました。

  • 入力された文字列を解析してインデックスを作成する
  • 作成したインデックスと本体を合わせた markdown を作成する
  • 出力した文字列を JavaScript ではなく markdown で出力する

loader に必要な処理を実装する

入力された markdown を解析してインデックスを挿入する

入力された markdown から見出しに当たる部分を抽出し、インデックスとして構成しながらアンカリングのために <a name="" /> を挿入していきます。

文字列 to 文字列の加工なので、特に難しい部分はありません (本題ではないので厳密な抽出はしていません)。

module.exports = function (content, map, meta) {
  const reg = /^(#+)( *)(.+)/mg

  let replaced = content
  let match
  let indexing = ''
  while (match = reg.exec(content)) {
    const [r, n, , s] = match
    const { index } = match
    indexing += `${toSpace(n)}- [${s}](#anchor-${index})\n`
    replaced = replaced.replace(r, `${n} <a name="anchor-${index}"></a>${s}`)
  }

  const combined = `${indexing}\n\n${replaced}`

  const json = JSON.stringify(combined)
  return `module.exports = ${json}`
}

function toSpace(h) {
  let s = ''
  for (let n = h.length - 1; n--;) {
    s += '  '
  }
  return s
}

出力した文字列を JavaScript ではなく markdown ファイルで出力する

loader から直接ファイルを出力する

ファイルを出力する方法として、Webpack から提供される emitFile という関数があります。これを用いると config ファイルに設定された output に従った場所にファイルが出力されます。ただし、この時は最終的に出力される index.md は使用できません。そこで、ひとまず index.with-index.md として結果を出力します。

ハードコーディングされたファイル名で出力するのはよくないので、実際に入力されたデータの持ち主のファイル名を変形します。ファイル名といったような loader 内で活用できる各種情報は Loader API に一覧されています。loader が何を参照しているのかがわかるので一読しておくと良いでしょう。

 module.exports = function (content, map, meta) {
 // snip

   const combined = `${indexing}\n\n${replaced}`
+  const fileName = path.basename(this.resourcePath, '.md')
+  this.emitFile(`${fileName}.with-index.md`, combined)

 // snip
 }

無事、以下のようなファイルが出力されました。

- [Section 1](#anchor-0)
  - [Section 1-2](#anchor-19)
    - [Section 1-2-1](#anchor-41)
  - [Section 1-3](#anchor-66)
- [Section 2](#anchor-88)
- [Section 3](#anchor-107)
  - [Section 3-1](#anchor-126)


# <a name="anchor-0"></a>Section 1
<!-- snip -->

出力される index.md 自体を markdown にする

さて、上の方法でファイルは出力されたものの、本来の結果ファイルである index.md は依然として JavaScript ファイルとして出力されています。markdown ファイルを入力し markdown ファイルを出力するための loader ですから、出力結果として markdown を得られる仕組みを提供する必要があります。

そこで本来のファイル名で出力するための plugin を書きます。pluign は loader にはできないあらゆることを解決するために作成されます。

They also serve the purpose of doing anything else that a loader cannot do.

JavaScript ファイルではなく markdown ファイルとして出力するための plugin を書く

loader と同じように、まず最小限の plugin を書き、必要な処理を処理を実装します。

webpack に設定を追加

 const path = require('path')
+const MdLoaderPlugin = require('../md-indexing-loader/plugin')

 module.exports = {
   mode: 'production',
   entry: './src/index.md',
   output: {
     path: path.resolve(__dirname, '../dist'),
     filename: 'index.md',
   },
   module: {
     rules: [
       {
         test: /\.md$/,
         use: [
           './md-indexing-loader/index.js',
         ],
       },
     ],
   },
+  plugins: [new MdLoaderPlugin()]
 }

最小限の plugin

Plugin は起動時にインスタンス化され、Webpack の complier オブジェクトを引数として apply を呼ばれます。complier を使って Webpack のライフサイクルに従ったイベントフックに関数を設定することで、任意のタイミングで任意の処理を行えます。

今回は出力結果のファイルに対する処理となるので、compilation のさらに細分化されたイベントの needAdditionalPass にフックしました。この段階ではすべての処理が終えられているため、compilation に十分な情報がセットされています。

class MdLoaderPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('MdLoaderPlugin', compilation => {
      compilation.hooks.needAdditionalPass.tap('MdLoaderPluginA', () => {
        console.log(compilation)
      })
    })
  }
}

module.exports = MdLoaderPlugin

index.md を Nodejs で読めるようにする

Webpack でのコンパイル中では import で読み込んで値を扱えますが、コンパイル後はブラウザで起動されることが前提となっているのでクロージャーですべてが隔離されています。そのためデフォルトの設定では index.md の値に直接アクセスできません。

そこで Nodejs のライブラリのように外部から読みこまれる形でコンパイルするように設定を変更しなければなりません。

Webpack の設定をライブラリモードに変更

Nodejs の読みこみを前提としているため、commonjs モードで出力します。

 module.exports = {
 // snip
   output: {
+    library: 'index',
+    libraryTarget: 'commonjs',
     path: path.resolve(__dirname, '../dist'),
     filename: 'index.md',
   },
 // snip
 }

これにより index.md の出力結果に exports["index"] = が加えられ、require で読みこんだ際には index を識別子としてアクセスできるようになります。

plugin が index.md を markdown ファイルに書き換える処理を書く

まず初期情報として出力ファイル名とライブラリ名を引数として与えるように、Webpack の設定を変更します。汎用的な plugin ではさまざまな動的処理によりファイルを判別しています。

 module.exports = {
 // snip
-  plugins: [new MdLoaderPlugin()],
+  plugins: [new MdLoaderPlugin('index.md', 'index')],
 }

あとは compilation.assets から結果ファイルパスを調べ、それを値として読みこみます。読みこんだ値は素の markdown ですから、それをそのままファイルに上書きすれば markdown ファイルが完成します。

const fs = require('fs')

class MdLoaderPlugin {
  constructor(target, name) {
    this.target = target
    this.name = name
  }

  apply(compiler) {
    compiler.hooks.compilation.tap('MdLoaderPlugin', compilation => {
      compilation.hooks.needAdditionalPass.tap('MdLoaderPluginA', () => {
        const { existsAt } = compilation.assets[this.target]
        const md = require(existsAt)[this.name]

        fs.writeFileSync(existsAt, md)
      })
    })
  }
}

module.exports = MdLoaderPlugin

loader 内のファイル出力は不要になったので削除します。

 module.exports = function (content, map, meta) {
 // snip

   const combined = `${indexing}\n\n${replaced}`
-  const fileName = path.basename(this.resourcePath, '.md')
-  this.emitFile(`${fileName}.with-index.md`, combined)

 // snip
 }

出力結果

- [Section 1](#anchor-0)
  - [Section 1-2](#anchor-19)
    - [Section 1-2-1](#anchor-41)
  - [Section 1-3](#anchor-66)
- [Section 2](#anchor-88)
- [Section 3](#anchor-107)
  - [Section 3-1](#anchor-126)


# <a name="anchor-0"></a>Section 1

text

## <a name="anchor-19"></a>Section 1-2

text

### <a name="anchor-41"></a>Section 1-2-1

text

## <a name="anchor-66"></a>Section 1-3

text

# <a name="anchor-88"></a>Section 2

text

# <a name="anchor-107"></a>Section 3

text

## <a name="anchor-126"></a>Section 3-1

まとめ

今回はエラー処理などを完全に無視して書きましたが、本来は他の loader との連携に当たっては入力の型チェックや出力内容の統一など、様々な処理が増えることになります。様々な loader を組み合わせていくなかで発生する不可解なエラー、そしてそれの追いづらさの理由が少しわかった気がします。

自分で複雑な loader を書くことはあまりないかもしれません。しかし、複雑な設定が必要な loader で頭を悩ませるよりもプロジェクトドメインにそったシンプルな使い捨ての loader や plugin を書くのが最も適した解決になる局面もありそうだと思いました。

Webpack の設定はつらいことで有名です。できればあまり近寄りたくはなかったのですが、こうして簡単な loader と plugin を作成することにより、いざとなったら介入できる (かもしれない) という心の余裕ができました。設定をコピペするときも、その設定内容の意図が見えるようになるような気がします。

行ったことは簡単で単純でしたが、得たものは多かったと感じました。Webpack に苦手意識があるかたは、是非一度 loader を造ってみることをおすすめします。

2018/08

並行コンピューティング技法という本をやり終えました。

夏季休暇中 (8/11 - 8/19) の課題としてはじめた 並行コンピューティング技法 ―実践マルチコア/マルチスレッドプログラミング という本を、先日ようやくやり終えました。

久しぶりに技術書的な本を一冊やりおえたので、本の紹介や思い出日記を書きます。

今回の学習について

なぜやろうと思ったか。なぜわたしに必要だと思ったか。

仕事では Ruby、JavaScript を主に使っていますが、個人的には Go 言語を使いこなせるようになりたいと思っています。

Go 言語には並列処理を簡単にハンドリングできる goroutine という仕組みがあります。並列処理のための排他制御や終了を取りあつかうための仕組みも準備されており、使用開始は容易です。

しかし、何を並列化できるか、並列化をどのように行うかという素養が全くありませんでした。外部 API に対するリクエストなど並列化のメリットがわかりやすいものはともかく、一般的な処理になるとお手上げです。

そこで言語の機能やライブラリの使用法ではなく、並列化自体を解説しているこの本をやろうと思い至りました。

学習方法について

この本をやっている最中に Learn Better――頭の使い方が変わり、学びが深まる6つのステップ という本を読みました。

  • 学習する対象の価値を見出す
  • 具体的な目標を設定する
  • 学習対象は少し難しいものを選ぶ
  • 学習対象へ能動的に行動する (読む・聞く・写すなど受動行動だけではなく、自分や他人に対して説明・要約など行う)
  • 時間をおいた分散学習 (一度やって終わりではなく、時間をおいて再度同じことを学ぶ)

など、よりよく学習するための方法が紹介されています。

今回たまたまいくつかの点を満たして学習を開始しました。能動的にという点は当初より 読書メモ を残すということで無意識にやっていましたが、メモをより明確な意図を持って行うようになりました。

時間をおくという点については、あるアルゴリズムを並列化した直後ではなく、次のアルゴリズムの途中かそれが終わってからメモをおこすことで実践してみました。本ではなく実装前にノートに書いたメモ書きや記憶やコードを頼りに書くことにより、再び考える機会が訪れて良かったように思います。

この日記の後段でも作成した図とともに学習した内容について触れています。これもまた分散学習の一助になるように思います。

説明の有効性

説明の有効性については、最近読んだ 知ってるつもり――無知の科学 でも言及されていました。学習という観点ではなく、人がどれだけ自分の無知に無自覚であるかを検査する方法 (物事を説明しようとすることにより自分がどれだけその物事について知らないかを自覚する) としての紹介でした。

自分に対して説明することにより本当に理解できたかどうか検査できるので、有用性は確かなものであるようです。(実際コードを書きおえて動くようになってから説明を書いてみて、理解がずれていることに気づいたアルゴリズムがありました)

本について

前半

並列化において念頭におかなければならないことを解説しています。

  • 逐次処理で行われていたものを並列化するので、
    • 並列化処理を実装すること自体がオーバーヘッドであること。
    • 並列化実行コストが必ずオーバーヘッドであること。
  • 適切な処理の粒度にしないと無用なオーバーヘッドを生むこと。
  • 並列化スレッド同士の処理順序は絶対に予測不可能なこと。
  • 処理の中にはある処理が出した結果が他の処理に影響を与える場合があり、それをなんらかの方法で並列化した処理同士で共有する必要があること。
  • 処理の中には同じデータ領域を読み書きする場合があり、排他制御が必要なこと。
  • 扱うデータの範囲によって並列化する場合は、そのデータの形によって最終的なデータ統合の処理の量が変わること。

など、実際に並列化を数多く行ってきた著者が、実際的な問題として語ってくれます。

後半

よく知られたアルゴリズムを並列化することにより、並列化処理で実際に直面する問題への対応を解説しています。

処理の順序が決まっていて並列化が無理そうなソートアルゴリズムを並列化します。また、逆に各処理の独立性が高く並列化するのが楽そうなアルゴリズムにおいては、再帰をループに書きかえたり、終了条件を明確化に設定しなければならないなど、処理だけではない実装のオーバーヘッドについても体験できます。

以下のようなよく知られたアルゴリズムを並列化しました。

  • ソートの章: バブルソート、シェルソート、クイックソート、奇偶転置ソート、基数交換ソート、直接基数ソート
  • サーチの章: 線形探索、二分探索 (を並列化する n 分探索)
  • グラフの章: 深さ優先探索、Floyd アルゴリズム (全頂点対最短経路) 、Prim アルゴリズム (最小全域木)

基本的なアルゴリズムは以前別の本でも学習しましたが、並列化するにあたって長時間接することにより、より具体的に理解できたような気がします。

学習中の思い出

学習中に図を多く描いたので、せっかくなので思い出話とともに羅列します。

図はほとんど whimsical で作成しました。機能が少なく、選択できる色も少ないので、逆に安定した図が描けるので便利です。

ただ、グラフは描きづらかったので、そこは Cacoo を使用しました。

データの形状について

分割したデータの形状によっても処理量が変わるという目からウロコな解説でした。ゴーストセルはスレッド間で競合するデータをどうやって持つかの一例です。

imageimage

データ分解について

これは学習中に書いた別の日記で言及した処理の図です。要は for i := threadNumber; i < l; i += threadTotalCount で回すとスレッド数分に分割された処理量になるという話でした。

imageimage

プリフィックススキャン

プリフィックススキャンは配列の各要素を足すなどの処理を加えていくので、一見並列化が難しそうな処理です。しかし、各スレッドの結果をプリフィックススキャンした結果を各スレッドに適用するとちゃんと結果が得られるという話でした。

imageimage

PRAM

無限の CPU を持つ抽象機械による並列和はこうなるけど実際にはそんなものないから使えないよ。

という話が最初の方であるのですが、各スレッドが出した結果をリダクションするときにはスレッド数分 (大体の場合において CPU 数分) のデータしかないから使えるよねと突如復活する話でした。

image

並列ウェイブフロント

バブルソートは順序依存が激しい処理ですが、並列化はできるという話でした。

imageimage

奇偶転置ソート

他のスレッドの割当範囲も更新するんだけど、アルゴリズムの特性上問題ないんだよという話でした。当初、データ分割量を必ず偶数にしないといけないと気づかなくて、データ競合して大変でした。

imageimage

シェルソート

遠くからの値をとっておおまかにソートしてから挿入ソートするとすごく速くなるというシェルソートの話です。実際の h はいい感じの速度がでるステップがあります。 (…, 121, 40, 13, 4, 1)

imageimage

クイックソート

おなじみクイックソートです。pivot 指定されたものと length == 1 になった部分は整列済みとして扱いカウント、カウントが配列の長さと同じになるのが終了条件という話でした。

image

基数交換ソート

ビットで行うクイックソート (pivot ナシ) ですね。終了条件は length == 1 になったもの、もしくはビットが末尾に至ったものという点で、並列クイックソートとは少しちがうのがミソという話です。

直接基数ソートは図を描くのが大変なので諦めて table にしました。こちらは末尾から任意のビット数でソートしていきます。

プリフィックススキャンによる array パッキングがミソです。

image

base 1 pass 2 pass 3 pass
485 340 526 041
041 041 739 188
340 485 340 340
526 526 041 387
188 387 485 485
739 188 387 488
489 988 188 489
387 488 988 526
988 739 488 739
488 489 489 988

線形探索

線形探索です。サーチは全データをさわりますが、探索は見つかった時点で処理が終わりなので、並列化においては他のスレッドにその終わりをどう伝えるかがミソになってくるという話です。

imageimage

n 分探索

範囲ではなくあくまで分割位置で探索する n 分探索で、毎ループごとに待ち合わせる必要があります。

imageimage

グラフ

おなじみグラフです。エッジ情報を行列で扱うという方法を知りませんでした。便利ですね。

imageimageimage

深さ優先探索スタック

深さ優先探索を再帰ではなくループにした場合、次に訪れるノードの管理はスタックで行うという図です。キューではないのがミソでした。

image

全域木、最小全域木

実質最終章のシメの問題でした。

imageimage

まとめ

一冊全てやり終えると気持ちが良い。

先日の並行和で順序依存があった問題を解決

酢と塩 — ただの足し算だし順序依存ないだろと思っていたらそうでもなかった話 では float64 の並行和によって丸め誤差が発生して逐次処理と結果が異なってしまうという話をしました。

「当初逐次処理と結果が異なる」ことが悪だと思っていました。しかし冷静に考えると円周率の近似値を求めるという要求は満たしています。

同じ条件での返り値を保証する

コンピューターの性質を考えると丸め誤差は発生して当然です。ただし並行処理の順序により毎回返り値が変わってしまうのは大問題です。

並行処理内の結果は常に同一なのですから、後は部分和を足し合わせる順序を保証すれば、答えは常に同一になります・

<br/>type SumResult struct {
    Offset int
    Sum    float64
}
func compute(rectCount, workers int) float64 {
    sum := 0.0
    width := 1.0 / float64(rectCount)
    ch := make(chan SumResult)
    for i := 0; i &lt; workers; i++ {
        go func(ch chan SumResult, offset int) {
            sum := 0.0
            for i := offset; i &lt; rectCount; i += workers {
                mid := (float64(i) + 0.5) * width
                height := 4.0 / (1.0 + mid*mid)
                sum += height
            }
            ch &lt;- SumResult{
                Offset: offset,
                Sum:    sum,
            }
        }(ch, i)
    }
    // 部分和を足し合わせる順序を保証する
    results := make([]float64, workers)
    for i := 0; i &lt; workers; i++ {
        result := &lt;-ch
        results[result.Offset] = result.Sum
    }
    for _, n := range results {
        sum += n
    }
    return sum * width
}

これで rectCountworkers が同一なら毎回同じ答えが返るようになりました。処理速度も毎回結果が変わってしまう最速のものとほぼ同じです。

無駄な処理を省くテクニック

本を読み進めると載っていました。

前回のコードでは各 worker に割りあてる restCount の範囲を (それなりの処理で) 以下のように分割していました。

restCount = 10 worker = 4 の場合です。

しかし今回のように worker 数で for をステップしていくとそのような分割は必要がないようです。便利ですね。

ただの足し算だし順序依存ないだろと思っていたらそうでもなかった話

並行プログラミングの勉強をしています。読んでいる本に例題があったので、その本が提示する鉄則に従って

  1. 逐次処理を書く
  2. 独立した処理を見つける
  3. 独立した処理を並行化する
  4. テストする

を行ったところ、最初から躓いたのでメモに残します。

円周率の近似値を求める逐次処理

例題として円周率の近似値を求める関数が上げられています。

func compute(rectCount int) float64 {
    sum := 0.0
    width := 1.0 / float64(rectCount)
    for i := 0; i &lt; rectCount; i++ {
        mid := (float64(i) + 0.5) * width
        height := 4.0 / (1.0 + mid*mid)
        sum += height
    }
    return sum * width
}

並行化

ループ内の計算は各ループから独立しているため並行化が可能です。

func computeC(rectCount, workers int) float64 {
    sum := 0.0
    width := 1.0 / float64(rectCount)
    ch := make(chan float64)
    // worker あたりの計算量を []int で返す関数。略。
    works := splitWorks(rectCount, workers)
    head := 0
    for _, n := range works {
        tail := head + n
        go func(ch chan float64, head, tail int) {
            sum := 0.0
            for i := head; i &lt; tail; i++ {
                mid := (float64(i) + 0.5) * width
                height := 4.0 / (1.0 + mid*mid)
                sum += height
            }
            ch &lt;- sum
        }(ch, head, tail)
        head = tail
    }
    for i := 0; i &lt; workers; i++ {
        sum += &lt;-ch
    }
    return sum * width
}

テストが通らない

しかしこれでは同じ結果を得られませんでした。

<br/>func TestCompute(t *testing.T) {
    rows := []struct {
        total   int
        workers int
        comp func(int, int) float64
    }{
        {10000, 1, computeC},
        {10000, 2, computeC},  // 落ちる
        {10000, 10, computeC}, // 落ちる
    }
    for _, row := range rows {
        ex := compute(row.total)
        ac := row.comp(row.total, row.workers)
        if ex != ac {
            fmt.Printf("%+v, %+v: %+v %+v\n", row.total, row.workers, ex, ac)
            t.Fail()
        }
    }
}
10000, 2:  3.141592654423134 3.141592654423129
10000, 10: 3.141592654423134 3.1415926544231265
--- FAIL: TestCompute (0.00s)

worker 数が 1 の時のみ通ります。

足し算で丸め誤差が起こる

結果の数値でだいたい察せられたかもしれません。この微妙な差異は float64 丸め誤差で起こっています。

worker が 1 の時は必ず同じ順序で加算されます。これは逐次処理の順序と同じなのでテストが通ります。

一方で worker が 2 以上の場合はworker 内での加算は常に 0.0 から始まり、最終的に加算される順序も不定です。 そのため、同じ worker 数でも同じ結果が返るとは限りません。

10000, 10: 3.141592654423134 3.141592654423127
10000, 10: 3.141592654423134 3.1415926544231265
10000, 10: 3.141592654423134 3.1415926544231274
--- FAIL: TestCompute (0.00s)

順序を保証する

ならば順番を保証すればよいということで、+= height を後回しにしました。これにより、無事テストが通りました。

func computeCC(rectCount, workers int) float64 {
    sum := 0.0
    width := 1.0 / float64(rectCount)
    ch := make(chan WorkerResult)
    works := splitWorks(rectCount, workers)
    head := 0
    for _, n := range works {
        tail := head + n
        go func(ch chan WorkerResult, head, tail int) {
            result := WorkerResult{
                Head:    head,
                Heights: make([]float64, tail-head),
            }
            for i := head; i &lt; tail; i++ {
                mid := (float64(i) + 0.5) * width
                height := 4.0 / (1.0 + mid*mid)
                result.Heights[i-head] = height
            }
            ch &lt;- result
        }(ch, head, tail)
        head = tail
    }
    heights := make([]float64, rectCount)
    for i := 0; i &lt; workers; i++ {
        ms := &lt;-ch
        for i, height := range ms.Heights {
            heights[ms.Head+i] = height
        }
    }
    for _, height := range heights {
        sum += height
    }
    return sum * width
}

ベンチマーク

BenchmarkCompute-8                50      36383353 ns/op
BenchmarkComputeC-8              200       9884245 ns/op
BenchmarkComputeCC-8              30      47883258 ns/op
BenchmarkComputeBig-8              1    12168459303 ns/op

最初の並行化 BenchmarkComputeC はスレッドの分だけの高速化がされています。順序を保証する BenchmarkComputeCC では逐次処理 BenchmarkCompute より遅くなってしまいました。

BenchmarkComputeBigBenchmarkComputeCC での低速化をうけて、他に計算結果を保証する方法はないものかとまず逐次処理を big.Float で行った結果です。めちゃくちゃに遅くなったので並行化はしませんでした。

まとめ

本の後半でこの例題を扱うという話なのですが、まだそこまで至っていないので、この本ではどのように解決されているのかはわかりません。

しかし一見簡単に見えた関数を並行化することによって、問題ある結果を返すようになってしまうというのは出だしのいい薬になった気がします。

株式会社 HeartRails に就職して丸 2 年が経過しました

株式会社 HeartRails に就職して丸 2 年が経過しました。

就職できました。 - ンンンパ

業務

サービス 担当箇所 技術 期間 (わたしの) 担当範囲
EC サイト A 表サイト SPA Vuejs v1 5 months 部分
検索サイト A Ajax 単ページ 生 JS (CoffeeScript) 0.5 months 全部
ECサイト B バックオフィス Ruby on Rails 6 months 大部分
検索サイト B バックオフィス SPA Vuejs v2 4 months 全部
検索サイト B 表サイト SPA Vuejs v2 + SSR 3 months 全部
検索サイト B 表サイト API Ruby 3 months 部分
検索サイト C データ連携サーバー Ruby on Rails 4 months 部分
検索サイト D 表サイト SPA 基盤 Vuejs v2 + Vuex 0.25 months 全部
EC サイト C バックオフィス改修 Ruby on Rails 6 months 部分

(期間はかぶっている場合もある)

感想

業務外

業務での反省 (自主ポストモ〜テム) を踏まえてつくったり、ライブラリを試すためにつくったりしたもの。ほとんど一連の機能は動作する状態までつくった上で AWS EC2 などにデプロイし、社内には公開している。

なお社内公開しているもの (学習として開発が行われるものなど) についての AWS 代金は社から支給される。

サービス 箇所 技術 ひとこと
情報収集系 A API Go + Echo + dbr
情報収集系 A クローラー Go + AWS Lambda
情報収集系 A フロントエンド Vuejs v2 + Vuex + TypeScript 後から Vuex と TypeScript をいれた
その他 A 全体 Express + TypeScript 後から TypeScript に変えた
コミュニケーション系 A 管理画面 Ruby on Rails
コミュニケーション系 A フロントエンド Vuejs v2 Preact に変えようかと
その他 B API Go + Echo + gorm
その他 B フロントエンド Vuejs v2 + Vuex + TypeScript 後から TypeScript に変えた
情報収集系 B 管理画面 Ruby on Rails
情報収集系 B クローラー Ruby Gem
情報収集系 B API Ruby + Sinatra Gem
  • バックエンドとフロントエンドは別リポジトリで作成することが多い。
  • 下手に HTML をレンダリングさせるよりも、API + SPA の方が非常に楽という認識になっている。
  • クローラーでやっていくのは昔から好きで、ことあるごとに何か書いている。
  • その他、slack への通知系をいくつか Go で書いた。

現況

  • Go 言語を勉強しています。
    • 自分アプリは主に Echo + dbr でつくっています。
    • AWS Lamba も Go を使うようになりました。
  • TypeScript を使うようになりました。
  • Vuejs しか使わなくなりました。
    • 最近改心して Vuex を使いはじめました。
    • Vuex で Nuxt 抜きで SSR できます。(やってません)
  • AWS は Terraform でやるようになって便利になった。
  • circleci v2 書きやすくていいですね。

これから

今日から 3 年目です。

静的型付け言語でバックエンドをやりたいです。

2018/07

API Gateway と DynamoDB でかんたん Slack slash command をつくった

API Gateway はダイレクトに DynamoDB へアクセスできます。動的処理を含まない簡単な文章を返すだけの Slash command ならば API Gateway と DynamoDB で完結できます。

image

ということで実際に登録して使っていますが、便利です。特に絶対に使うけれどもたまにしか使わないので忘れるというものをサッと取りだせるのがいいですね。Slack は他の通知系を集約していたりするので、そこに機能を集めていくと便利さが増すような気がします。

日記代わりに書き残しておきます。

さて

API Gateway と DynamoDB の準備は Terraform で、DynamoDB への値の登録は TypeScript を用いました。しかし設定内容はそれほど多くないので、いずれも手作業で行えるでしょう。特に DynamoDB はコンソールから手軽に編集できるため、管理が容易だと感じました。

まず Terraform ファイルを用意します。簡単な内容なので実際は一枚ものにしていますが、各セクションに分解しました。

一枚もの https://gist.github.com/mmmpa/4d472e0d844d894ebbdc4de8166010f3

環境変数を展開

ss_token には Slack slash command が送信する token を設定します。

$ TF_VAR_ss_token=xxxx terraform apply
variable "api_name" { default = "librarian_api" }
variable "table_name" { default = "librarian_db" }
variable "ss_token" {}

DynamoDB を用意する

doc_nameGetItem し、content というプロパティからコマンドで表示するテキストを取りだす想定です。

resource "aws_dynamodb_table" "db" {
  name = "${var.table_name}"
  read_capacity = 1
  write_capacity = 1
  hash_key = "doc_name"
  attribute {
    name = "doc_name"
    type = "S"
  }
}

API Gateway の構築

基礎部分

resource "aws_api_gateway_rest_api" "main" {
  name = "${var.api_name}"
  description = "${var.api_name}"
}
resource "aws_api_gateway_method" "method" {
  rest_api_id = "${aws_api_gateway_rest_api.main.id}"
  resource_id = "${aws_api_gateway_rest_api.main.root_resource_id}"
  http_method = "GET"
  authorization = "NONE"
  request_parameters = {
    "method.request.querystring.text" = false
    "method.request.querystring.token" = true
  }
}

クエリ文字列を変形し、DynamoDB へリクエストを送信する部分

Slack が送信する token の検証は #if($input.params('token') != "${var.ss_token}") で行い、正しくなければ処理を切りあげます。

resource "aws_api_gateway_integration" "integration" {
  rest_api_id = "${aws_api_gateway_rest_api.main.id}"
  resource_id = "${aws_api_gateway_rest_api.main.root_resource_id}"
  http_method = "${aws_api_gateway_method.method.http_method}"
  type = "AWS"
  uri = "arn:aws:apigateway:ap-northeast-1:dynamodb:action/GetItem"
  integration_http_method = "POST"
  credentials = "${aws_iam_role.role.arn}"
  passthrough_behavior = "NEVER"
  request_templates = {
    "application/json" = &lt;&lt;EOF
#if($input.params('token') != "${var.ss_token}")
#stop
#end
#if($input.params('text') == '')
#set($key = 'help')
#else
#set($key = $input.params('text'))
#end
{
  "TableName": "${var.table_name}",
  "Key": {
    "doc_name": {
      "S": "$key"
    }
  }
}
EOF
  }
}

DynamoDB からのレスポンスを Slack のコメントフォーマットに変形する部分

resource "aws_api_gateway_method_response" "res" {
  rest_api_id = "${aws_api_gateway_rest_api.main.id}"
  resource_id = "${aws_api_gateway_rest_api.main.root_resource_id}"
  http_method = "${aws_api_gateway_method.method.http_method}"
  status_code = "200"
  response_models = {
    "application/json" = "Empty"
  }
}
resource "aws_api_gateway_integration_response" "integration_res" {
  depends_on = [
    "aws_api_gateway_integration.integration",
  ]
  rest_api_id = "${aws_api_gateway_rest_api.main.id}"
  resource_id = "${aws_api_gateway_rest_api.main.root_resource_id}"
  http_method = "${aws_api_gateway_method.method.http_method}"
  status_code = "${aws_api_gateway_method_response.res.status_code}"
  selection_pattern = "200"
  response_templates = {
    "application/json" = &lt;&lt;EOF
#set($text = $input.path('$.Item.content').S)
#if($text == "")
{ "text": "command not found" }
#else
{ "text": $text }
#end
EOF
  }
}

API Gateway が DynamoDB にアクセスするためのロール

特に信頼関係と称される AssumeRole を忘れがちなので注意しましょう。

resource "aws_iam_role" "role" {
  name = "${var.api_name}_api_role"
  path = "/"
  assume_role_policy = &lt;&lt;EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "1",
      "Effect": "Allow",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}
resource "aws_iam_role_policy" "policy" {
  name = "get-sample"
  role = "${aws_iam_role.role.id}"
  policy = &lt;&lt;EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "1",
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem"
      ],
      "Resource": [
        "${aws_dynamodb_table.db.arn}"
      ]
    }
  ]
}
EOF
}

API Gateway を terraform apply ごとにデプロイし、URL を出力する

resource "aws_api_gateway_deployment" "deploy" {
  depends_on = [
    "aws_api_gateway_integration.integration",
  ]
  rest_api_id = "${aws_api_gateway_rest_api.main.id}"
  stage_name = "doc"
  description = "Deployed at ${timestamp()}"
}
output "endpoint" {
  value = "${aws_api_gateway_deployment.deploy.invoke_url}"
}

Terraform の設定内容は以上です。

DynamoDB へ登録する

ここからは値の登録です。

今回は JSON を用意して TypeScript (ts-node) で登録しました。

{
  "doc1": {
    "content": "doc1 の内容"
  },
  "doc2": {
    "content": "doc2 の内容"
  }
}
import { DynamoDB } from 'aws-sdk'
import * as fs from 'fs'
export type DocT = {
  description: string
  content: string
}
export type DocsT = { [key: string]: DocT }
const { TABLE_NAME: TableName = 'librarian_db' } = process.env
function put (db: DynamoDB, item: DynamoDB.PutItemInput): Promise&lt;DynamoDB.PutItemOutput> {
  return new Promise((resolve, reject) =>
    db.putItem(item, (err, data) => (err ? reject(err) : resolve(data))))
}
async function main (): Promise {
  const db = new DynamoDB()
  const data: DocsT = JSON.parse(fs.readFileSync('./built/content.json').toString())
  const items = Object.keys(data).map(k => ({
    TableName,
    Item: {
      doc_name: { S: k },
      content: { S: JSON.stringify(data[k].content) },
    },
  }))
  for (let i = 0, l = items.length; i &lt; l; i += 1) {
    const item = items[i]
    await put(db, items[i]).catch(console.error)
    console.log(`put: ${JSON.stringify(item, null, ' ')}`)
  }
}
main().catch(console.error)

2018/06

AssemblyScript を使って WebAssembly で Hello, world.

AssemblyScript を使って WebAssembly で Hello, world.

WebAssembly をやってみた系のエントリーでは add(1, 2) //=> 3 という内容が多いのですが、やはりここは Hello, Wasm. を表示したいと思いました。そこで今回は "Hello, Wasm." が (結果的に) 得られる関数を書きました。

WebAssembly.Memory

WebAssembly の関数が直接やりとりできるのは数値のみなので、文字列のやりとりには JavaScript と共有した WebAssembly.Memory を読み書きしなければなりません。

(実際には内部の ArrayBuffer を読み書きします)

バイト配列への encode

WebAssembly.Memory は単純なバイト配列のみを扱います。そのため "Hello, Wasm." を関数に渡し受けとる際には以下が必要になります。

  • Uint8Array への decode
  • Uint8Array への encode
  • バイト配列に埋めこまれた任意の Uint8Array 部分を取り出すためのルール

Uint8Array 部分を取り出すためのルール

今回は AssemblyScript 内で文字列を定義した際に使用されるルールを借用し、以下のようにしました。

  • 先頭 4 バイトに uint32 で文字列の長さを埋めこみ
  • その後ろに文字列を埋めこむ。

処理の際にはバイト配列内の位置を指定し、文字列の長さを取得した後に必要な分だけバイト配列を読みこみます。

余談ですが Go 言語の wasm では起動時の引数として string + "\0" という形でバイト配列に埋め込んでいるようです。

AssemblyScript

JavaScript 側と被らない位置からバイト配列を使用するために 4096 ほどオフセットしています。

let memHead = 4096
export function hello (head: i32): i32 {
  const pointer  = memHead
  const stringHead = head + 4
  // 埋めこまれた文字列の長さを取得
  const length   = <i32>load<u32>(head)
  // JavaScript も文字列の長さを知る必要があるので
  // 長さを同フォーマットで埋めこみ
  i32.store(memHead, length)
  memHead += 4
  // 必要な分だけ文字データをバイト配列に埋めこみ
  for (let i = 0; i < length; i++) {
    i32.store8(memHead, <i32>load<u8>(stringHead + i))
    memHead++
  }
  // 埋め込んだ文字列の先頭位置を返す
  return pointer
}

WebAssembly を起動する JavaScript (Node 版)

複数の引数がある場合などを想定して、二つ投げてみました。

const fs = require('fs')
const util = require('util')
const readFile = util.promisify(fs.readFile)
const encoder = new util.TextEncoder("utf-8")
const decoder = new util.TextDecoder("utf-8")
async function main () {
  const wasm = await readFile('hello.wasm')
  const memory = new WebAssembly.Memory({ initial: 1 })
  const importObject = { js: { memory } }
  const wasmSet = await WebAssembly.instantiate(wasm, importObject)
  const {
    instance: {
      exports: {
        hello,
        memory: { buffer }
      },
    },
  } = wasmSet
  // 引数用の文字列を Memory に登録
  const registeredArguments = ['Hello, Wasm.', 'Yay!']
  const register = storeArgumentAndGetPointer(buffer, 0)
  const argumentPointers = registeredArguments.map((arg) => register(arg))
  argumentPointers.forEach(pointer => {
    console.log(
      readHelloResult(
        buffer,
        hello(pointer) // 登録した引数用の文字列のバイト配列内での位置を投げる
      )
    )
  })
}
function readHelloResult (buffer, offset) {
  const view = new DataView(buffer)
  // 文字列の長さをバイト配列から取りだし
  const stringLength = view.getUint32(offset, true)
  // 必要な分だけバイト配列から取り出して decode
  const array = new Uint8Array(view.buffer, offset + 4, stringLength)
  return decoder.decode(array)
}
function storeArgumentAndGetPointer (buffer, head = 0) {
  let offset = head
  return function (string) {
    const pointer = offset
    const { length } = string
    // 文字列の長さをセット
    new Uint32Array(buffer, offset, 1).set([length])
    offset += 4
    // 文字列を後ろにセット
    new Uint8Array(buffer, offset, length).set(encoder.encode(string))
    offset += length+ (8 - (string.length % 8))
    return pointer
  }
}
main()

実行

$ node exec.js
Hello, Wasm.
Yay!

感想

Memory 管理をちゃんとしないと確実に破滅することは間違いないでしょう。基本的にバイト配列を直接読み書きすることになるので、決まった量を決まった位置でやりとりする処理でないと扱いが難しそうです。

興味があって触ってみましたが、何かしら高速化が必要な処理があるとしても、わたしような普通の Web 系のレベルでは実戦投入するにはちょっと難しいという感想でした (単純な計算なら大丈夫な気もしますが、単純な計算で高速化が必要になる局面は無さそうです)。

2018/05

一つのアプリケーション開発でも機能を gem に分けて分割統治すると具合がよい

コツは同じリポジトリかローカルパスが通る状態でやること。あと vendor とかにダウンロードする方式にしないこと。

gem 'module_a', path: '../module_a'
gem 'module_b', path: '../module_b'

rails s でのサーバーへの反映 (つまりブラウザでクリッククリックして動作確認しているような場合) は再起動が必要で少し手間です。しかし rspec など自動テストにおいては都度 gem が読みこみなおされるため、あらためて何かをする必要はありません。

メリット

各 gem のテストが早い。

特に Rails を基本としたアプリケーションの場合、ちょっとしたテストでも起動に時間がかかるため、テストはミリ秒で終わるのに起動に数秒かかってしまうということがあります。

gem に切りだしておくと、Rails 特有の機能が必要ない場合とても早く起動し、早く終わります。便利ですね。

まじらない

ドメインが強く分離されるため、変な依存関係や結合が起こりません。平和ですね。

2018/02

Capistrano で数回 deploy したあとに pm2 で起動できなくなる現象の対処

Capistrano で何度か deploy を行っていると、いずれ pm2 restart ecosystem.json でエラーが発生して起動できなくなります。

PM2        | Error: spawn node ENOENT
PM2        |     at _errnoException (util.js:1003:13)
PM2        |     at Process.ChildProcess._handle.onexit (internal/child_process.js:201:19)
PM2        |     at onErrorNT (internal/child_process.js:389:16)
PM2        |     at process._tickCallback (internal/process/next_tick.js:152:19)

この問題は公式ドキュメントのチュートリアルでも解説されている有名なものだったのですが、エラーログや日本語からの検索では対処法の発見が困難であったため書き残しておきます。

公式ドキュメント

原因

Capistrano は deploy において新しい releases/YYYYMMDDHHMMSS ディレクトリを作成し、転送完了後に symlink をはりかえて current とする動作があります。古くなった releases/YYYYMMDDHHMMSS ディレクトリは設定回数分の履歴を経た後に削除されます。

pm2 のデフォルトでは初めて pm2 start pm2 restart を行ったときに current であった releases/YYYYMMDDHHMMSS ディレクトリを基準として ecosystem.json を参照し続けるため、上記の削除発生後の起動では ENOENT エラーが発生することになります。

対処

ecosystem.jsoncwd として current を設定します。これにより参照先が current として記録されるので削除の影響を受けることがありません。

{
  "apps": [
    {
      "name": "SSR Server",
      "script": "./built/server/index.js",
      "cwd": "/home/www/project_root/current",
    }
  ]
}

cwd のハードコードがつらい場合は PWD を使うといいでしょう。

module.exports = {
  apps: [
    {
      name: 'SSR Server',
      script: './built/server/index.js',
      cwd: process.env.PWD,
    },
  ],
};

.circleci/config.yml を分割しておく

circleci-cli では workflows を解釈しないため、ローカルで circleci config.yml のテストをしようとすると手間がある。そこで build ごとに yml を分割しテスト、それを config.yml に反映したい。

複数の yml を連結してくれるような仕組みは circleci にはない (と思う) 。かといって手連結はめんどくさいので、とりいそぎ pre-commit で連結して config.yml を生成するようにするスクリプトを用意する。

/.circleci
  - _config.yml
  - apply.yml
  - validate.yml
- pre_commit.rb

という構成で

_config.yml

workflows を設定しておく。

version: 2
jobs:
workflows:
  version: 2
  test:
    jobs:
      - validate:
          filters:
            branches:
              ignore: master
      - apply:
          filters:
            branches:
              only: master

各 build を分割する

workflows にある #{build_name}.yml という名前で yml を作成する。今回は applyvalidate が必要なので apply.ymlvalidate.yml になる。config.yml として valid にしておかないと circleci-cli でテストできないのできっちり書く。

apply.yml

version: 2
jobs:
  build:
    parallelism: 1
    docker:
      - image: ruby
    steps:
      - checkout
      - run:
          name: work
          command: apply

validate.yml

version: 2
jobs:
  build:
    parallelism: 1
    docker:
      - image: ruby
    steps:
      - checkout
      - run:
          name: work
          command: validate

pre-commit

.git/hooks/pre-commit

#!/usr/bin/env sh
bundle exec ruby "$(git rev-parse --show-toplevel)/pre_commit.rb"

pre_commit.rb

.circleci 配下にある yml を連結する。

require 'yaml'
class Combine
  CONFIG_PATH = '.circleci/config.yml'
  CONFIG_BASE_PATH = '.circleci/_config.yml'
  CONFIG_CHILDREN_PATH = '.circleci/**/*.yml'
  def execute!
    check!
    prepare!
    File.write(
      CONFIG_PATH,
      base_configuration.merge('jobs' => combined_configuration).to_yaml
    )
    commit!
  rescue ConfigurationHasChange => e
    puts "\e[31m#{e.message}\e[0m"
    exit(1)
  end
  private
  def check!
    return unless File.exist?(CONFIG_PATH)
    raise ConfigurationHasChange if config_yml_has_change?
  end
  def config_yml_has_change?
    return false unless `git status`.match?(/modified:.+#{CONFIG_PATH}/)
    `git diff HEAD^ -- #{CONFIG_PATH}` != ''
  end
  def prepare!
    File.delete(CONFIG_PATH)
  rescue => e
    puts "#{e} (But ignore this error.)"
  end
  def base_configuration
    YAML.load_file(CONFIG_BASE_PATH)
  end
  def configurations
    Dir.glob(CONFIG_CHILDREN_PATH)
  end
  def pick_name(f)
    File.basename(f).split('.').shift
  end
  def combined_configuration
    configurations.inject({}) do |a, f|
      name = pick_name(f)
      if name == '_config'
        a
      else
        a.merge(name => YAML.load_file(f)['jobs']['build'])
      end
    end
  end
  def commit!
    `git add #{CONFIG_PATH}`
  end
  class ConfigurationHasChange &lt; RuntimeError
    def message
      &lt;&lt;-EOS
"#{CONFIG_PATH}" has change.
DO NOT update "#{CONFIG_PATH}" manually.
Any configuration must be split out to "\#{BUILD_NAME}.yml".
      EOS
    end
  end
end
Combine.new.execute! if $0 == __FILE__

結果

連結された config.yml が作成され、 git add される。変更が入っていれば commit に含まれるはずだ。

---
version: 2
jobs:
  validate:
    parallelism: 1
    docker:
    - image: ruby
    steps:
    - checkout
    - run:
        name: work
        command: validate
  apply:
    parallelism: 1
    docker:
    - image: ruby
    steps:
    - checkout
    - run:
        name: work
        command: apply
workflows:
  version: 2
  test:
    jobs:
    - validate:
        filters:
          branches:
            ignore: master
    - apply:
        filters:
          branches:
            only: master

2018/01

父の命日

父の命日である。先日は七回忌の法事をした。だからなんとなく命日にあわせて父がよく飲んでいた角瓶を飲んでいる。これを飲むと結構な割合で翌日に頭痛になるんだけど、父が飲んでいたというのがあるので時々買って飲んでいる。

父は食道癌がステージ 4 の段階で発見され、食道の大部分を切除する手術を受けた。手術はとても上手くいったと医者が自賛していた。その後、肝臓に転移が見つかり、3 年ぐらいで死んだ。5 年生存率という言葉がよく響く結果である。

父は手術後はごくたまに少量のビールを飲んでいた。術前も角瓶よりはビールをひたすら飲んでいたと思うんだけど、わたしはビールがあまり好きではないので角瓶を選んで飲んでいる。

わたしが喉が痛い場合に飲む薬

風邪を治す薬はないことが知られていますが、喉の痛みはやわらげることができます。今回の喉風邪で特に以下の二つを飲むことによって喉の痛みを非常にやわらげることができました。

ペラック T 錠

主成分であるトラネキサム酸が腫れなどの痛みを改善します。

患部 (今回の場合、喉) に以上が発生するとプラスミンが発生し、プラスミンが炎症の原因のヒスタミンやプロスタグランジンなど痛みの原因物質、そして腫れを発生させます。そのプラスミンを抑える働きをするのがトラネキサム酸です。

バファリン

おなじみの解熱鎮痛剤です。

主成分のイブプロフェンは痛みの原因物質であるプロスタグランジンの発生かかわるシクロオキシゲナーゼをブロックして痛みをやわらげます。

まとめ

両者は相互作用もなく、リャンメンで喉の痛みを抑えてくれるようです。そのうえでのど飴などで喉を潤しながら過ごすことによりストレスなく喉風邪をやり過ごすことができるでしょう。

2017/12

今年の洗濯回数は 9 回でした。

去年

今年の洗濯回数は 8 回でした。 - ンンンパ

1 回目 2017 年 1 月 25 日

2 回目 2017 年 3 月 2 日

3 回目 2017 年 4 月 8 日

4 回目 2017 年 5 月 12 日

5 回目 2017 年 6 月 19 日

6 回目 2017 年 7 月 15 日

7 回目 2017 年 10 月 3 日

8 回目 2017 年 11 月 20 日

9 回目 2017 年 12 月 25 日

まとめ

今年は手洗い後の乾燥を失敗して腐らせたボクブリを結構な枚数を捨てました。そのせいですこし洗濯のローテーションが早まっています。

また中盤で T シャツを 60 枚ぐらい購入してすべて入れかえましたが、T シャツは一度洗濯して乾燥機で縮めてから着るのが好きなので、洗濯回数の増減に影響はありませんでした。

外出時作業用にサブディスプレイを買った

Diginnos (amazon リンク)

8.9 inch のバッテリー駆動する、WUXGA (1920x1200) で表示できるディスプレイです。14,580 円で小型ディスプレイとしては安い部類に入ると思います。

結構どぎつい色が出ます。わたしは目が弱いので常に輝度を下げて使っているせいもあるんですが、デフォルトの表示ではかなり目が痛くなりました。現状ではコントラスト 0、輝度 10 の設定で落ち着いています (輝度はデフォルト値ですが、コントラストを下げると明度も下がっているようで、輝度をいじる必要はありませんでした)

精細

8.9 inch で WUXGA なので 254 ppi です。これはかなりこまかくて、ターミナルに設定していた 9 pt の文字を読むのはちょっと厳しい。高精細なのでアンチエイリアスがかかった文字が綺麗に見えますから、適当に文字サイズを上げることで解決できました。

まとめ

コードはメインのディスプレイで書くとして、簡単な画面確認やターミナルにおけるファイル watch からの自動動作のログを流しておく分には十分使える感触です。そしてそれがサブディスプレイをほしかった理由なので、この買い物は概ね正解だったと言えます。

バッテリーの持ちについては確認していませんが、電源を使えない場所での使用は考えてないので考慮に入っていません。

2017/11

自分用 Vuex アンチョコ

まだマニュアルを読んだだけの段階なので不正確な部分があると思う。使っていって正確なところがわかった段階で都度アップデートする。

// mutations, actions の名前には他所から参照できる定数が好ましい
export const ADD_DATA = 'ADD_DATA'
export const REPLACE_DATA_LIST = 'REPLACE_DATA_LIST'
export const FETCH_DATA_LIST = 'FETCH_DATA_LIST'
new Vuex.Store({
    state: {
        dataList: [
            { id: 1, active: true },
            { id: 2, active: false },
        ]
    },
    // getters は state を参照するメソッド
    // mapGetters で Vue component の computed にマッピングできる
    getters: {
        // 第一引数には現行の state が入る
        activeData: state => state.dataList.filter(data => data.active),
        // 第二引数には他の getters を参照できるように getters への参照が入る
        activeDataLength: (state, getters) => getters.length,
        // 返り値を関数にすることにより、state を対象としたメソッドを公開できる
        findDataById: (state, getters) => id => state.dataList.filter(data => data.id === id),
        },
    },
    // mutations は state を変更する
    // mutations の名前には他所から参照できる定数が好ましい
    // mutations は同期的でなければならない (非同期処理には actions を使う)
    // mapMutations で Vue component の methods にマッピングできる
    mutations: {
        // 第二引数は payload と呼ばれ、object にすることが好ましい
        //
        //    store.commit(ADD_DATA, { data: { id: 3, active: true } })
        //
        [ADD_DATA] (state, { data }) {
            state.dataList.push(data)
        },
        [REPLACE_DATA_LIST] (state, { dataList }) {
            state.dataList = dataList
        },
    }
    actions: {
        // context は state や getters など store と同じ機能を持つがそのものではない
        //
        //     await store.dispatch(FETCH_DATA_LIST)
        //     console.log(store.state.dataList) // 最新の dataList が得られる
        //
        async [FETCH_DATA_LIST] (context) {
            const { dataList } = await api.fetchData()
            context.commit('REPLACE_DATA_LIST', dataList)
        }
    },
    modules: {
        // 以上の構成を名前空間を持った一段下がった位置に定義できる
        // ただし引数が以下のようになる
        //
        //     someGetter (state, getters, rootState, rootGetters)
        //     someAction ({ dispatch, commit, getters, rootGetters }) // context を展開している
        //
        // また root に commit および dispatch する場合は { root: true } が第三引数に必要になる
        //
        //     commit('someMutation', null, { root: true })
        //     dispatch('someOtherAction', null, { root: true })
        //
        // mapFoo などの展開は <a href="https://vuex.vuejs.org/ja/modules.html">https://vuex.vuejs.org/ja/modules.html</a> 参照
    },
    strict: debug,
    // plugins: debug ? [createLogger()] : []
  })

起床即タバタスクワットをするという生活

米海軍特殊部隊の元司令官、朝すぐに行動するために前日の夜にやっておく2つのこと | BUSINESS INSIDER JAPAN というエントリーがありました。このエントリーの内容を特に実践しているわけではありませんが、起床後朝食前に運動をすることによってすぐに脳が目覚めるようになりました。

朝食を食べてからの脳の立ち上がりが遅い時があったのですがそれがほぼなくなり、起床後の運動の効果を感じています。そこで、わたしが行っているタバタスクワットについてメモしておきます。

タバタスクワットとは

タバタプロトコルという運動法があります。

20 秒の激しい運動と 10 秒の休憩を 8 セット続けて行う、というのが一般的に知られているルーチンです。「激しい運動」とは 1 度でも軽く息が上がる程度の運動で、バーピーや全力疾走が選ばれることが多いようです。

ただし、わたしの場合は起床後に行いたいのでそこまできつい運動はできません。そこである程度の重量のプレートを抱えてのスクワットを行っています。それをタバタスクワットと称しています。

実行のハードルが低い

運動を習慣づけようとする時、最大のハードルとなるのは運動を予定に組み込もうと、余計な努力をしてしまうこと。

冒頭のエントリに述べられているとおり、準備が必要な行動を習慣づけるのは大変です。その点タバタスクワットに必要なのは基本的には自分の身体のみなのです。

タバタプロトコル用のタイマーも無料のスマホアプリが多数あるためすぐに用意できますし、なんなら自分で秒針を見つめて数えて行うこともできますので、すぐに実行できるでしょう。

4 分で確実に終わる

タイマーのスタートボタンを押してしまえばあとは流れで終わりまでいきます。好きな曲を 1 曲流している間に終わる長さなので、なかなか時間が経たずにつらいということがありません。

まとめ

楽にできてすぐに終わってある程度充実感もあっておすすめです。

脳の本読み続けてる

脳が補正

あらゆる「知覚」はあらゆる感覚器から届いた信号を無意識下で大いに補正されたものである。

  • その信号を何らかの欠損で脳に届けられなければわかりやすい障害となる。
  • その信号を何らかの異常で間違った補正をするとわかりづらい障害となる
  • その信号を脳が勝手に生み出すと夢や統合失調症という障害になる

障害への対処

どう欠損しているかわかれば対処できる。わかりづらい障害でも同じカテゴリに入れられた人で対処できた人が幾人かいれば、まるで誰もが対処できるように扱われる。最近話題にあがったものでは発達障害が勘違いのマトになっていた。

身体障害が軽微なものだとは決して思っていないが、その障害の形を捉えることさえ難しい精神障害者は本当に大変なんだろうなと考えを新たにした。わたしの場合は誰かをたすけるほど高尚な意思を持ってないのだけれども、せめて的はずれな気持ちを持ちたくない。

引き続き脳

無意識が先にパターンに気づく。

被験者にある成否の法則性をもったゲームをさせる (例えばポイントになるカードは得る、ポイントを減らされるカードは捨てる)。最初はその法則を被験者には知らせないがプレイしていくうちに判明できるたぐいの法則で、その法則がわかれば正解し続けられる。

被験者が全てに正解するようになる以前に、脳はそれぞれのカードに対して違った反応を返すようになるようだ。それからしばらくして被験者はその法則に気づく。

身体的反応に耳をすます

ワインバーグさんの本の中身で、意思よりも自分の身体の反応に気をつけるのが良いというのがあった。立ち方や心拍やその他に何かしら現れるのだと。

脳も身体からのフィードバックに影響をうける

この姿勢だから今の状況はこうである、と脳が補正する。うつむけばしんどく、胸を張れば自信にあふれる。

脳の本引き続き読んでる

あまり頭に残しておけてないので、もう何回か通しで読む必要がありそう。

理性と感情

極めて雑に分けると脳の動きは二分できて、この理性と感情はいつもせめぎあっている。

脳は社会化したサブモジュールの集合

サブモジュールは複雑に連携し、身体を動かしたり「意思」を決定したりする。脳の損傷や病気や遺伝的欠陥ではこのサブモジュールが一部欠損したり連携が断たれたりする。

欠損の場合は比較的わかりやすい異常となって現れるが、連携が一部断たれた場合は予測がつかない異常となって現れる。視覚が全く正常に働いていないのに本人は完全に視認していると感じる病気もある。その場面で絶対に言ってはいけないと思っている言葉を言ってしまうなどという病気もある。

脳の動きは観測できるようになっている

神経生化学の発達により脳がどう働くかは観測できるようになってきた。意識や意思決定も観測可能になってきている。しかしその解像度はまだ著しく低い。

脳の本読んでる

運動

人間が身体を動かそうとする時にやってるのは極めて抽象的な思考で、どの筋肉をどれだけ動かしてなどと個別の命令はしない。無意識がよしなにしてくれる。

知覚

刺激をどう解釈するかの問題なので、光信号を背中への振動という触覚に変えたり舌への電気刺激に変えたりすることにより、「視覚」できるようになる。初期段階ではその刺激を意識的に解釈しなければならないが、慣れるにつれ無意識に知覚できるようになる。

飲酒中メモ

有名な大事故、つまり有名な失敗に関する本を読んでいると、けっこう知ってる大事故が出てくる。

人間の注意力が削られる状況で起こった事故として紹介されていた事案が、欠乏の行動経済学でも紹介されていたりする。事故、失敗が結局は人間から起こるという事実がある。実際に業務を行う人間の動きを視界から外して失敗を語ることはできない。

失敗させないための実装というのは大切。失敗は人間が起こすのだけれども、人間を失敗させやすい実装というのは世の中に溢れている。失敗させやすい実装があるという事実がわかっていれば、失敗させないための実装もまたあるということがわかる。

人間の認知が惑いやすいという事実を認識して、その惑いやすい認知をいかに正確に導くかを念頭においてデザインすることにより、環境によって簡単に上下する人間の能力に依存しない真にすぐれた実装が行える。

リクエスト成功時のレスポンス

「成功」という結果を返ってきた場合でもそれがエラーであることがある。本来なされるべき処理ではない処理を行ってしまったり、意図しないリクエストを投げてそれが完了してしまった場合だ。

レスポンスの簡略化のためにステータスコードのみで body を空にすることがあるが、これはあまりよくないのではないか。求められているレスポンスがありそれを返す場合はその内容でまちがったリクエストをしたことに気づきうるが、空の場合はそうではない。レスポンスが必要とされていない場合は、強いて何に成功したかをレスポンスするのもよいと思われる (リクエスト内容をそのまま返すのはサイズによってはやりすぎだが、有効かもしれない)。

不親切な返信

入力を求められていてその内容がわからないというコメントがあり、必要な内容を知る人間が単に文字列を返信していた。わたしはその文字列がどう用いられるか知っているのでどう入力するかわかったのだが、その入力内容がわからない人にはわからなかった。

実は入力先は 2 つあって、空白で区切られたその文字列を分けて入力する必要があったのだが、わからない人は文字列を 2 つの入力欄にそのままそれぞれ入力したのだった。もちろん問題は解決しなかった。この「分けて入力」という方法は一度でも「いつものやつね」で済む程度の問題だったが、体験したことのない人には全くわからないという可能性は十分にあった。

この事案では、この入力欄にはこれ、この入力欄にはこれと言うのが最適だったのだろう。なにかがわからないというコメントを受けた場合はある程度丁寧にコメントしたほうがよいという学びがあった。

エラーをエラーとして扱う

エラーをエラーとして扱う

発端となるエラーがあって、それ単体で大事故になることはない。そのエラーに気づかない、あるいは復旧できない、あるいはまちがった復旧により致命的なエラーとなる場合がある。そこで起こりうるエラーに対してどうしておくのがいいかというと

  • エラーをエラーとして認識できる
  • エラーを観察できる
  • エラーからのリカバー動作をそうであると認識できる
    • リカバー動作は通常動作のルールでは異常動作に見えることがあり、それがリカバー動作であると認識していない人間により妨げられてしまう可能性がある

みたいな感じ。想定外のエラーは観察するしかないので、実装上でよくわからないからといって握りつぶしてはいけない。観察もできなくなり、異常事態の結果だけが残って調査もできなくなる。

なにをやるにもまずエラーが起こった時どうなるのかを把握しておく必要がある。これがこうなったらエラーという発見がまずあり、そのエラーが引き起こす事故のダメージを予見する。

というかリカバーは必要ないのでは

「硬い結合 (ひとつの出来事がすぐ次の出来事につながる)」部分に注意する。リカバーは硬い結合を事故を連鎖させず継続するためのものだが、リカバーでより誤った状態が発生し大事故となる可能性は多分にある。

エラーが起こったときにそこからの連鎖反応的な事故を回避できるようするのが肝要である。物理機械とはちがってプログラミングでは「早く失敗する」ことによる中断が可能なので、リカバーよりも中断が強い。

2017/10

goroutine と channel を復習する

いろいろ考えて golang を第 2 言語として全振りすることに決めたのでちゃんとやっていこうと思います。

わたしは web application をつくるのがナリワイなので非同期処理を並列に行うことが多い。したがって goroutine を使う場合も終了タイミングが読めない非同期処理を扱う場合が多いので、それを想定して goroutine の使い方を復習します。

参考

以下の記事を参考にしながらコードを書きつつ挙動などを把握しようと努めました。

goroutine に単純に非同期処理を逃した例

終了待ち忘れ

逃しているが goroutine 内の処理を完了を待たないので finish のみが出力されてしまう。

package main
import (
    "github.com/mmmpa/go_playground/easy"
    "log"
)
func main() {
    go async()
    go async()
    go async()
    log.Print("finish\n")
}
func async() {
    s := easy.RandomSecsSleep(3)
    log.Printf("sleep: %v\n", s)
}

sleep という log は出ない

2017/10/18 05:28:12 finish

channel で各 goroutine の終了までブロックする

channel は送信側でも受信側でもブロックが生じる挙動を持ちます (buffer が満杯になった場合。デフォルト buffer は 0 なので 1 つ受信するとそれが送信されるまでブロックされる)。今回は受信するまでプロセスをブロックする挙動を利用して goroutine の終了を待つことにします。

ch を作成し ch を goroutine の func に引数として渡しておくことで goroutin から ch への送信 (ch <-)が行なえます。メインプロセスでは ch からの受信 (<-ch)をまつことによりプロセスをブロックすることができます。

<-ch + <-ch + <-ch として 3 度の送信を待っているので 3 度の受信が起こるまで (すなわちすべての goroutine が終了に至るまで) メインプロセスはブロックされます。

package main
import (
    "github.com/mmmpa/go_playground/easy"
    "log"
    "time"
)
func main() {
    ch := make(chan time.Duration)
    go async(ch)
    go async(ch)
    go async(ch)
    total := &lt;-ch + &lt;-ch + &lt;-ch
    log.Printf("finish: total: %v\n", total)
}
func async(ch chan time.Duration) {
    // これは 1 - 3 秒ランダムにスリープする自前関数です
    s := easy.RandomSecsSleep(3)
    log.Printf("sleep: %v\n", s)
    ch &lt;- s
}

sleep という log が出る

2017/10/18 05:15:30 sleep: 1s
2017/10/18 05:15:30 sleep: 1s
2017/10/18 05:15:31 sleep: 2s
2017/10/18 05:15:31 finish: total: 4s

余談

js の async/await 時に Promise を先に走らせて await あとに置くことで並列実行する手法が似ていると思いました。

大量の処理を goroutine 量を決めて処理する

限界いっぱいまで goroutine を発行すると特に web application では他の request 分の能力も食いつぶしてしまってよくないのではないでしょうか?ということで goroutine 数を絞って使いまわす方法です。

今回のケースでは大量の URL リストがありそれらをゆるやかにダウンロードするという想定をします。一般的に http request にはタイムアウトを設定します。その上で一度に数百のリクエストを発行してしまうと自分のネットワークキャパシティを食いつぶし、リスト後半がことごとくタイムアウト扱いとなるということもありえます。そこで一度の接続本数をある程度絞りましょう、という想定です。

  • 並列ダウンロード本数を抑えるために一定数の worker goroutine を作成します (fetch)。
  • worker はまだダウンロードされていない URL を必要としますが、これは別の goroutine から channel で供給します (provide)。channel には送信受信が成立しないとブロックされるという性質があるためです (もちろん channel に十分な量の buffer を設定すれば for などで一度に詰め込んでおくことは可能です)。
  • すべてのダウンロード結果を収集し「終了した」という判断をして結果を返すために receiver goroutine を作成します。この処理はメインプロセス内に書くことも可能です。

従来型の並列プログラミングでは fetch のみが存在し URL 取得と結果収集は mutex によるデータ保護下で行われていたと思います。それを channel による通信で行うのが golang way だと理解しています。

package main
import (
    "github.com/mmmpa/my_playground/easy"
    "log"
    "time"
    "sync"
)
func main() {
    // url を生成する自前関数です
    urls := easy.GenUrls(5)
    start := time.Now().UnixNano()
    maxWorkers := 2
    worker_in := provide(urls)
    worker_out := fetch(maxWorkers, worker_in)
    task_result := receive(worker_out)
    log.Printf(
        "finish: worker_total: %v, total: %v \n",
        &lt;-task_result,
        time.Duration(time.Now().UnixNano()-start),
    )
}
func receive(out chan time.Duration) chan time.Duration {
    re := make(chan time.Duration)
    go func() {
        defer close(re)
        total := time.Duration(0)
        defer func() { re &lt;- total }()
        for s := range out {
            total += s
        }
    }()
    return re
}
func provide(urls []string) chan string {
    in := make(chan string)
    go func() {
        defer close(in)
        for _, url := range urls {
            log.Printf("send: %v\n", url)
            in &lt;- url
        }
    }()
    return in
}
func fetch(maxWorkers int, in chan string) chan time.Duration {
    out := make(chan time.Duration)
    go func() {
        defer close(out)
        wg := sync.WaitGroup{}
        for i := 0; i &lt; maxWorkers; i++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                for url := range in {
                    log.Printf("start fetching: %v\n", url)
                    s := easy.RandomSecsSleep(3)
                    log.Printf("fetched: %v %v\n", url, s)
                    out &lt;- s
                }
                log.Print("in closed\n")
            }()
        }
        wg.Wait()
    }()
    return out
}
2017/10/21 06:05:12 send: url_1
2017/10/21 06:05:12 send: url_2
2017/10/21 06:05:12 send: url_3
2017/10/21 06:05:12 start fetching: url_2
2017/10/21 06:05:12 start fetching: url_1
2017/10/21 06:05:13 fetched: url_2 1s
2017/10/21 06:05:13 start fetching: url_3
2017/10/21 06:05:13 send: url_4
2017/10/21 06:05:14 fetched: url_1 2s
2017/10/21 06:05:14 start fetching: url_4
2017/10/21 06:05:14 send: url_5
2017/10/21 06:05:16 fetched: url_3 3s
2017/10/21 06:05:16 start fetching: url_5
2017/10/21 06:05:17 fetched: url_4 3s
2017/10/21 06:05:17 in closed
2017/10/21 06:05:17 fetched: url_5 1s
2017/10/21 06:05:17 in closed
2017/10/21 06:05:17 finish: worker_total: 10s, total: 5.000707599s

channel は関数内で生成する (結果的に goroutine も)

func provide(urls []string, in chan string) {
    defer close(in)
    for _, url := range urls {
        log.Printf("send: %v\n", url)
        in &lt;- url
    }
}

書き始めはこのような関数を用意し、メインプロセスで go provide(urls, in) としていました。channel を外部から注入していくスタイルですね。in を閉じられるとわかるのはこの関数のみなので defer close(in) がありますが、channel を供給するメインプロセス側では閉じる処理がどこにあるかが判然としません。

func provide(urls []string) chan string {
    in := make(chan string)
    go func() {
        defer close(in)
        for _, url := range urls {
            log.Printf("send: %v\n", url)
            in &lt;- url
        }
    }()
    return in
}

書き換えたあとの channel を返すスタイルの関数は本処理を 1 段ネストさせてしまう他、関数の呼び出し順序が縛られてしまうというデメリットがあります。しかし channel の発行場所で close を行えるというメリットがあります。close が外や他の関数にある場合、close closed channel などの事故を起こしてしまう可能性が高まりますので、安全性を考えると channel を返していくスタイルが良いと思いました。

しかし channel 注入型もメインプロセス上で channel と goroutine の関係性の見通しが良い感じもしますし、悩ましいところですね。

worker 用の channel を閉じる

func fetch(maxWorkers int, in chan string) chan time.Duration {
    out := make(chan time.Duration)
    go func() {
        defer close(out)
        wg := sync.WaitGroup{}
        for i := 0; i &lt; maxWorkers; i++ {
            wg.Add(1)
            // worker 本体
            go func() {
                defer wg.Done()
                for url := range in {
                    log.Printf("start fetching: %v\n", url)
                    s := easy.RandomSecsSleep(3)
                    log.Printf("fetched: %v %v\n", url, s)
                    out &lt;- s
                }
                log.Print("in closed\n")
            }()
        }
        wg.Wait()
    }()
    return out
}

worker が結果を送信する channel は複数の goroutine からの送信を受けつけるため、すべての goroutine が終わったのを確認してから閉じなければなりません。すべての worker が終わったことは sync.WaitGroup を使うことによって簡単に検知できますが、もう 1 段ネストが深くなってしまうのが難と言えば難です。また wg.Wait() 用の関数もまた goroutine にしなければなりません。

receive の終了

func receive(out chan time.Duration) chan time.Duration {
    re := make(chan time.Duration)
    go func() {
        defer close(re)
        total := time.Duration(0)
        defer func() { re &lt;- total }()
        for s := range out {
            total += s
        }
    }()
    return re
}

こうして供給、worker の channel を適切に閉じていくことにより、結果を収集する receiver も channel の close を見るだけで処理を終了することができるようになります。書き始めた当初はそのキビがわからず、len(urls) 回 for を回した後に終了するという処理になっていました。

今回の学習はここまで

Contextselect を用いた中断などもコードを書いて確認したのですが、疲れたので今回はここまで。

Debian Stretch に変えてから発生していた復旧不可能な画面フリーズについて解決できました

画面はフリーズ、ただしマウスだけは動くという状況で、さらに再生中の音楽などは鳴りつづけていたのでマシン自体は生きていました。そこで SSH から接続して原因を確認しました。

/var/log/syslog によると以下の通り。

Oct 17 05:59:18 debian kernel: [125120.564661] nouveau 0000:01:00.0: fifo: SCHED_ERROR 0a [CTXSW_TIMEOUT]
Oct 17 05:59:18 debian kernel: [125120.564667] nouveau 0000:01:00.0: fifo: gr engine fault on channel 9, recovering...

このメッセージ以降、画面が固まってしまいます。

nouveau

nouveau とはフリーのビデオドライバーです。エラーコードで検索しても特に解決方法は見つかりませんでしたが、わたしのビデオカードは NDIVIA 製でしかも以前の OS では純正のビデオドライバーをインストールできたことを思い出しました。

そして純正のビデオドライバーをインストールすることにより、この問題からは離れることができました。

NDIVIA 純正ビデオドライバーのインストール方法

基本的には https://wiki.debian.org/NvidiaGraphicsDrivers#Debian_9_.22Stretch.22 の手順に従うだけです。 /etc/apt/sources.list の編集、 apt updateapt install というおなじみの手順ですね。

/etc/apt/sources.list の編集

deb <a href="http://httpredir.debian.org/debian/">http://httpredir.debian.org/debian/</a> stretch main contrib non-free

installation & configuration

sudo apt install linux-headers-$(uname -r|sed 's/[^-]*-[^-]*-//') nvidia-driver nvidia-xconfig
sudo nvidia-xconfig
sudo shutdown -r now

手元に The Go Playground 的な環境を用意しておく

The Go Playground https://play.golang.org/ は便利ですが、IDE の補完に頼り切りのわたしにとっては逆に挙動の確認に時間がかかる手段となっています。

そこで fsnotify で保存ごとに実行してくれる main.go を用意してシュッと確認できる環境を用意しています。これを走らせた上で同 directory 上にファイルを配置すると勝手に走らせてくれるという寸法です。

main.go

package main
import (
    "log"
    "github.com/howeyc/fsnotify"
    "strings"
    "os/exec"
    "fmt"
    "bytes"
)
func main() {
    watcher, err := fsnotify.NewWatcher()
    done := make(chan bool)
    go func() {
        for {
            select {
            case ev := &lt;-watcher.Event:
                // through jetbrains temporary files
                if strings.Contains(ev.Name, "___") {
                    break
                }
                // through myselfoddog69
                if strings.Contains(ev.Name, "main.go") {
                    break
                }
                if ev.IsModify() {
                    log.Printf("Run: %v", ev.Name)
                    cmd := exec.Command("go", "run", ev.Name)
                    var out bytes.Buffer
                    var stderr bytes.Buffer
                    cmd.Stdout = &amp;out
                    cmd.Stderr = &amp;stderr
                    err := cmd.Run()
                    if err != nil {
                        log.Println(fmt.Sprint(err) + ": " + stderr.String())
                        break
                    }
                    log.Print(stderr.String())
                    log.Print(out.String())
                }
            case err := &lt;-watcher.Error:
                log.Println("error:", err)
            }
        }
    }()
    err = watcher.Watch(".")
    if err != nil {
        log.Fatal(err)
    }
    // Hang so program doesn't exit
    &lt;-done
    watcher.Close()
}

エブリベンチ

毎日、あるいは 1 日おきなどの高頻度でベンチプレスをするスタイルをエブリベンチといいます。ベンチプレス関係の競技の記録保持者も行っている程度にはデタラメでないトレーニング方法ですが、効果の要因は諸説あるようです。

わたしはつい最近自宅でベンチプレスをはじめましたが、エブリベンチはわたしのようなホームトレーニーに最適ではないかと思います。安全面の問題からギリギリの重量で追い込むことはできませんから、せめてできるだけ多く刺激を与える必要があります。4 セット目でようやく 10 回できるかできないかぐらいになる重量だと、疲労が上回ってしまうこともなく、毎日できます (なおもうすぐ 40 歳とかいう年齢です)。

始めた重量は 45kg という軽量のものでしたし、いまも 50kg とかですが、増量直後は 3 セット目ぐらいで 8 回が限界になっていたものの、1 週間後には 50kg で 4 セット 10 回こなせるようになりました。

さすがにこのペースのまま伸びるとは考えていませんが、安全を確保しつつある程度伸ばしていけるのなら、これはアリの方法だと考えています。

2017/09

Farfetch で返品した

Farfetch で十数万のレザーパーカーを買ったんだけど微妙にシルエットに不満があった。本当に微妙な不満だったので、一度試着してから一晩置いて、翌朝また試着した。そして、やっぱり不満だったので返品手続きをした。

通販でそんな高い服を買ったのは返品もサービスに含まれているのが大きな理由だった。連絡して、当日に DHL の回収担当がやってくる。書類と商品を確認して持ち帰ってそれで終わり。

翌日になると返品ステータスが「商品引取り待ち」から「商品返品輸送中」に変わっていたので何も問題はなかったが、「回収員の人がニセモノでもこれじゃわからないな」と回収後にちょっと不安になったのだった。

引き渡す際には回収手続き番号などを確認したほうがよかったのだろうか。返品サービス自体は注文のハードルを下げるのですごくいいと思うが、ここらへんの引渡し時確認手順みたいなのは明記して欲しいところ。

有線マウスが好き

有線マウスが好きでずっと使っているんだけど、取り回しがむずかしい。有線マウスが好きな理由はとにかくその軽さが理由なんだけど、マウスを移動するにあたってコードが動きそれが何かに接触すると抵抗が起こり、結果としてものすごい不快感が発生する。

一昨日までは机の右の空間には何もなくて自由気ままに動いていたのだけれど、右側に机を配置して (2 枚目のサブディスプレイを置いた) その上に余っているノート PC を載せた結果、コードが接触するようになってしまった。

ノート PC を適切に移動するかどければいいのはわかってるんだが、いい場所がなくて困っている。

2017/08

IntelliJ IDEA の右クリックから Atom でファイルを開けるようにする (ついでに Thunar からも)

IntelliJ IDEA

File > Setting > Appearance & Behavior > Menus and Toolbars に各ポップアップメニューの項目があるので、そこの任意の位置に External tools に登録 (後述) しておいた Atom を配置します。

f:id:mmmpa:20170831041259p:plain

External tools に登録

File > Setting > Tools > External Tools の [+] ボタンをクリックして登録を行います。コマンドラインから開く時と同じように設定すればそれで動きます。

ただ Open console のチェックをはずす、 Parameters-n オプションをつけるなど微調整をしたほうが心地よく使えるでしょう。(-n オプションをつけていないと既存のウィンドウに開かれる場合があり、複数のワークスペースで Atom を開いてる場合に現在アクティブなワークスペースで開かない場合がありすこし鬱陶しい)

f:id:mmmpa:20170831042014p:plain

Thunar

Thunar は Xfce についてきたファイルマネージャーですが、編集 > アクションの設定 でポップアップメニューに項目を追加できます。

f:id:mmmpa:20170831042205p:plain

おわり

日常業務において IntelliJ IDEA に不満はありませんが Markdown の補完が肌にあわないのでプレビューを行える Atom を使用しています。-n オプションを知るまでは Atom も大変不便な場合があったのですが、ちゃんと help を読んで心地よく使えるようになりました。

Terminator (ターミナル拡張) のタイトル部を、ウィンドウごとに任意のものに変更する (bash)

f:id:mmmpa:20170829034221p:plain

ターミナルを立ち上げまくっていると上のようなことになってどれがどれだかわからず大変につらい。

このタイトルはプロンプトの変更と同じく PS1 に食わすことで変更できます。プロンプトを生かしたままで変更しなければならないので ~/.bashrc などに関数を定義して一発で変更できるようにします。

set_title () {
  ORIG=$PS1
  TITLE="\e]2;$*\a"
  PS1=${ORIG}${TITLE}
}
set_title EC2

f:id:mmmpa:20170829040402p:plain

Terminator ではフォーカスがあたっているターミナルのタイトルが表示されるので、分割しまくっている場合は Broadcast all などで全てに設定しないといけないのが少し手間ですね。

参考

stackoverflow.com

Bash/プロンプトのカスタマイズ - ArchWiki

プロンプトと同じようにシェルにエスケープシーケンスを出力することでターミナルのウィンドウタイトルもカスタマイズできます。プロンプトでウィンドウタイトルのカスタマイズすることができます。技術的には xterm の機能ですが、近代的なターミナルの多くがカスタマイズをサポートしています。使用するエスケープシーケンスは ESC]2;new titleBEL です。ESC と BEL はエスケープとベルの文字列に置き換えてください。

"\e]2;NEW TITLE\a"

Amazon Linux で普通に yum install した Nginx の auth_request_module を有効にしたい

現在の設定をそのままにコンパイルし直すという手を使いました。

  1. nginx -V で現在の configure を得る
  2. 1 で得た configure に --with-http_auth_request_module を加える
  3. Nginx 同バージョンのソースを得る
  4. ./configure が通るまで足りないライブラリを追加する
  5. make && make install
  6. nginx -s reload
$ sudo nginx -V
# 略
$ wget https://nginx.org/download/nginx-1.10.3.tar.gz
# 移動とか略
$ sudo yum install -y pcre-devel zlib-devel openssl-devel gcc gcc-c++ make gd-devel perl-ExtUtils-Embed GeoIP-devel gperftools-devel
$ sudo ./configure --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/var/run/nginx.pid --lock-path=/var/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --with-ipv6 --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_addition_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_slice_module --with-http_stub_status_module --with-http_perl_module=dynamic --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-google_perftools_module --with-debug --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic' --with-ld-opt=' -Wl,-E --with-http_auth_request_module'
$ sudo make
$ sudo make install
$ sudo nginx -s reload

Nginx で print デバッグ

そもそもきちんとログに出してログを見ろという話ですが、 return を使えばどの変数になにが入っているかを簡易に確認できます。

server {
  listen 9998;

  location = /check-header {
    add_header Content-Type text/plain;

    # ステータスコードとボディ文字列
    return 200 $http_user_uuid;
  }
}

f:id:mmmpa:20170824093050p:plain

特に夏だからというわけではないが体重を 10 kg 減らしたのでグラフ

減りました。

2017/3/27 開始の本日までのグラフです。(記録がとんでる部分があるので日数は一致しない)

test

なぜ?

2017/3/27 に行った人間ドックの最後の説明で空腹時血糖値が境界を超えてアウトだったので「ヤバいマジ糖尿」という雰囲気のことを言われた。

(なお HbA1c は普通で、しかも後日普通の健康診断で普通に血液検査をしたところ、ちょっと高めではあるが境界にも達していない平常値だった。脅しすぎ医者 + ビビりすぎ私)

どうやって?

食事を減らした

減らした後の食事はおおむね以下の通り

  • 朝: 魚肉ソーセージ 4 本
  • 朝から昼にかけて: サラダチキン 3 枚
  • 夜: 1000 kcal 前後 + 泥酔するまで飲む

体重が増えていた時はこれに加えて魚肉ソーセージが 2 本と胸肉が 3 枚ほど追加されていた。体重が増え続けていたのも納得だし、それを削って体重が減ったのも納得というところ。

運動は減らさなかった

1 日 2 時間のエアロバイク運動は食事を減らした後も継続した。腹が減っていてもなんとか漕げる。エアロバイク中は暇なのでブラッドボーンなどのゲームをしたり電子書籍を Android や iPhone に音読してもらっている。

なけなしの筋肉を落としたくなかったので筋トレもちゃんとやるようにした。おかげで必要以上に腕などは細くならなかったと思う。

まとめ

食事を見なおせば体重は減る。

Rails の ActiveRecord での lock! の細かい話

前プロジェクトでログを眺めていて気づきました。仕組みを考えれば確かにという感じだった。

class User < ApplicationRecord
  after_initialize -> { logger.info('Instantiate!') }
end
User.find(1).lock!
#  User Load (0.2ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
# Instantiate!
#  User Load (0.3ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 FOR UPDATE
# Instantiate!

User.lock(true).find(1)
#  User Load (0.5ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 FOR UPDATE
# Instantiate!

2017/05

騒がしい社内で心穏やかに仕事するためのセット

みなさん、静かな環境で心穏やかにお仕事されていますでしょうか?

わたくし、前職は非常におしゃべり好きな人間がおおい職場で、さまざまな会話音声により集中力をそがれておりました。他人のくだらない音声ほど集中力をそぐものはないと思います。

そんなわたくしに心の平穏をもたらした 2 つの道具を紹介させていただきます。

ぶっちゃけた話、耳栓

イヤホンなどで大音量で音楽を流すという手もあるのですが、それはそれで音に集中力を持っていかれるので、やはりここは静寂を求めていきたいところです。

そこで、以下の 2 つを組みあわせることで、比較的安価にあなたの耳に静寂を取り戻すことができます。

f:id:mmmpa:20170529184752j:plain

イヤーマフ PELTOR H540B

写真奥手に見えるのがこのイヤーマフです。

頭にひっかけるタイプではなく、挟みこむ力で状態を維持するので、多少の締め付けがありますが、防音性能はおりがみ付きです。

髪にかからないので、派手な髪型の御仁や、頭皮への圧力が気になるお年ごろの方でも安心ですね。

任意の、耳に突っ込むタイプの耳栓

MOLDEX 耳栓 8種セット プラケース付

MOLDEX 耳栓 8種セット プラケース付

写真手前にうつっているのが耳栓です。耳に突っ込むタイプなら割とどれも性能がよくて、わたしは 500 円ぐらいで amazon で買える、テキトーな詰めあわせのものを使っています。

イヤーマフのみでもかなりの静けさは得られるのですが、ダメ押しとして突っこんでおくと、静けさがうるさいぐらいに静かになります。

耳栓のかわりにカナル型のイヤホンを突っこむと、音量をさほど上げることなく、好みの音楽の下で穏やかに働くことができるので、そちらもおすすめです。

まとめ

前職、前前職ではこの 2 つを組みあわせることにより、平穏を得ることに成功していました。

現職ではリモートワークなので、人の会話音声や騒音の類に悩まされることはほぼないのですが、隣室でリフォーム工事がはじまり急遽復活の要が発生したので、ついでに紹介してみました。

おすすめです。

2017/03

3.11

あの震災から時間が経つうちに、身の回りで人間結構死んだな〜みたいな感慨があった。

(先週ぐらいに震災と人間が紐づく品が出てきたのだった)

2016/12

Ruby on Rails + webpack-dev-server で Development.

Rails で JavaScript が必要なプライベートプロジェクトでは、ながらく Npm + Watchify + Gulp + Rails を手動起動などしながらすすめていましたが、最近、意を決して Yarn + Webpack + Rails に変更しました。そこで、Rails と JavaScript の接合点も Railsway にのった設定に変えようと思いました。(今までは public/css を直接参照していた)

ざっと検索したところ、webpack-dev-server 経由で JavaScript を参照する場合、あらたな helper を用意する方式しか見つけられなかったので、assets の path を変更する方式をメモります。

概要

webpack-dev-server を port 5001 で起動し、Rails の assets 用のタグがそれを参照するようにします。

Rails configuration

#config/environments/development.rb

Rails.application.configure do
  # snip
  config.action_controller.asset_host = 'http://localhost:5001'
end

Webpack configuration

Files place

JavaScript ファイルとして app1.js, app2.js, app3.js が書きだされる想定です。

- app # 他 Rails のディレクトリ
- public
- lib
  - sass
    - common.sass
  - src
    - app1
      - index.js
    - app2
      - index.js
    - app3
      - index.js
  - package.json

package.json

style-loader を噛ませるととても遅いので、sass は node-sass ダイレクトで書きだします。書きだし先のディレクトリを参照できるように、webpack-dev-server を --content-base でルートを public に合わせておきます。

  "scripts": {
    "watch": "webpack-dev-server --progress --colors --config ./webpack/dev.config.js --port 5001 --content-base ../public",
    "watch-sass": "node-sass --watch ./sass/common.sass ../public/stylesheets/common.css"
  },

webpack.config.js

publicPath を javascripts という、Rails ネーミングに合わせておきます。

sass も webpack-dev-server を通す場合、こちらにも publicPath が必要ですが、こちらは ExtractTextPlugin 側に設定しなければならない点に注意します。

const entryPoints =  require('glob').sync('./src/*/index.js').reduce((a, v)=> {
  a[v.split('/')[2]] = v
  return a
}, {})

const jsConfiguration = {
  entry: entryPoints,
  output: {
    publicPath: 'javascripts',
    filename: "[name].js"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  }
}

const ExtractTextPlugin = require('extract-text-webpack-plugin')
const cssConfiguration = {
  entry: './sass/common.sass',
  output: {
    publicPath: 'stylesheets',
    filename: "common.css"
  },
  module: {
    loaders: [
      {
        test: /\.sass|\.scss/,
        loader: ExtractTextPlugin.extract({
          fallbackLoader: "style-loader",
          loader: "css-loader?minimize!sass-loader",
          publicPath: 'stylesheets',
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("common.css")
  ]
}

module.exports = [
  jsConfiguration,
  // cssConfiguration
]

できあがり

これで以下のようにコンパイルされ、webpack-dev-server は狙ったファイルを返すようになりました。

= javascript_include_tag('app1.js')
<script src="http://localhost:5001/javascripts/app1.js"></script>

なお Rails 起動時には webpack-dev-server も同時に起動することになりますが、Procfile.dev を用意して foreman で起動すると手間がかからなくて良いようです。

web: bundle exec rails server -p 5000
compile: cd lib/es && yarn run watch
compile: cd lib/es && yarn run watch-sass
foreman start -f Procfile.dev

今年の洗濯回数は 8 回でした。

一人暮らしももう 20 年に近くなりますが、洗濯機が家にあったことがありません。家に洗濯機がないので、コインランドリーを利用しています。利用料金は洗濯に 1,200 円、乾燥機に 700 円ほどです。

乾燥機は 25 kg対応の大型のものが 700 円で 56 分ほどまわります。本来はそこまでの容量のものを、そこまでの時間回す必要がないのですが、とにかくパリパリにするのが好きなのでまわします。

1 回目 2 月

2 回目 4 月

記録なし

3 回目 5 月

4 回目 7 月

5 回目 8 月

就職しました。

6 回目 10 月

7 回目 11 月

8 回目 12 月

まとめ

冬場はパーカーが洗濯物に加わりますので、少しペースが早くなりますね。

今年も少し T シャツとブリーフが増えましたが、同じぐらい捨てました。来年も同様のペースになることでしょう。よろしくお願いいたします。

top コマンドで得られる結果をどう見ていけばいいのかわからないので、とりあえず視覚化した

もともと一つの処理であるとか、一つのメソッドであるとかの処理時間には興味があって、測定などをしていました。ActiveRecord を使うにあたって発行クエリを抑えることにより、本当に早くなるのか確かめたりするのがすきです。

全体的なパフォーマンスを測定し、改善に繋げる知識がない

そういう局所的なものは何度もまわして処理時間を取って、という方法でなんとなく測定をできるのですが、Web アプリケーション全体の負荷の把握方法や、運用においてのスローダウンの原因究明などに使える情報などの取得については、ほとんど知識がありません。

ぐぐることにより、各種コマンドで負荷を数値化できることはすぐにわかりましたが、その数値が何を意味するかはよくわかりません (負荷が高い低いぐらいはわかるが、どういう経緯でそうなったかわからない)。さらに、これを蓄積し、それをそのまま眺めたところで、勘の悪いわたしが何かを把握できるとは思えなかったので、せめてわかりやすい形に変換しようと、最近取り組んでいたのが top のグラフ化です。

f:id:mmmpa:20161211200646g:plain

これは Chrome で 70 タブぐらい一気に開いたときの図です。CPU は大暴れ、メモリ消費もアゲアゲで大変きれいですね。

どこかにありそうなソフトですが、こういうラッパーのようなものをつくると、そのコマンドと仲良くなれるのでまるっきり無駄ということはないと信じています。

勉強

現段階では単にグラフ化しただけなので、ここからどうつなげていくかは要勉強というところですが、これだけでも Chrome の大量のタブは大量のプロセスを産んで、それぞれ個別にちょっとずつメモリを消費して大変なことになるので、個別のメモリに注目していてはその本当の負荷はわからないということがわかりました。

いまは、翔泳社 + kindle のポイント還元セールで入手した以下の本で、パフォーマンスのあれこれに関する勘所を把握しようとしています。各分野においての基礎知識を軽く説明ののちに、パフォーマンスなどの原因や解決法の話に入るので、まるでわからない状態で聞かされることがなく、いい本だと思います。

絵で見てわかるシステムパフォーマンスの仕組み

絵で見てわかるシステムパフォーマンスの仕組み

その他

プログラムのほうは、バックエンドは Go から SSH で対象サーバーに接続していますが goroutine の不適切使用により、同データへの同時アクセスが原因と思われる panic というのを経験して、非同期処理の経験値がアップしました。よかったですね。

Go 言語でつくったもののメモリとかをなんとなく見れるようにする

pprof を用いた詳細な情報を得る方法はさんざん紹介されており、しかしその詳細の情報のどこを見ればいいのかわからないので、とにかく簡単に見れるような施策を打ってみました。

f:id:mmmpa:20161202202126g:plain

これは社の Slack で動いてるボットの一つの強制停止画面ですが、ボットは継続して動いているので、メモリリークなどが気になるところです。ということで、グラフかつ、前後の増減がスッと把握できるようにしました。(これは短期すぎて役に立たなさそうですが)

グラフの描画は雑に C3.js で行っており雑な JavaScript なので特にあらためて何もないのですが、グラフを描画する用のデータ配列に関しては、今後もちゃんと把握して真面目に実装していきたいので、package 化して再利用できるようにしました。

github.com

go heaper.Run(1, 60)

http.HandleFunc("/heaps", func(w http.ResponseWriter, req *http.Request) {
    heaps, _ := json.Marshal(heaper.Read())
    w.Header().Set("Content-Type", "application/json")
    w.Write(heaps)
})

こんな感じで pprof.WriteHeapProfile で得られる情報を任意の間隔 (1 sec)、任意の量 (60) ためておき、見れるという感じで、今後、どこを見ればいいのかわかるようになれば、それなりに役に立ってくれるはずです、多分。ただ単に画面が動いてるのが好きなのでやってみたという気がしないでもない。

2016/11

Go 言語で struct を url.Values に展開した

現在勉強用に作っているボットでは、Slack の API を使います。勉強用なので、ライブラリを使わず、独自に実装しています。API ではエンドポイント毎にさまざまな要求パラメーターがあって、その引数を自由な形式にすると間違いのもとになるので、専用の struct を用意して間違いが起こらないようにしています。

func (a *API) ChatPostMessages(p ChatPostMessageParameters) (ChatPostMessagesResponse, error) {
    var r ChatPostMessagesResponse
    err := a.postAndStruct(&r, "/chat.postMessage", p)

    return r, err
}

public にしているメソッドはそのようなキッチリ定義した struct を引数として要求する一方で、色々なエンドポイントを叩くときのメソッド (postAndStruct など) は共通化したかったため、private にしてあるその共通メソッドの引数では、どんな struct でも受けつけて url.Values に変換、POST や GET したいと思いました。

というわけで、あまり推奨されていないであろう、reflect で struct をなんでも展開という方法を使います。

func (a *API) buildParameters(parameters interface{}) url.Values {
    values := url.Values{}
    r := reflect.ValueOf(parameters)
    rt := r.Type()

    for i := 0; i < rt.NumField(); i++ {
        f := rt.Field(i)

        key := ""
        if to := f.Tag.Get("json"); to != "" {
            key = to
        } else {
            key = strings.ToLower(f.Name)
        }

        v := fmt.Sprint(r.Field(i))

        if v != "" {
            values.Add(key, v)
        }
    }

    values.Add("token", a.Token)

    return values
}

interfrace{} として受け取った値を reflect.ValueOf(parameters) するのがキモで、それ以外は流れ作業でした。どうやれば interface{} -> struct できるかというのは github.com/fatih/structs というライブラリを読んで知りました。ソースコードがスッと読める言語は、こういう時に便利ですね。

github.com

Go 言語でつくったボットを GitHub -> CircleCI -> Bluemix と自動デプロイできるようにした

Golang でのボット作成では、1 個目は自 PC で動かすことしか考えていませんでしたが、2 個目はきちんとどこかにデプロイすることを目標に作成していましたので、ついでに自動でやれるようにしました。

Heroku では 24 時間稼働のボット用途には不向きだと思われたので、IBM Bluemix にデプロイ先に選びました。Bluemix もデフォルトで Golang をサポートしていますが、ビルトインの buildpack だと Godeps のバージョンをきちんと読んでくれなかったり (これは気のせいかもしれない) したのでgithub にあるものを利用したりして色々とあれでしたが、うまく行きました。

circle.yml の様子。

machine:
  environment:
    REPO_ROOT: "${HOME}/.go_workspace/src/YOUR_DOMAIN/YOUR_PACKAGE_DIR"

dependencies:
  pre:
    - mkdir -p ${REPO_ROOT}
    - cp -rf ./* ${REPO_ROOT}
    - go get github.com/tools/godep
    - curl -v -L -o cf-cli_amd64.deb 'https://cli.run.pivotal.io/stable?release=debian64&source=github'
    - sudo dpkg -i cf-cli_amd64.deb
    - cf -v

test:
  pre:
    - go vet ./...
  override:
    - cd ${REPO_ROOT} && godep go test ./...
  post:
    - cf api https://api.au-syd.bluemix.net
    - cf login -u $BLUEMIX_USER -p $BLUEMIX_PASSWORD
    - cf target -o $BLUEMIX_ORG -s $BLUEMIX_SPACE
    - cf a

deployment:
  production:
    branch: master
    commands:
      - cf push APP_NAME -b https://github.com/cloudfoundry/go-buildpack.git

とにかくプッシュ、テスト、デプロイまでをやれるようにとツギハギしたので、まだ無駄があると思いますが、とりあえずこれでアップできます。

つまずきポイントとしては、

  • ${HOME}/.go_workspace/src/ へリポジトリを展開しないと、go get -t -d -v ./... でリモートからパッケージ fetch しようとして死んだ (YOUR_DOMAIN を github ではないところにしているため、みつからない)
  • cf api でエリア指定を間違えた (だいたいの例はアメリカ南部になっているが、シドニーを使っている)
  • cf のインターフェースが gem install cf のものとちょっとちがう

などがありました。

基本的にはテストが通ったら cf で Bluemix にデプロイするというだけなので、すでに cf でのデプロイを済ませている人は、設定の際にあらためて考えることはない感じですね。

Slack で動いてるボットの処理が長い場合、フィードバックとしてインジケーターを出すということをやった

いま golang の練習用に作成しているボットには、URL をわたすと、そのサイトのキャプチャを撮影する機能があります。諸事情からボットのいるマシンとは別の場所、Heroku に設置していますが、起動が遅かったり、キャプチャ自体が遅かったりするので、ちゃんとやっていってるのかわかりません。そこで、ローディングインジケーターを出すことにしました。

できあがり

f:id:mmmpa:20161120045725g:plain

「こはる」とは社の Slack にいるボットですが、対抗して golang でつくっているのが「ごはる」です。かわいいですね。

アイコンの元ネタには以下を使わせてもらっております。

github.com

インジケーター

まずインジケーターを作成しました。実際はベタッと書いてから切りだしましたが、とにかくまずインジケーターを用意します。できあがりを想像してワクワクするのが原動力です。

type CaptureLoadingIndicator struct {
    indicators []string
    length     int
    step       int
}

func (c *CaptureLoadingIndicator) initialize(indicators []string) {
    c.indicators = indicators
    c.length = len(c.indicators)
    c.step = c.length - 1
}

func (c *CaptureLoadingIndicator) next() string {
    c.step = ((c.step + 1) % c.length)
    return c.indicators[c.step]
}

func createCaptureLoadingIndicator() *CaptureLoadingIndicator {
    c := (&CaptureLoadingIndicator{})
    c.initialize([]string{"▖", "▘", "▝", "▗"})

    return c
}

goroutine

キャプチャ機能では、キャプチャされた画像は Heroku からダイレクトに Slack に投稿されますが、キャプチャできたかどうかは Response で受けとれます。よって、Heroku へのリクエスト開始をインジケーターの開始、Response が帰ってくれば終了するとします。

細かい処理ははぶいて、goroutine 部です。http.PostForm は同期的に処理されますから、基本的に以降の処理をブロックします。これを goroutine に追いだして本プロセスとは並行に行いつつ、終わるまでの間はインジケーターを表示します。

Big Sky :: golang の channel を使ったテクニックあれこれを参考に、処理が終わるまで待つ処理を書きます。

// インジケーターを表示するメッセージの TS を取得する
var ind slack.ChatPostMessagesResponse
slack.ChatPostMessage.Strike(&ind, string(m.Channel), "starting...")

// 以下で goroutine ポーリングする
q := make(chan CaptureServerResult, 2)

go func() {
    // Heroku へリクエスト開始
    resp, err := http.PostForm(captureServer, values)
    // リクエスト終わり
    q <- CaptureServerResult{resp, err}
}()

indicator := createCaptureLoadingIndicator()
for {
    if len(q) > 0 {
        break
    }
    // 上記で取得した TS をターゲットとして、chat.update をかける
    slack.ChatUpdate.Strike(nil, string(m.Channel), ind.TS, fmt.Sprintf("loading `%v`", indicator.next()))
    // update 間隔は加減しましょう
    time.Sleep(200 * time.Millisecond)
}

slack.ChatUpdate.Strike(nil, string(m.Channel), ind.TS, "loaded")

resp := <- q
// response をもとにした処理が続く

<- q は処理をブロックしてしまうので、なんの動きもない重い処理、待ちの時に他の処理をさせておきたい場合はチェッカーとしては使えないんですね。

しかし、Channel をうまく利用することにより、簡単に実現することが出来ました (先達の知識に感謝)。こういうのは大体、開始や interval 処理ではなく、終了がめんどくさかったりするのですが、それが特に問題にならずよかったです。

あと func(){}() ができるのが色々便利ですね。

Ruby 外から Web アクセスする何か (Capybara とか、cli とか) を RSpec でテストするときのアクセス先をモックする

mmmpa.mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmpa.net

のテストをこの方法で行いました。

本編

たとえば、Capybara は各種ブラウザを介するアクセスのため、webmock が効かず、別の gem が必要なのは有名です。 https://github.com/oesmith/puffing-billy

Capybara テストする際はこれでいいのですが、Capybara テストする場合には不便かもしれませんね。

また、バッククォートや Open3 を用いて呼びだすような cli、たとえば siege などのターゲットをモックすることはできません。cli におんぶにだっこの gem を開発するときのテストに、かなり不便ですね。

RSpec 開始時にサーバーをたてる

望んだ結果を返すアクセス先があればいいので、テスト中に起動する仮想サーバーをたてます。

public ディレクトリに適当な HTML を用意した上で、

RSpec.configure do |config|
  require 'webrick'
  config.before(:suite) do
    port = ENV['MOCK_PORT'] || 3000
    host = ENV['MOCK_HOST'] || '127.0.0.1'
    started = false

    Thread.start do
      WEBrick::HTTPServer.new(
        DocumentRoot: File.expand_path('./public/', __dir__),
        BindAddress: host,
        Port: port,
        AccessLog: [],
        StartCallback: ->{ started = true }
        Logger: WEBrick::Log::new("/dev/null", 7)
      ).tap { |server|
        Signal.trap(:INT) { server.shutdown }
        server.start
      }
    end

    while !started
      sleep 0.5
    end
  end
end

Thread はあたらしいものを用意しないと、sever.start の時点で RSpec のプロセスが停滞してしまいますので注意しましょう。

また、別 Thread になるので server.start が完了するしないにかかわらずテストに突入、テストが落ちるということがあるので、StartCallback を使って、きちんとサーバーがスタートしたことを確認してからはじめましょう。

単純なアクセス以外

さらに、リダイレクトなど、単純な HTML アクセス以外の処理が必要ならば、インスタンス servermount すると処理を追加できます。

# たとえばリダイレクト
# Rails を模するなら、301(Moved) ではなく 302(Found)
server.mount_proc('/redirect') do |req, res|
  res.set_redirect(WEBrick::HTTPStatus::Found, '/redirected.html')
end

できあがり

これで、以下のようなテストが、自由に行えるようになりました。

expect(`siege -t 10s http://127.0.0.1:3000/foo.html`).to be_truthy

例は siege 自体のテストのようになってしまっていますが、実際は取得した結果をあれこれしたものをテストしました。

雑ではありますが、とても楽に、多くのアクセス先を用意できるようになりました。

2016/10

siege をラップして多少細かい情報をまとめる gem 書いた。

github.com

Usage

たとえば Rails 内でこうやる。

re = SiegeSiege.run(
      time: 20,
      concurrent: 4,
      user_agent: false,
      urls:
        [
          "http://localhost:3002#{students_path}",
          "http://localhost:3002#{students_path} POST name=abc",
          SiegeSiege::URL.new("http://localhost:3002#{students_path}", :post, {name: 'abc'}),
        ] + Student.ids.shuffle[0..2].map { |id| "http://localhost:3002#{student_path(id)}" }
    )

するとこういうのが得られる。

普通に使ってもコンソールに出てきてたやつ

re.total_result
=> {:command=>"siege -v -c 4 -t 20s -r 1 -R /tmp/20161030-21093-1lo07ca -f /tmp/20161030-21093-13f1kj6",
 :defaulting_to_time_based_testing=>{:value=>20.0, :unit=>"seconds"},
 :transactions=>{:value=>96.0, :unit=>"hits"},
 :availability=>{:value=>75.0, :unit=>"%"},
 :elapsed_time=>{:value=>19.47, :unit=>"secs"},
 :data_transferred=>{:value=>2.03, :unit=>"MB"},
 :response_time=>{:value=>0.8, :unit=>"secs"},
 :transaction_rate=>{:value=>4.93, :unit=>"trans/sec"},
 :throughput=>{:value=>0.1, :unit=>"MB/sec"},
 :concurrency=>{:value=>3.93, :unit=>""},
 :successful_transactions=>{:value=>96.0, :unit=>""},
 :failed_transactions=>{:value=>32.0, :unit=>""},
 :longest_transaction=>{:value=>10.65, :unit=>""},
 :shortest_transaction=>{:value=>0.01, :unit=>""}}

アクセスに使った URL 毎に平均レスポンス時間とか

re.average_log
=> [#<struct SiegeSiege::AverageLog id=6, url="http://localhost:3002/students/10763", count=17, secs=0.691, siege_url=#<SiegeSiege::URL:0x007fbee6efe870 @http_method=:get, @parameter={}, @url="http://localhost:3002/students/10763">>,
 #<struct SiegeSiege::AverageLog id=0, url="http://localhost:3002/students", count=16, secs=1.276, siege_url=#<SiegeSiege::URL:0x007fbee6efeb18 @http_method=:get, @parameter={}, @url="http://localhost:3002/students">>,
 #<struct SiegeSiege::AverageLog id=5, url="http://localhost:3002/students/39284", count=16, secs=0.767, siege_url=#<SiegeSiege::URL:0x007fbee6efe938 @http_method=:get, @parameter={}, @url="http://localhost:3002/students/39284">>,
 #<struct SiegeSiege::AverageLog id=7, url="http://localhost:3002/students/94576", count=17, secs=0.108, siege_url=#<SiegeSiege::URL:0x007fbee6efe7a8 @http_method=:get, @parameter={}, @url="http://localhost:3002/students/94576">>,
 #<struct SiegeSiege::AverageLog id=3, url="http://localhost:3002/students", count=15, secs=1.367, siege_url=#<SiegeSiege::URL:0x007fbee6cd9518 @http_method=:post, @parameter={:name=>"abc"}, @url="http://localhost:3002/students">>,
 #<struct SiegeSiege::AverageLog id=1, url="http://localhost:3002/students", count=15, secs=0.651, siege_url=#<SiegeSiege::URL:0x007fbee6efea50 @http_method=:post, @parameter="name=abc", @url="http://localhost:3002/students">>]

標準出力をまとめたやつ

re.raw_log
=> [#<struct SiegeSiege::LineLog protocol="HTTP/1.1", status="200", secs=10.09, bytes=633, url="http://localhost:3002/students/10763", id=6, date=Sun, 30 Oct 2016 13:10:31 +0000, siege_url=#<SiegeSiege::URL:0x007fbee6efe870 @http_method=:get, @parameter={}, @url="http://localhost:3002/students/10763">>,
 #<struct SiegeSiege::LineLog protocol="HTTP/1.1", status="200", secs=10.29, bytes=45579, url="http://localhost:3002/students", id=0, date=Sun, 30 Oct 2016 13:10:31 +0000, siege_url=#<SiegeSiege::URL:0x007fbee6efeb18 @http_method=:get, @parameter={}, @url="http://localhost:3002/students">>,
 #<struct SiegeSiege::LineLog protocol="HTTP/1.1", status="200", secs=10.24, bytes=633, url="http://localhost:3002/students/39284", id=5, date=Sun, 30 Oct 2016 13:10:31 +0000, siege_url=#<SiegeSiege::URL:0x007fbee6efe938 @http_method=:get, @parameter={}, @url="http://localhost:3002/students/39284">>,
 #<struct SiegeSiege::LineLog protocol="HTTP/1.1", status="200", secs=0.25, bytes=633, url="http://localhost:3002/students/94576", id=7, date=Sun, 30 Oct 2016 13:10:41 +0000, siege_url=#
# 略

所感

カジュアルにテストできて便利ですね、みたいなツールで、詳細な情報を取ろうとするのはやめといた方がいいと思いました。(適切なやつをさがそう)

Capybara で Chromedriver をつかってモバイルモードでテストする時の Capybara.register_driver とか

だいたいのサイトは User Agent で切り替えてると思うので、わざわざモバイルモードでテストが必要なのかしらとか思わないでもない (ダブルタップとか、スワイプをテストする?)、が一応メモ。

Capybara に Driver として登録

# chromedriver configuration

# chrome の起動オプションが使える http://peter.sh/experiments/chromium-command-line-switches/
# デフォルトではサブのディスプレイに表示してしまうので、ずらしている
default_args = %w(
  --window-position=2560,0
)

# ブラウザの外枠 (スクリーンショット撮って計測しよう)
chrome_frame_offset = {
  w: 10,
  h: 86
}

# プリセットの商品のセッティングを使う場合。
# DevTools で選べるモバイルから必要なものをピックアップ。(リストデータくれよ)
[
  {name: 'Apple iPhone 6 Plus', w: 414, h: 736}, # :apple_iphone_6_plus
  {name: 'Google Nexus 7', w: 600, h: 960},      # :google_nexus_7
].map { |configure|
  configure[:w] += chrome_frame_offset[:w]
  configure[:h] += chrome_frame_offset[:h]
  configure
}.each do |configure|
  Capybara.register_driver configure[:name].gsub(' ', '_').downcase.to_sym do |app|
    Capybara::Selenium::Driver.new(
      app,
      browser: :chrome,
      desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
        'chromeOptions' => {
          args: [
            "--window-size=#{configure[:w]},#{configure[:h]}"
          ] + default_args,
          mobileEmulation: {deviceName: configure[:name]}
        }
      )
    )
  end
end

# 独自のセッティングでモバイルモードを使う場合
# これはイッパツで全画面をスクリーンショットしたかった時の設定
Capybara.register_driver :chrome_dummy_mobile do |app|
  w = 320
  h = 1600

  Capybara::Selenium::Driver.new(
    app,
    browser: :chrome,
    desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
      'chromeOptions' => {
        args: [
          "--window-size=#{w + chrome_frame_offset[:w]},#{h + chrome_frame_offset[:h]}"
        ] + default_args,
        mobileEmulation: {
          deviceMetrics: {
            width: w,
            height: h,
            pixelRatio: 2.0
          },
          userAgent:
            'Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1'
        }
      }
    )
  )
end

選んで使う

# 登録しておいた driver から選ぶ
Capybara.configure do |config|
  config.run_server = false
  config.default_driver = :apple_iphone_6_plus
  config.app_host = 'http://googole.com/'
end

RSpec とか Capybara の Example 内で使いたいメソッドをクラス単位でまとめて使いやすくする gem を書いた。

これ

github.com

先週の gem は 80 行ぐらいだったんですが、今回は 40 行です。

なぜ

入社してからこっち、ずっと Vue.js で SPA 制作という業務に従事していましたが、UI 系の宿命で (一部しか) テストがない。

そんななかで、わりと分岐の多い (難しくはない) 実装をすることになって、これは手作業では (変更の都度に) 確認しきれんなということで Capybara に出張ってもらっていました。テスト維持コスト問題というものがあるので、業務リポジトリには入っていない、ローカルでのみ存在する Capybara です。かわいいよ。

そんななかで findall を直書きしていると、途中での変更抜きにしても、見栄えがとてもつらいことになります。

経路によっては他の Example にも出てくるので、メソッド化をするわけですが、しかし、ページごとにちがう submit_button があったりするので、 registration_page_submit_buttonshopping_cart_submit_button など、なんとかして名前をかえていく必要がありまして、これが読みづらい。

というわけで Registration.submit_buttonShoppingCart.submit_button というように簡単にわけられるようにしました。

これが

def registration_page_submit_button
  find('button', text: 'Register Now!')
end

def shopping_cart_submit_button
  find('button', text: 'Buy Now!')
end

feature 'register then buy' do
  scenario do
    visit '/resistration'

    # input data to some form

    registration_page_submit_button.click

    visit '/cart'

    # input data to some form

    shopping_cart_submit_button.click
  end
end

こうなる

class RegistrationPage < RSpecMethodGrouping::Base
  def submit_button
   find('button', text: 'Register Now!')
  end
end
class ShoppingCart < RSpecMethodGrouping::Base
  def submit_button
   find('button', text: 'Buy Now!')
  end
end
feature 'register then buy' do
  scenario do
    visit '/resistration'

    # input data to some form

    RegistrationPage.submit_button.click

    visit '/cart'

    # input data to some form

    ShoppingCart.submit_button.click
  end
end

よみやすい!

かどうかは人によるかと思いますが、メソッドの管理がしやすくなったのでヨシとします。

この量ではわかりにくいかもしれませんが、他画面遷移 + 要素めちゃ多いとかになると、結構効いてきます。

テスト内の隠蔽どうなの問題

テストに使うデータを過度に隠蔽すると、テストの意図まで隠蔽されて、まるでわからないという問題があります。

しかし、これは、テストの主題ではなく、ページ内の要素を特定しておく (変更時はイッパツで対応できる) のが主目的なので、特に問題ないと思います。

もりもり Capybara でスクリーンショットを撮っていきましょう。

最近 SQL にはまっているので、ついでに Rails というか ActiveRecord で発行された SQL query をカウントする gem を書いた。

Installation

gem 'a_r_q_logger'

github.com

せつめい

ActiveRecord::Base サブクラスのインスタンス生成をできるだけ抑えれば、それだけ処理時間が抑えられるのは自明とされています。

そこで、今は、できるだけ少ない queries に抑えることを目的として SQL の練習をしていますので、すぐわかるようにしました。

その練習を進めていく上で、その 1 query の処理時間が気になっており、SQL のみの処理時間もとれるようにしましたというか、出発点はこっちです。

pry(main)> ARQLogger.log { 3.times { TestModel.has_children(10).map(&:children_count) } }
=> #<struct ARQLogger::Result count=3, msec=1508.4>

from 句で前もって as しておくのと、CASEWHEN ごとに (複数の) 集約関数を書くのではどっちが速いのか気になって、こういうことをしていたのです。 (おそらくオプティマイザがいい感じにしてくれるおかげで、CASE に同じ式を何回も書くほうが速かった)

テストでも使える

query を抑えようと書いたかっこいい生 SQLが、善意の第三者により書きかえられるということはありがちなことですが、amount of queries を抑えようと書いた生 SQL が、その善意により too many queries になっては目もあてられません。

query 発行量はコントロールできる要素ですから、これはテストで縛っておくことができます。

たとえば以下は N + 1 の解決法でよく出てくる includes の付与テストです。

it do
  expect(ARQLogger.log {
    TestModel.includes(:test_child_models).all.each { |m| m.test_child_models.map(&:name) }
  }.count).to eq(2)
end
it do
  expect(ARQLogger.log {
    TestModel.all.each { |m| m.test_child_models.map(&:name) }
  }.count).to eq(11)
end

久しぶりに gem を書くのが目的だった

どこかにありそうだなと思って軽く調べて、そんな感じの gem もありましたが軽く無視って書きました。

たのしかたです。

2016/08

npm run scripts で current directory を参照したい。

環境変数を使います。

$PWD ではダメ

npm run foo は常に基準となるディレクトリで実行されるので、たとえば以下の npm run here は、配下のディレクトリに潜っても同じ結果を返します。

"scripts": {
  "here": "echo $PWD"
}

これは current directory を考慮せずに node_modules を一定の条件で使えるので、とても便利です。

なので先に設定しておく

便利ですが、 current directory が欲しい場合もありますので、別個に環境変数を設定し、それを参照します。

"scripts": {
  "here": "echo $C_D"
}
$ C_D=$PWD npm run build

これで実行したディレクトリを得られます。(しかし調べればビルトインで参照できる変数がありそうである)

おわり。

なぜ

watchify

いまは watchify でコンパイル行為を行なっており、こういう build を用意しています。

"scripts": {
  "build": "watchify -t babelify --extension=js ./index.js -o ./built.js -v"
}

特定のディレクトリを指定して watchify

SPA を作成している時分にはこれでよかったのですが、今は自習として、共通のライブラリを使う別々の小さな js ファイルを作成するということをやっています。

いっぱいあるし、個々の build は書きたくない。

このようにしておいて

"scripts": {
  "build": "watchify -t babelify --extension=js $C_D/index.js -o $C_D/built.js -v"
}
$ C_D=$PWD npm run build

余分な js を書く必要がなくてハッピーでした。(脱 Gulp 中かつ、なんらかの他のランナーも使う予定がなかった)

就職できました。

1年間の無職を終えました。

HeartRails | ハートレイルズ | ザ・ウェブサービス・カンパニー

よろしくおねがいします。

2016/07

Capybaraのウェイト時間で足りない場合、さらに待たせる。

Capybaraはfindなどの要素検索メソッドで、見つかるまである程度の時間待ってくれます。

時間が足りない場合は

Capybara.default_max_wait_time = 30

などとすることで時間を増やせます。

all('hoge')[0].clickだとおなじみのundefined method `click' for nil:NilClassで終わる

終わりますので、終わらせないために適当にrescueします。

def re_find(wait = 0.5, count = 20, &block)
  yield
rescue
  raise if count == 0

  sleep wait
  re_find(wait, count - 1, &block)
end
re_find { page.all('a.niceButton')[0].click }

これで中断もできますし、急場はしのげますね。

Access-Control-Allow-Originが設定されてないWeb APIを叩くために中継サーバー書いた。

大げさなタイトルみがありますがただのRailsです。

github.com

うすうす実装なのでさがせばあるんでしょうけど、人が書いたもの使いたくない場合ってあるじゃないですか。

起動

$ TARGET=http://you-want-to-ajax.server.com ALLOW=http://your-local-js.server.com rails s

つかう

あとはhttp://your-local-js.server.comでうごくJavaScriptからhttp://localhost:3000のRailsを叩くだけ。

Ajax側の実装でエンドポイントの根本ドメイン変えられるようにしておけば安心ですね。

わからんかった

method == 'OPTIONS'のときはControllersession.idが取れないので、みんなたちで使えるような仕様にはできなかったので、ローカルだけで使えるものになった。

なんでかな。

vue-routerのroutesを全部出すやつ

だいたい見れる。

log(dig(router))
function digState (state, path = []) {
  state.nextStates.forEach((nextState) => {
    if (nextState.handlers) {
      nextState.handlers.forEach((handler) => path.push(handler))
    }
    if (nextState.charSpec.validChars) {
      digState(nextState, path)
    }
  })

  return path
}

function digNames (names, path = []) {
  for (let name in names) {
    router._recognizer.names[name].handlers.forEach((handler) => path.push(handler))
  }
}

function dig (router, path = []) {
  digState(router._recognizer.rootState, path)
  digNames(router._recognizer.names, path)

  return path
}

function log (path) {
  let logged = {}
  let logs = []
  path.forEach((hh) => {
    let h = hh.handler
    if (logged[h.fullPath]) {
      return
    }
    if (h.name) {
      logs.push(['/' + h.name, '=>', h.fullPath].join(' '))
    } else {
      logs.push(h.fullPath)
    }
    logged[h.fullPath] = true
  })

  logs.sort().forEach((l) => console.log(l))
}

ActiveAdmin辺りでCircular dependency detected while autoloading constant Fooが出る場合の措置。

Rails 4.2.7、ActiveAdmin 1.0.0.pre4で発生しました。

普通にやってる分には出ないんですけど、下の記事みたいに外からRails.application.require_environment!すると、models関連でCircular dependency detected while autoloading constant Fooが出ます。

mmmpa.mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmpa.net

措置

読み込み順序が問題なので、autoload_pathsの先頭にmodelsのディレクトリを追加してから、require_environment!します。

Rails.application.config.autoload_paths.unshift("#{Rails.root}/app/models")
Rails.application.require_environment!

ベーシック認証が必要なサイトにアクセスするテストで、ダイアログをシカトするためにNginxでProxyする。

Phantomjsじゃ動いてくれないJavaScriptライブラリがありまして(よくある)、Selenium + Firefoxでスクリーンショットおじさんになっています。

nginx.conf

実際はRubyではないのでこのようなコードではないんですけど、概念的にこんな感じです。

server {
    listen       8080;
    server_name  localhost;

    location / {
        proxy_set_header Authorization "Basic #{Base64.encode64 'id:password'}";
        proxy_pass https://my-secret-web-site.com;
    }
}

Base64エンコードしたのをヘッダに含めます。

一般的にはブラウザでのアクセスの際や、エンドポイントの設定ファイルでhttps://id:password@my-secret-web-site.comでやったりするんでしょうけど、ライブラリがパースをしくじったり、なんかブラウザに丸出しになるのがイヤって時に使えます。

Rails開発でリポジトリには入れたくないんだけどローカルではやっておきたいテストがある場合の取りあつかい

自明だったり、すごく重かったり、細かすぎるテストたちがいます。

重いテストは短期的にも長期的にもコストになり、細かすぎるテストは実装変更時のコストが必要以上に高く、よくありません。

しかし、たまにやっときたいんですよというテストがありますので、本体に影響がないようにやっていきます。

Railsのディレクトリの外にもう一層つくる

以下のような構成になりました。

- /my_test
  - /.git
  - /rails_application # 本体
  - /seed              # テスト用の
  - /spec              # 自分専用テスト
    - /models
    - rails_helper.rb  # 自分専用のテストのrails_helper.rb
  - .gitignore         # 本体を含めないため
  - Gemfile            # 自分専用テスト用のGemfile

自分だけが使うGemを用意する

テストで使いたいが、該当アプリのGemfileに入っていないので使えないGemというのがありますので、どうにかします。(今回のはテストで使う!というGemではないですが)

むりくりrequireしても、コンテキストが変わってしまい、ダメなようですので、本体のGemfileの内容をinstance_evalします。

instance_eval(File.read(File.expand_path('../rails_application/Gemfile', __FILE__)))

# 好きなGemを入れる
gem 'seed-fu'

seed-fu

ところで、ダミーデータもまた、大量だったり詳細だったりすると、コストになります。

しかしさまざまなレコード状態表示の調整をする場合など、ある局面では役に立つので、テスト以外でも使えるように用意しておきます。

#!/usr/bin/env ruby
require File.expand_path('../../rails_application/config/application', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
Rails.application.require_environment!

require 'seed-fu'

10000.times do |n|
  ModelA.seed(
    id: n,
    name: SecureRandom.hex(4)
  )
end
$ bundle exec ./seed/seed.rb 

これで外側から、自分のデベロップメントや、テストに、大量のダミーデータを投入できるようになりました。

テスト

rails_helper.rbをコピペして編集する

environmentspec_helperに対するパス部分を本体のファイルに当たるように変更します。

require File.expand_path('../../rails_application/config/environment', __FILE__)
require File.expand_path('../../rails_application/spec/spec_helper', __FILE__)

さきのseed-fuを利用する

seed-fuconfig.before :all内などに書いておけば安泰ではないでしょうか。DatabaseCleanerなどを使っている場合は、それが動いた後である必要があります。

require File.expand_path('../seed/seed', __FILE__)

実行

require 'rails_helper'

RSpec.describe ModelA, type: :model do
  it { expect(ModelA.create!).to be_a(ModelA) }
  it { expect(ModelA.count).to eq(10000) }
end
$ rspec -cfd

# 略

  should be a kind of ModelA(id: integer, name: string, created_at: datetime, updated_at: datetime)
  should eq 10000

Finished in 1 minute 41.39 seconds (files took 3.8 seconds to load)
2 examples, 0 failures

できました。

これはジョークのようなテストですが、たとえばCapybaraでスクリーンショットを撮りまくるといった、半分趣味のようなテストも、本体に影響なくどんどんやっていくことができます。

Authlogicに関するメモ

認証機能再発明するべからずは有名な鉄則ですが、まるで知らないままというのもマズイので実装を読むことでお茶を濁していきたい。

saltSCryptの運用、persistence_tokenの更新などで比較的安全にいけるのでは。

パスワード

salt

Authlogic::Random.friendly_token
def friendly_token
  # use base64url as defined by RFC4648
  SecureRandom.base64(15).tr('+/=', '').strip.delete("\n")
end

暗号化

authlogic/lib/authlogic/crypto_providers以下に、各種Cryptorをラップしたアダプターがある。

SCryptならAuthlogic::CryptoProviders::SCrypt#encrypt(*tokens)で暗号化パスワードを得られる。

*tokensには[password, salt]が入り、saltは保存されてログイン時のパスワード一致を見る時に使われる。

生パスワードは勿論破棄される。

パスワード確認

各種Cryptorの比較演算子を使ったりする。

# ユーザーのレコードから得る
salt = user.password_salt
encrypted = user.encryped_password

# 入力
password = 'aaaaaaaa'

# 比較
SCrypt::Password.new(encrypted) == Authlogic::CryptoProviders::SCrypt.encrypt(password, salt)

セッション

cookieなどの値を得るために、Controllerインスタンスを持つアダプターが必要になる。

Controller経由では、初期化時に自動的に付与されるAuthlogic::ControllerAdapters::RailsAdapterが設定する。

Authlogic::Session::Base.controller = RailsAdapter.new(self)

それ以外で使いたい場合は、必要な値を返すなにかを用意する。

class ControllerLike
  def initialize(request)
    @request = request
  end

  def cookies
    @request.cookies
  end

  def params
    @request.params
  end

  def session
    @request.session
  end

  def responds_to_last_request_update_allowed?
    true
  end

  def last_request_update_allowed?
    false
  end

  def method_missing(*args)
    false
  end
end

Authlogic::Session::Base.controller = ControllerLike.new(request)

開始

create時にユーザーレコードにpersistence_tokenを保存する。

クッキーにはそのpersistence_tokenとユーザーのidを、後にsplit可能な形で保存する。[persistence_token, id].join('::')など。

復元

保存しておいたクッキーの値のidからユーザーを検索、persistence_tokenと一致するか見る。

AWS Lambdaで使う関数をローカルでテストするサーバーを建てる。

AWS Lambda + AWS Api Gateway + AWS DynamoDBでなにかをつくることにはまっています。

AWS Lambdaで使う関数自体はmochaなどでテストできますが、実際にブラウザから叩くテストをローカルでしたいと思いました。

そこでNode.jsで簡単なサーバーを建てます。

まず、AWS Lambdaはこのような関数です

"use strict";

const Workbook = require('./src/workbook').Workbook;
const Marker = require('./src/marker').Marker;

exports.handler = (data, context, callback) => {
  let workbook = new Workbook(data);
  let marker = new Marker(workbook, data);

  marker.mark((result) => {
    if (result) {
      context.succeed({result: 'correct', data: marker.explain()});
    } else {
      context.fail(JSON.stringify({result: 'incorrect', data: marker.explain()}));
    }
  });
};

WorkbookMarkerがDynamoDBにアクセスします。環境変数を使って、内部で参照ファイルを切りかえ、ローカルのDynamoDBにアクセスするようにしているので、そちらは大丈夫です。

テストサーバーです

サーバー側の言語をまともに触ったのはNode.jsがはじめてで、はじめて書いたのもこのような簡易なサーバーでした。非常に懐かしいですね。

import * as http from 'http';
import * as qs from 'querystring';
import * as marker from '../marker/index';

http.createServer(function (req, res) {
  if (req.method !== 'POST' && req.method !== 'OPTIONS') {
    res.end('');
    return;
  }

  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'POST');
  res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
  res.setHeader('Content-Type', 'application/json');

  if(req.method === 'OPTIONS'){
    res.end(``);
    return;
  }

  let body = '';
  req
    .on('data', (data) => {
      console.log('data', data);
      body += data
    })
    .on('end', () => {
      qs.parse(body);

      marker.handler(JSON.parse(body), {
        succeed: (data)=> {
          res.end(JSON.stringify(data));
        },
        fail: (stringifiedData)=> {
          res.end(stringifiedData);
        }
      });
    });

}).listen(process.env.PORT || 3080, '127.0.0.1');

OPTIONSによるプリフライト対応処理を追加しました。(昨日は素のPOSTだけ飛んでたのにな……)

まずPOSTOPTIONS以外はハネます。

つぎに、データの整形ですね。こういうのは自動でやってくれないんでしょうか。

marker.handlerがAWS Lambdaでの関数本体なので、そこへデータを投げます。

context.succeescontext.failで関数を終了するつくりなので、そこに関数を妖異します。その関数呼び出しをもってJSONレスポンスを作成します。

実際のfailでは{errorMessage: 引数}というレスポンスになりますが、これはAWS Api Gatewayで整形します。ので、それを踏まえたJSONとします。

$ TEST=true babel-node test-server/index.js

テストできた

これでブラウザ側からSuperagentなどを使いPOSTすると、実際のレスポンスが得られるようになりました。

クリッククリックで行うテストがはかどりますね。

RegexpなどActiveRecordでメソッドが用意されていない標準SQLの演算子を清く正しく使う。

AWS Lambdaを使って、ブラウザ側とサーバー側で同じバリデーションをするということをやった。

日記です。今日の日記コードはこれ。

github.com

qiitaにはさすがにもうAWS Lambdaでフォームなんていう記事は山盛りあったのでこっちで。

日記

Node.jsはサーバーとして動かせるので、ブラウザ側とサーバー側で同じスクリプトを用いることが可能です。(アイソモーフィック?)

しかし動かすとなるとサーバーを用意して〜、など色々と難関がありました。その難関を軽く解消してくれたのがAWS Lambdaであることはみなさんご存知のとおりですね。

バリデーションの準備

まずバリデーターライブラリを用意します。

これにはvalidatorを使いました。

github.com

これをラップしたRecordというクラスをつくり、以下のようなコンフィグ(config.jsに入っています)をあたえ、validateメソッドで確認できるようにしました。

exports.config = {
  attributes: ['name', 'email', 'age', 'gender'],
  validation: {
    name: {
      isLength: {min: 1, max: 20, message: '1-20文字で入力してください'}
    },
    email: {
      isEmail: {message: 'メールアドレスを入力してください'}
    },
    age: {
      isNumeric: {message: '数字を入力してください'}
    },
    gender: {
      isIn: ['female', 'male', 'other']
    }
  }
};

ブラウザ側バリデーション

あとはこのバリデーターを、ブラウザ側で使えるようにします。

ライブラリやコンフィグファイルが別ファイルになっているので、毎度おなじみのbrowserifyで連結します。

const Record = require('../lib/src/record').Record;
const config = require('../lib/src/config').config;
const record = new Record(config);

const Validator = window.Validator = {};

Validator.validate = (data) => {
  if (record.assign(data).validate()) {
    return {result: 'success', data: record.parameters};
  } else {
    return {result: 'failure', errors: record.errors};
  }
};

あとはこのバリデーションをサーバーに送信する前に行い、validであれば送信します。

サーバー側バリデーション

送信されてくるデータは全く安全でないのは常識ですから、サーバー側でもバリデーションする必要があります。

ブラウザ側とほぼ同じコードですが、AWS LambdaではNode.jsが動いていますから、browserifyは必要ありません。

今回はバリデーションの成否で終了していますが、そこらへんにdynamoDBやメール送信の処理を書けばいいんじゃないでしょうか。

"use strict";

const Record = require('./src/record').Record;
const config = require('./src/config').config;

exports.handler = (event, context, callback) => {
  let record = new Record(config);

  if (record.assign(event).validate()) {
    context.succeed({result: 'success', data: record.parameters});
  } else {
    context.fail(JSON.stringify({result: 'failure', errors: record.errors}));
  }
};

エラー時の戻り値がブラウザ側と違ってしまうのが気になるところですが(failの引数は{"errorMessage": 引数}JSONとして飛んでいく)、これはAWS Api Gateway側でfailureをキャッチして、同じ形にします。

.*"result":"failure".*で失敗ステータスコードに振り、本文マッピングテンプレートでエラー部を取り出したものをレスポンスとします。

$input.path('$.errorMessage')

ブラウザ側送信時処理

送信せずとも同じバリデーションが行えるわけですから、送信前にバリデーションし、invalidであればdeployErrorsというメソッドが、各項目のinputの上や下にエラーを表示します。

もし何らかの都合でinvalidなデータが送信されても、サーバー側でinvalid判定されて、同じエラーが返ってきてdeployErrorsというメソッドが、各項目のinputの上や下にエラーを表示します。

    function send($form, $thanks) {
      var data = traceValue($form);

      var validated = Validator.validate(data);
      if(validated.result == 'failure'){
        deployErrors($form, validated.errors);
        return;
      }

      disablizeButton($form, true);
      superagent
              .post(endPoint)
              .set('Accept', 'application/json')
              .send(data)
              .end(function (err, res) {
                if (err) {
                  deployErrors($form, res.body.errors);
                  disablizeButton($form, false);
                } else {
                  $form.hide();
                  $thanks.show();
                }
              });
    }

わりと長年の夢であった、サーバーとクライアントで同じコードでバリデーションするというのが、比較的簡単にできてしまいました。しかも課金の心配が結構軽い。

フォーム作成というこまごまとした仕事は、前職前前職ともによくあって、cgiの、メンテされてるんだがされてないんだかわからないスクリプトを使用していました。

Rails大好きなわたしもさすがに一個の問い合わせフォームにRailsというわけにもいかなくて難儀していましたが、これならヤバイ脆弱性も自分の責任のうちで作成できるのでいいかもしれませんね。

HerokuにSymbolic Link入りを投げるとき気をつけること

リソースを再利用しようとしました。

そこでディレクトリに対してln -sで作成しますが、例えば同ディレクトリ内の何がしかへ張る場合、

$ ln -s development staging

とする必要があり

$ ln -s ./development staging

これだと辿れなくなります。

セキュリティかなにかの都合でスラッシュアクセスがブロックされているのでは。同じ理由で上には登れないんじゃないかな(試してない)。

レールズにプルリクエストがマージされてハッピーだった

ので、思いつく限りのSNSやブログに書いています。

ここにも書きます。

どれ

以下の記事のやつを作ってる時に「バグかしら?」と思ってモンキーパッチで回避してて、せっかくだからとpull requestを飛ばしてみたら忘れた頃にマージされたという話です。

qiita.com

Rails5.0.0がつい先日リリースされましたが、その時には自分でも忘れていて、ほん今日、なんか通知が出てるなと思って見てみたらマージされていました。

無職してた甲斐があった

仕事をしないで自分で思うがままにコードを書きつづけていたところで出会ったバグ的なものでしたから、無職でなかったら出会わなかった可能性があります。

これがここのコードが原因で起こってる、っていうのも結局pメソッドとクリッククリックで追いつめて見つけたみたいなもんですから、よっぽど時間に余裕がないと無理だったでしょう。

無職時代のいい思い出になったな〜〜と言いたいところですが、まだ就職先は決まっておりません。

大変ですね。

まぁとにかく嬉しい

好きなフレームワークに貢献できたということで、本当に嬉しいですね。

2016/06

Discourseにみる権限管理。

権限管理に興味があって、ちょろちょろ読んでいます。

qiita.com

Spreeを読む前はDiscourseを読んでいました。Discourseでは権限管理にこれといったGemは用いず、独自の権限管理機能を実装しています。

Guardianというクラスです。

ただの感想みたいになったのでこっちのブログにメモとして残します。

Guardian

用法はCancancanと大きくかわるところはありません。

まずcurrent_userに類するものでGuardianのインスタンスを作成します。

app/controllers/application_controller.rb

def guardian
  @guardian ||= Guardian.new(current_user)
end

そしてそのインスタンスに処理内容を冠したメソッドに処理対象のなんらかのオブジェクを渡すと権限の確認が行われます。

app/controllers/users_controller.rb

guardian.ensure_can_edit_username!(user)

許可された動作ならそのまま処理はすすみ、許可されていない動作であればDiscourse::InvalidAccess例外を発生します。

設定

Spreeにおいて、Cancancanはユーザー単位で権限を設定していました。

Guardianは処理対象のオブジェクトのクラスごとにモジュールを用意します。

lib/guardian/user_guardian.rb

module UserGuardian
  # 略
  def can_edit_username?(user)
    return false if (SiteSetting.sso_overrides_username? && SiteSetting.enable_sso?)
    return true if is_staff?
    return false if SiteSetting.username_change_period <= 0
    is_me?(user) && (user.post_count == 0 || user.created_at > SiteSetting.username_change_period.days.ago)
  end
  # 略
end

そしてGuardianincludeするというシンプルな仕組みです。

lib/guardian.rb

class Guardian
  include EnsureMagic
  include CategoryGuardian
  include PostGuardian
  include TopicGuardian
  include UserGuardian
  include PostRevisionGuardian
  include GroupGuardian
  #略
end

実行

前述の例のように接頭辞としてensure_をつけた場合、設定されたメソッドがありません。

app/controllers/users_controller.rb

guardian.ensure_can_edit_username!(user)

そこでEnsureMagicというmoduleに実装されたmethod_missingがあらためてメソッドを探索し、その結果をうけてtrueを返すか例外を発生します。

感想

最初は単一のクラスで、その後どんどん増改築した結果、基本となるGuardianにもわりとコードがありつつ、includeするmoduleにもコードがもりもりあるという感じになっています。

それを除けば、method_missingから定義済みメソッドを探索して実行、権限を確認するという動作は、メソッドにより権限をドバっと設定するCancancanより好ましさを感じます。

自分でも

引数でわたされるオブジェクトのクラスを見て該当のGuardianを検索してインスタンス化、そこからメソッドを探索して権限確認という実装を自分でも書いてみましたが、なかなかいい塩梅でした。

github.com

でも多分仕事でつかうならCancancanを使いますね。

rails-erdでRails Engineベースのアプリケーション(Spree、Solidusなど)のER図を得る。

たっぷりとしたコードを含むRails Engineは上から読んでいくにはつらいものです。

いくつかのgemに分割されているSpreeのようなアプリケーションを読むにはインストールして起動しつつ、その上で読んでいくのが楽ですが、手がかりがないのはつらい。ということてrails-erdを使います。しかし、

Engine以下のmodelsは読んでくれない

という問題があるのでそれに対処します。

Engine以下のmodels以下のファイルをapp/models以下にコピー

これでrails-erdのスコープに入ります。

Solidusを読みたいので、Solidusをインストールしたうえでコピーします。

$ cp -R ./vendor/bundle/ruby/2.3.0/gems/solidus*/app/models/* app/models/

rails-erdインストール

graphvizぐらいはすでに入っているかもしれませんがインストール。

$ sudo apt-get install graphviz
group :development do
  gem "rails-erd"
end
$ bundle install

書きだし

$ bundle exec erd

でかい

でかい

f:id:mmmpa:20160615061941j:plain

ちなみにネームスペース、クラス名は特に汚染されないのでコピーしたままでもアプリケーションは動きます。

2016/05

通販で自転車を買った思い出

いままで直近3台全部が通販で購入した自転車だった。

次は店頭でロードを買いたい。仕事がみつかったらね……。

Be.BIKE 16STREET

f:id:mmmpa:20160525045505j:plain

38,000円

楽天で見つけたミニベロで、アルミ製、サスあり。黒くてカッコ良かった。

届いてわーいと乗り出したところ、ブレーキがほとんど効かず死にかけた。あとハンドルの方向が歪んでいた。ブレーキはワイヤーを調整したらちゃんと効いた。

この件ではなくて、その後の問い合わせへの回答が雑だったので、あの店では二度と買わないと思う。

これにのってはじめて100km(大阪と東寺往復)走ったり、カプレオに換装するなど自転車いじり初経験車となったりで、いい思い出が多い。

最初の整備不良以外は異常なし。

KHS FLITE100

f:id:mmmpa:20160525045515j:plain

59,400円

色はこれとちがって、あんまりよくないやつ。クロモリのシングルスピードで値段の割に重量が8kgとかで軽くて良かった。

あと写真とはちがってちゃんとブレーキがついてる。

走るぞ〜と思って買ったものの、チューブ交換後のセンター出しがだるすぎて遠出する気になれなくなってしまった(遠出==パンクという連想がある)。

上のミニベロのチェーンリングを移植してちょっとギア比を上げて、後ろはフリーにして今のメインチャリ。

特に異常は出ず。

17バイシクル エクスウォーカー

f:id:mmmpa:20160525045527g:plain

38,000円

極小径車で、通勤先が変わって置く場所が確保しづらくなったので購入。

これがパンクすると交換が絶対大変だわということで、ノーパンクタイヤ仕様で購入した。

購入3日後ぐらいに、なにかガタつくなとながめてみたら、後輪のナットが取れていた。

さすがに最初からなかったとは考えられないので、タイヤ交換後の締めが甘かった + ノーパンクタイヤの振動で外れたのだろうか。

締め直してからは取れてない。

届いたらまず整備しよう

ということがわかりますね。

Windowsをメインで使っている時にRails開発環境を作った話

(具体的な方法や手順についての話はありません)

現在わたしはデスクトップ機でDebianを使用していますが、もともとWindowsユーザーでした。

なにしろ前職に転職したきっかけがFlashでしたし、前々職はホームページ屋さん勤務でしたから、開発はWindowsでやるというのは別に普通でした。

さて、転職後Flash案件も無事鎮火した後、前職はRuby推しのところでしたので、Railsを使うので今からやってくださいという話になりました。じゃあやりましょうということで開発環境整備が必要です。

Windowsをちょっとあきらめる

学習しはじめに、まずWindows版のRubyとRailsを導入しました、しようとしました。しかしすんなりと動きませんし、ぐぐりんぐしたところ、うーん、目が滑る。

そこで諦めのいいわたしはWindowsをスパッと半分だけあきらめて、VMWare上にLinux環境を整備しました。

わたしが使っているIntelliJには、SSH接続によりリモートの上のSDKを使う機能があったので、WindowsのIntelliJからVMWareのRubyやRailsを参照することにより、コード補完などをまったく問題なく使用することができました。便利ですね。

SambaやVMWareのファイル共有で直接(っぽく)ソースコードを編集することで、同期のようなめんどくさい問題は起こりませんでした。

Windowsをあきらめる量を増やす

しばらくして、ちょっとしたアップデート時の、SSH経由のGemファイルダウンロードの重さに辟易したわたしは、VMWare上にデスクトップ環境を構築しました。

IntelliJはJavaの上で動くので、Linuxでも問題なく動きます。素晴らしいですね。

ちがった重さはあったものの、Gemファイルのダウンロード待ちのような理不尽な待ち時間はなく、徐々にLinuxにも慣れていきました。

Windowsをあきらめた

そしてとうとう、Windowsをアンインストールし、OSをLinuxに変えました。

ただ、Windowsの各種アプリケーションを完全にあきらめることは出来なかったので、LinuxのVMWare上にWindows環境を構築しました。主従逆転です。

結局なんなの

前職で社員に支給されていたのはWindowsでした。そしてひとつの開発サーバーをdevelopment_someone環境を用意して、みなで使っていました。まったく恐ろしい。(わたしは支給された低スペックノートPCを使うぐらいならとPCから液晶から全部持ち込んで、自分のPC上で開発していました)

そのような恐怖体験をしたり、Windows上に無理に開発環境をつくるよりも、いっそあきらめるという選択をしたほうが精神衛生上いいのではないか、という話です。(パソコン持ち込みやアプリインストールが無理なら、辞めよう)

しかし、わたしは宗教上の理由でやりませんでしたが、OSX機を買えば話がはやそうですね。

ActionCableで遊んでたらなんかリロード毎につながったりつながらなくなったりすんですけど?って時。

あたらしいもの好きなのでActionCableをいじっています。WebSocketは別に新しくない?おかたいことを言わないで。

ところでこの解決策は推測でしかないんですが、とりあえず解決したので暫定メモ的なアレですが、困ってる人はどうですか。

Redisががんばったまま

おそらく開発中に、ブラウザから接続したままでサーバーを再起動したりしてると、適切にConnectionが切断されないままになって、Redisはがんばったままになっています。

こうなると、2本のチャンネルの1本だけつながったりつながらなくなったりして、本当に原因がわからなくなります。

Redisも再起動しよう。

おわり。

ぐぐりんぐしてなんとなく使う、という癖を治すためにとりあえずRails 5.0.0.rc1のドキュメントを入手する。

最初はドキュメントを頭からケツまで読んだほうが良いと思うんですよ。その時使わないポイントも頭に入れておくと、それが使えるタイミングが来たら、あれ、見たことあるぞ?となると思うんです。

歳を取ってくると結構実感するレベルで記憶力が弱ってきて、全部とは言わないまでも、新しいクラスなどを頭からケツまで気合入れて読むタイミングを設けないと駄目なんじゃないかという気がしてるのもあります。

Rails 5.0.0.rc1のソースを入手

$ git clone https://github.com/rails/rails/
$ cd rails/
$ git checkout -b rails5 origin/5-0-stable

rdocを書きだし

$ bundle install
$ bundle exec rake rdoc

これでdoc/rdoc/以下にHTMLのドキュメントが書きだされます。

読みましょう。

2016年なのでgulpfile.coffeeをgulpfile.jsに書きなおした。

1ファイルとかだとdecaf onlineでやり直してチョロチョロ書きなおすだけでよくて楽でした。

ところで最初に用意したのは1年前ぐらいだったのですが、バージョンが進んでいたりdeprecatedが発生していたりで、ライブラリの刷新が必要になりました。

Node.js 6ではナマでES2015が使える

というか、Node.jsぐらいならナマでいけるだろと適当に書きなおしていたら動かなくて、5を使っていたのに気づきました。

ので、「6では」などとあらためて書きました。

アロー記法は本当に良いですね。

ライブラリのバージョンをお手軽に更新する

github.com

これが便利で、以下でpackage.json内のライブラリのバージョンを最新に更新できます。

$ npm install -g npm-check-updates
$ npm-check-updates -u

インストール自体は行われないので適宜やっていく感じになります。

更新されたファイルだけをコンパイルするにはgulp-watch

github.com

とにかく導入時は投入が再優先だったのであまり調べて使ってませんでした。

gulp.src('src/sass/**/*.sass')

gulp.watch以下でこの指定方法を使うと、更新されたファイル以外もコンパイルしてしまうので、watchのonChangeイベントから変更されたファイルを取得、そのパスをみて書きだし先にディレクトリ構造を再現するというシチ面倒くさいことを自分でやっていました。

gulp-watchを使えばそこらへんをよしなにしてくれます。

gulp.task('sass', () => {
  gulp
    .src(sassWatch)
    .pipe(watch(sassWatch))
    .pipe(plumber())
    .pipe(sass())
    .pipe(gulp.dest(cssOut));
});

watch(sassWatch)がgulp-watch部で、これでwatchしてくれますのでgulp.watch部はいらなくなりました。

GulpでもいちおうWatchify

github.com

Watchifyはnpm runで使っていますが、いちおうGulpでの使いかたもさらいました。

ほぼコピペですが、ES2015対応にtransform: ['babelify']とその設定ファイル.babelrcを追加しました。

let watching = false;
gulp.task('enable-watch-mode', () => watching = true);
gulp.task('js', watchify((watchify) => {
  gulp
    .src(jsWatch)
    .pipe(watch(jsWatch))
    .pipe(watchify({
      watch: watching,
      transform: ['babelify']
    }))
    .pipe(gulp.dest(jsOut));
}));

おわり

Railsを使う時でもこの手のコンパイルはGulpでやっています。

Gemに依存するのがこわいというか、アセットパイプラインが好きじゃないとかそんな理由なんですが、一応RailsのGemでのやっていきかたも調べておいた方がいいかもしれない。

github.com

ブラウザの拡大率を取得できたことにより生まれそうな邪悪さ

ブラウザの拡大率、ズームレベルを取得した。 - Qiitaという半分冗談みたいなエントリーを投稿しました。

zoomer.mmmpa.netで試せますが、Firefox+Flashが動く環境でしか動作せず、被弾環境が少なすぎるので「冗談」です。

ところでなつかしの文字サイズ固定

はるか昔、InternetExplorerでは大中小などといった文字サイズをユーザー側で設定し、それをもって画面の表示サイズを調整していました。

そして、その文字拡大機能は、CSSでfont-sizeをpxで指定することにより無効にできるという時代があったのです。

ユーザーの設定を無視できる邪悪なCSSとして嫌われていました。

今では画面全体を拡大したり、pxで指定しても文字だけを拡大できたりよい時代になりました。

そこで拡大率を見られるとしたら

拡大率に関してぐぐりんぐしていた時に実際見たアイデアですが、拡大率にあわせて文字サイズや画像サイズを調整したいというのがありました。

つまりユーザーの拡大率を無視して、ワシが見せたいようにだけ見えるようにしたるわいという、レトロIE時代の邪悪な文字サイズ固定が現代に蘇るみたいな悪夢がそこにはありました。

実際にはほぼ取得できないので夢で終わってよかったですねという話なんですが、ユーザー情報を取得出来すぎるのは邪悪さを生みうるなぁと思いました。

自分じゃないReact.Componentから上がってくるドラッグイベントを処理する。

のはつらい。

同一Component内で処理するのは楽

Marker Workbookではそのようにやりました。簡便に書くと以下。

  startDrag(e){
    e.preventDefault();

    let div = e.currentTarget;
    let store = [];
    let startPosition = {x: e.pageX, y: e.pageY};

    store.push({x: e.pageX, y: e.pageY, movedX: 0, movedY: 0});

    let move = (de) => {
      de.preventDefault();

      let now = {x: de.pageX, y: de.pageY, movedX: de.pageX - startPosition.x, movedY: de.pageY - startPosition.y};
      store.push(now);
    };

    let clear = (ue) =>{
      ue.preventDefault();

      this.setState({mousePositions: store});
      div.removeEventListener('mousemove', move);
      window.removeEventListener('mouseup', clear);
    };

    div.addEventListener('mousemove', move);
    window.addEventListener('mouseup', clear);
  }

  render(){
    return <div className="drag-area" onMouseDown={this.startDrag.bind(this)}>drag area</div>
  }

ドラッグ開始の合図であるonMouseDownと同時に各種イベントリスナーを登録しつつドラッグ終了時のイベントリスナー掃除系のリスナーも登録して一発で全部終わるみたいな感じです。

大体このように終わらせるのが楽でよかった。

下位Componentに何も判断させたくない場合

ショートカットによってドラッグに意味があったりなかったり、発行したりしなかったりというのを下位Componentに書きたくない場合は、開始と終了を上位Componentで判断しなくてはならないので、自ずとそのような副作用が必要になります。

  onPress(e) {
    e.preventDefault();

    this.store = [];
    window.addEventListener('mouseup', this.onRelease);

    let now = {x: e.pageX, y: e.pageY};
    this.store.push({x: e.pageX, y: e.pageY, movedX: 0, movedY: 0});
    this.startPosition = now;
  }

  onRelease(e) {
    e.preventDefault();

    this.setState({mousePositions: this.store});

    this.store = null;
    this.startPosition = null;
    window.removeEventListener('mouseup', this.onRelease);
  }

  onDrag(e) {
    if (!this.store) {
      return;
    }
    e.preventDefault();
    let now = {x: e.pageX, y: e.pageY, movedX: e.pageX - this.startPosition.x, movedY: e.pageY - this.startPosition.y};
    this.store.push(now);
  }

  render() {
    return <Pusher {...{onPress, onDrag}}/>
  }

onPressで初期化して、onDragで下位からデータがどんどんくるのでそれを登録、onReleaseで破棄という流れです。

JavaScriptで確実にmouseupをつかんでblur()する。 - ンンンパでも書きましたがmouseupはつかめたりつかめなかったりするので、これはwindowに登録して確実に取れるようにします。

下位Componentはこのように。

export default class Pusher extends React.Component {
  render() {
    return <div className="drag-area" onMouseDown={this.props.onPress} onMouseMove={this.props.onDrag}>pusher drag area</div>
  }
}

しかし

書いてる途中で、どのような場合でもドラッグ中はドラッグ中の情報を、ドラッグ終了時はドラッグ中の情報を全部集積したのを上位に投げて、それを使うかどうかは上位がステートによって判断すればそれでいいだけやんけと思い当たりました。

ドラッグ中に随時その情報が必要(で連続性を判断しなければならないような)場合は、下位Component側からIDを持ったdispatchをさせるのが良いのかな。

とりあえずそうします。

一応今回のおソース。

github.com

WatchifyとBabelifyでES2015とReactをする。(no Gulp)

BabelがわりにTypeScriptを使うという雑なことを続けていましたが、Babelでも作業できるようにします。

├ public/
│  ├ js/
│  │  └ build.js # 書きだされたjs
│  └ index.html
│
└ src/
   ├ js/
   │  └ index.js # ES2015のjs
   ├ out         # ../public/jsへのシンボリックリンク
   ├ .babelrc
   └ package.json

Babelの準備

ES2015を使うにはbabel-preset-es2015が、ReactのJSXを使うにはbabel-preset-reactが必要なので、WatchifyとBabelifyに加えて、それぞれインストールします。

npm i -D watchify babelify babel-preset-es2015 babel-preset-react

Babelが走る設定は.babelrcを用意してそれに記述します。

{
  "presets": ["react", "es2015"]
}

package.jsonの準備

npm run buildでwatchifyが走るように、package.jsonのscriptsの項目にコマンドを追加します。

"scripts": {
  "build": "watchify -t babelify ./js/index.js -o ./out/built.js -v"
}

ES2015とJSXで書かれた./js/index.jsをwatchifyコマンドでbabelify経由コンパイルし、解釈できるようにした./out/built.jsをアウトプットするというコマンドです。

-vはワッチ処理結果を表示してくれるので、ちゃんと動いてるかわかりますから入れておいたほうが安心です。

./outは../public/jsへのシンボリックリンクとなっていますので、最終的には../public/js/build.jsという位置に書きだされます。

これは単なる趣味で、ダイレクトにディレクトリを指定して良いと思います。

no Gulp

特に反Gulpであるとかそういうことではなくて、Sassのコンパイルや最終的な圧縮などではGulpでポンです。

しかしちょっと試したい時にながながとGulpを用意するのはめんどくさいのでno Gulpが良いです。

TypeScriptでmixinした時に型情報が見れないのをある程度なんとかする。

わたしが使っているのはこういうやつで、"Real" Mixins with JavaScript Classes由来のやつです。

gist.github.com

このようにインします。

export default class Mixed extends (mix(BaseClass).mix(
  Mix1,
  Mix2,
  Mix3
) as typeof BaseClass) {
  // class
}

これだとget/setがいける、順番を気にしなくていい、extendsに書ける、など結構いい感じで好きです。(prototype方式だとget/setで死んだような)

しかしクラスを動的に作る方式なので、クラスをインターフェースとしてimplementsに入れることができず、IDEの支援が半減してつらい。

そこで、入れられる側の引数であるsuperclassはtypeof Hogeですよと教えると、とりあえず入れられる側で設定しているプロパティにはアクセスできます。

export let Mix1 = (superclass: typeof Mixed) => class extends superclass {
}

これだと入れられる側の平行アクセスが出来ないとか、外側から入れられたメソッドなどの情報が見れないので警告が出るとか色々ありますけど、まぁ小マシになります。

ES6のDestructuring assignment(分割代入)でさらに便利記法

分割代入 - JavaScript | MDN

分割代入というのは

let {a, b} = {a: 'a', b: 'b'};
console.log(a, b); // a b

というオブジェクトのキーをもとに変数に展開できる、これだけでも十分便利なものですが、さらに便利にする記法もあります。

変数に別名をつけられます。

let {a: aa, b: bb} = {a: 'a', b: 'b'};
console.log(aa, bb); // a b

2点の比較をする時に{x, y}を返す関数を使っている時に片方だけ展開できなくてどうしようかと思っていましたが、これで安心です。

デフォルト値を設定できます

let {a='1', b='2'} = {a: 'a'};
console.log(a, b); // a 2

let [c='3', d='4'] = ['c'];
console.log(c, d); // c 4

:ではなく=であることに注意しましょう。

なおデフォルト値は取りあつかいに注意が必要な雰囲気がありました。

Destructuring assignmentのご利用は計画的に - Qiita

TypeScriptのコード規約をIntelliJのデフォルト設定任せにしていたので、TSLintでちゃんとやる。

*lintは自分で、あるいは社で作成された一定の規約に従ったコードを書かないと怒られシステムです。

人の目とか感覚ではなく、機械が機械的に怒ってくれるので感情が沸かないのが良いですね。

TypeScriptにはTSLintがある

現在JavaScriptを書くにあたってはバベルがわりにTypeScriptを使っているので、TSLintを設定します。

TypeScriptとTSLintをインストールして、

npm i -g typescript tslint

設定ファイルであるtslint.jsonを生成します。

tslint --init

これで基本的なルールが記述されたtslint.jsonが作成されるので、Rulesなどを参照しながらお好みのルールに仕上げます。

これで一貫性をもったコードを書けるようになり、治安維持につとめられます。

設定の必要がなくなったルール

This rule is now implemented in the TypeScript compiler and does not need to be used.

なかにはこういうルールもあるので、他人のtslint.jsonからコピペ追加するときにはいちおうドキュメントを見てみましょう。

IntelliJでの設定

Settings > Languages & Frameworks > TypeScript > TSLintに設定ページがあります。

Enableにチェックをいれて、NodeへのパスとTSLintパッケージへのパスを通します。

初期値ではtslint.jsonを自動検索してくれますが、社から配給されるファイル名がちがうであるとか、宗教上の理由でファイル名がちがう場合は、特定のファイルを指定することもできます。

f:id:mmmpa:20160509105126p:plain

特に付け加えたルール

no-bitwise、no-conditional-assignmentは悩ましかったけど入れなかった。

no-bitwiseは今触ってるやつ以外では入れましょうという感じですね。

curly

Enforces braces for if/for/do/while statements.

no-angle-bracket-type-assertion

型アサーションをasで書くようにするルール。

<ClassA>a;   // ng
a as ClassA; // ok

わたしの場合Reactを常用しているので、JSX(TypeScriptだと拡張子tsxになる)を使うことが前提になっています。

JSXのシンタックスと当たってしまって<>での型アサーションは使えないので、両方が混ざるのを防ぐ意味があります。

Reactで右クリックを扱う

ReactにはonContextMenuが用意されているので楽です。

  render() {
    let onClick = (e, isRight?)=> {
      e.preventDefault();
      if(isRight){
        // 右クリック
      }else{
        // 左クリック
      }
    };

    return <div
      onClick={(e)=> onClick(e)}
      onContextMenu={(e)=> onClick(e, true)}>
      何か
    </div>
  }

ただしonMouseDownでは、左クリックも右クリックでもonMouseDownがよばれますので、こうします。

  render() {
    let onPress = (e)=> {
      let isRight = e.nativeEvent.which === 3;
      if(isRight){
        // 右クリック
      }else{
        // 左クリック
      }
    };

    return <div onMouseDown={(e)=> onPress(e)}>
      何か
    </div>
  }

まちがってonContextMenuとonMouseDownを組みあわせると、右クリックだけ複数回呼ばれることになります。

アンドゥリドゥの案メモ

実装

gist.github.com

テストも書いてまぁ動いたのでこれで。

gist.github.com

最初のメモ

gist.github.com

こんな感じかな。未実装。

運動後にやってみる。

JavaScriptで確実にmouseupをつかんでblur()する。

Marker Workbook

PDF.jsとReactでブラウザ上で完結する暗記帳的なものをつくった。 - Qiita

ではキーボード・ショートカットでスペースを使うのですが、ボタン押下後にスペースを押すと、そのボタンにフォーカスが残っていて再度押下されてつらいということになりました。

というわけで押下後はフォーカスを外していきたいのですが、マウスアップにe.currentTarget.blur()とした場合、マウスダウンしたままでボタンの外にカーソルを移動しマウスアップした場合、マウスアップイベントは発行されません。ので、blur()が不発に終わります。ぶるぁ。

というわけで、古来からの手法で、blur()されるボタンをReactでつくります。

import * as React from "react";
import * as ReactDOM from 'react-dom';

export default class BlurButton extends React.Component {
  render(){
    let onMouseDown = (e)=>{
      this.props.onMouseDown && this.props.onMouseDown(e);

      let button = e.currentTarget;
      let up = (e)=>{
        button.blur();
        window.removeEventListener('mouseup', up);
      };
      window.addEventListener('mouseup', up);
    };

    return <button {...this.props} {...{onMouseDown}}>{this.props.children}</button>
  }
}

マウスダウン時にwindowにイベントリスナーを付与して、どこでマウスアップしようがとにかくblur()します。

buttonタグを単純にリプレイスできるように、propsは単純にバケツリレーして、onMouseDownだけはラップしてblur()を入れます。

これでとりあえずスペースやエンターでボタンが再度押下される不幸がなくなりました。

2016/04

Reduxは欧米人用。

ないから生まれるんだ。

中国人は道徳心が無いから儒教が生まれた。

日本人は勇気がないから武士道が生まれた。

アングロサクソンはずるいからフェアプレーの精神が生まれた。

あのめんどくささの極致みたいなファイルの多さは欧米人の我の強さ、自分主義、協調性の無さを殺すため生まれたんだよ! ΩΩ Ω

Reduxめんどくさい……個人プロダクトで絶対使いたくない……。

auから0simにかえて困ったこと

simロックフリー機器を所有していないことを失念していたので連絡手段がなくなった。

<おわり>

リボ払い計算機に暗黒時代の消費者金融の金利を追加した。

リボルビング払いのコワさを知るために簡単な計算機つくった - ンンンパ

出始めの頃で、ほんときっつい金利だった時代のです。

それでもアコムとかは最大の金利は課してなかったらしくてへぇ〜みたいな感じだったんですけど、大してかわらんぞい。

昔のみんなたちが払っていた額

というわけで50万円借りて最低返済額15,000円で返し続けると、じゃじゃーん、こうなります。

返済回数 総お支払い額 支払った利子
66回 988,627円 488,627円

ぅぉぅ……おもったよりすごい……。

あの計算機は基準額の減算を単純化して、返済額の50%を減らして金利計算してるので、本来のよりはちょっと安くなるはずなんですが、これはすごい。

ちなみに今の上限金利だと48回返済で217,691円が利子となるようで、感覚的にまだ普通の借金かなぁという気がしますね。

それにつけても無職のわたし

しかし無職の身で借金について調べていると本当に気が滅入ってきますね。

早く就活しなければ……。

ダークソウル3をクリアしたおもいで

攻略サイトなし、友達なし、ソロプレイという特殊性癖が前提ですが、とにかくボス戦がおもしろくなくてつらかった。

  1. ファンネルっぽい遅延攻撃がうざい
  2. ボス戦のこっちのモーションに合わせた攻撃がうざい

ということで、なんというかうざいタイプの難易度の上げ方だなぁという感じでした。

一撃で死なされるのはデモンズソウルとブラッドボーンで慣れてるので別に良いんですが、とにかくテンションじゃなくてストレスで血圧上がる。

明日からは攻略サイト見ながら2周目やります。

リボルビング払いのコワさを知るために簡単な計算機つくった

銀行が提供しているシミュレーターはやたらと入力項目が多かったり小さかったりでアレだったので、ただサッと手数料額を見てウヒョ〜コエ〜したいだけのわたしみたいな人には不向きだったので、とにかくサッと知れるやつが欲しくてササッと。

個人的には全回数クリックしてめんどくささを知るのがおすすめです。

なんか消費者金融モード入れた時点で大騒ぎになりはじめたけど一応。

github.com

アイデアとわ〜

みたいたことをジョンダニエルさんを飲みながら考えていたんだけど、あっ、これ作ろうってわたしが思うのって結局、実務とか自分がやるときのつらさが起因なんだよな。

だから、なんか天才が突然思いついた!みたいなのはまったく縁がなくて、とにかく目の前の問題のアレがあれ。

というわけで

仕事をしていればそういうアレにしょっちゅう出会うのでそれを打ち倒すための開発を。

そうでなければ(俺みたいな無職)は、とにかく色々やってみて自分で不満を見つけるしかない。

まぁ普通に生活しててもはてブの検索は使いたいけどコメントは1mmも観たくないみたいのがあるのでそれぞれの問題を解説することがアイデアになります。

よかったですね。

2016/03

あっ

CSIを英語で見ると「フラッシュドライブ」と言ってるのを字幕でも吹き替えでも「ユーエスビー」と言っているのが許せなかったんです。

USBメモリならまだわかるんですよ。「ユーエスビー」という呼称は、浸透したとはいえ1人でイラッとしてたんです。

しかしシーズン12では吹き替えで「フラッシュメモリ」になっていました。字幕は未だ「USB」。

でも良いんです、吹き替え派だから。

アイワント字幕

ところで、耳があまり良くないので字幕は常時表示したい。

日本のドラマや映画のDVDも、全部字幕対応して欲しいです。

歯医者大好き

食事が大好きなので、食事を妨害する虫歯による痛みは世界最大の敵です。

それを倒してくれる、好きにならないはずがない。

好きすぎて間接マーケティングします。

イメージ

治療が痛い、想像を絶するほど。

そういうイメージが刷り込まれてる。特にギャグ風味のフィクションでは、だいたいそのように扱われてきた。わたしの大好きなろくでなしブルースでもそうだった。

実際

とにかく麻酔を打ってくれる。まず麻酔。痛いとか聞かないうちから麻酔。

麻酔を使い慣れてるから、麻酔を入れる針を入れる前の下準備もちゃんとするので、あんまり痛くない。チクリと痛いのは痛い。

それをすぎれば、あとは麻酔が効いたまま晩御飯を食べる時に、唇とか口のなかを噛まないように気をつけるだけです。(次の日がつらいので)

歯医者、行こう。

麻酔に定評がある歯医者がおすすめです。

質問のしやすいグループウェアとはどんなのなのか〜?

とか考えながらモックをつくってました。動いてる。

反応をボタンひとつで返せるようにしたかった

ただ知らないだけでも、その反応がわかるようにしたかった、という感じです。

メーリングリストで質問を投げた時の問題点は、知らないから黙っているのか知っているけどめんどくさいから返信していないのか、そもそも読んでいないのかがわからないんですよね。

チャットでもそうで、たとえログインマークがついていても、読んでいるかわからないし、調べてるから質問が帰ってこないのか、知らないからスルーしているのかわからない。

足あとではだめで、足あとだと読んだということはわかりますけど、忙しいから後回しにしているのかもというのがあります。

というわけで、反応は意志的にしてほしいけど、簡単にできるようにしたかったのです。

反応を待ってるのとか、反応がない理由がわからないのってイライラしますしね。

反応が簡単なら質問もしやすい

f:id:mmmpa:20160321152135p:plain

特定の人間に質問を投げられるというのが大切なのですが、ちょっと気にしすぎる人間なんかだと、相手が忙しそうとか上司とかだと腰が引けてききにくいなんてことがあるかもしれません。

でもボタンひとつで反応が返せるとわかっていたなら、そのちょっとした障壁がなくなって質問しやすいのではないかな〜と思いました。

f:id:mmmpa:20160321152309p:plain

その他

今回SPAじゃなくて各画面に細かくわけたReactを配置して結構フットワーク軽めに実装できて、それをごちゃごちゃとQiitaに下書きしたんですけど全然まとまらなかったので、まぁこういう思いでつくったりしてますみたいな思い出話になりました。

一応使えそうなとこだけ切り出して書いた。

React全部バケツリレー自動化 - Qiita

25日で失業保険が終わるんです

働くのは好きだしそれはいいんですけど、そのまえに働く場所があるのかすげぇ不安で就活準備で心が精神がざわついています。

とりあえず職務経歴書を書くぞ。

ボーンズをシーズン1から観てるんだけど

ほんとに脚本チーム変わったんじゃないかっていうぐらい豹変するドラマってあるよな。

ボーンズはDisc4から本当におもしろくなった。感情に訴えかけてくる。

クリミナルマインドも最初おもしろくて、2-4辺りはめちゃくちゃつまらなくて、シーズン5ぐらいでまたいきなり面白くなったんだよな。

詳しい人が調べてほしい。

今日のamazon日替わりで「漫才入門」買いました。

元祖爆笑王という名前と99円という価格で、またどこかの馬の骨が書いたamazon廉価本か……と思いきや、講師などを務めるちゃんとした放送作家の人らしくて即買しました。

おすすめです

漫才入門

漫才入門

いきおいでおすすめですとか書いてしまいましたがまだ読んでません。

明日から読みます。

長寿作品を最初から見るとめちゃくちゃおもしろくない時がある

具体的に言うと、ろくでなしブルース(少年ジャンプ)とかボーンズ(アメリカの検死捜査ドラマ)です。

ろくでなしブルース1-4巻は(作者が後述しているとおり)普通の漫画で、むしろ退屈なんです。

でも5巻の小平次や島袋が出てくるあたりでめちゃくちゃ面白くなります。キャラの振る舞い方がピタッとはまるというか、とにかくおもしろい。

ボーンズも最初は天才を適当に揃えてきゃいきゃいやってる感じで、まぁわりとおもしろくない。

ところが第4話ぐらいからいきなりおもしろくなる。

その時感じたのは、キャラクターが突然「人間」として喋り始めたんじゃないか、みたいな感想だったんです。

まぁそんな明確な理由を説明する能力がないので

とにかく、最初はなぜかおもしろくない長寿番組を最初から見ると、そういう転機みたいなのを感じられておもしろいです。

「頭の体操」の作者がなくなったらしい。

小学生の時に読んだ本と言えば、

  • 頭の体操シリーズ
  • 図書館にあった落語の本
  • シャーロックホームズシリーズ

が大部分を占めていて、たぶん、いまの考え方とか基準にちいさくない影響を与えている。

頭の体操シリーズは、問題をあてて楽しむというより、その発想に痺れて何度も読んだ記憶がある。

ご冥福をお祈りします。

天然のブリと養殖のブリでは圧倒的に天然のブリが好きなんだが

行動範囲の問題なのか、養殖のブリしか売っていない。

養殖のブリは異常な量の脂で、食べた後には胸やけ必至なんだが、とにかく養殖しか売っていない……。

魚の脂は体にいいと言われているが、胸やけとかそういう喫緊の問題で詰めてこられると、あ、天然で良いですとなる。

天然を食わせてくれ〜。

一年越しでpaizaコーディングスキルチェックの「うなぎを蒲焼きにしたくない」を倒せたので嬉しくなって日記に書く。

トイレ最高!

去年、6回ぐらい挑戦して駄目だったけど、今日ふと思いついて何度か失格して、トイレで座ってる時に突然わかって書いたらうまくいきました。

トイレ最高!

日記です

結局今回も何度か失格はしてるんですけど、個人的に引っかかってたのをなんとかクリアできてうれしくなったというだけの話で、素晴らしいコードを編みだしたとかそういうのではないので、日記です。

成長したのかどうなのか

去年とくらべてコードが綺麗になったような気がします。成長しましたか。

やってることはほとんどおなじなんですけど、自分で後で読んでもわかりそうな気がする。

とのこと(消しました)。

今月のAmazon月替わりセール、結構いい。

テスト自動化はまぁ素養としても、プログラムやってるとついついないがしろにしがちな「見ため」に関する話をわかりやすく解説してる「なるほどデザイン」がめちゃ安い。

これは買うしかないってことで即座に買った。

読みあげは使えないけどまぁしゃーないかな……。

システムテスト自動化 標準ガイド CodeZine BOOKS

システムテスト自動化 標準ガイド CodeZine BOOKS

個人的に電子書籍は山ほど買ってるんだけど、全体としての電子書籍の利用率は対して上がってないらしい

www.kyodo.co.jp

いいなと思った人は早めに使いだして使いつづけて、馴染まないなと思った人はもう一生さわらない感じになるんだろうか。

それとはまったく関係ないんだけど、Kindleの月替わりセールのページが早朝だとまだ用意されてないので、電子書籍に関する日記を書きます。

文字情報がない電子書籍に価値なし

確かに紙のほうがいいなと思う時もある(漫画とか)。

スマホの読みあげ機能を使って流し読みしてから、再び読みあげ機能を使ってやっていったほうが頭にはいるので、もう電子書籍は手放せない。

読みあげ優先で買う

買う時の優先順位は、

  1. ePub
  2. Google Play Book
  3. PDF
  4. Kindle

読みあげ機能は、AndroidGoogle Play Bookアプリが一番ラクに使えて自動ページ送りもあって最高。

で、手持ちのePubもアップロードしておくとGoogle Play Bookとして再生できるので、ePubGoogleで買える電子書籍が最優先となる。

ついでiOSのGood Reader。PDFの読みあげにはこれ。自動ページ送りはしてくれないんだけど、アクセシビリティ機能を有効にしなくても読みあげが使えるので良い。有料だけど買った。

KindleAndroidiOSアクセシビリティ機能を有効にして読みあげをさせなくてはならないので、全体の操作も視覚障害者使用となり、かなり不便になる。

AndroidKindleアプリは産廃と言ってもよく、Kindleで読みあげするならiOS

読みあげのおかげでスッと読めた本たち

仕事やめてからの本だから、ここ半年分ぐらい。読んだだけでやってないのもあった。

エリック・エヴァンスドメイン駆動設計、実践ドメイン駆動設計、Web API: The Good Parts、説得とヤル気の科学、エクストリームプログラミング、キラー・クエスチョン、体系的に学ぶ 安全なWebアプリケーションの作り方、Railsガイド、はじめてUNIXで仕事をする人が読む本、実践Heroku入門、SQLアンチパターン、パーフェクトソフトウェア、マネジメント、達人に学ぶDB設計徹底指南書、達人に学ぶSQL徹底指南書、角川インターネット講座(今4巻目)、裁判員のためのかみくだき刑法、ゲームプログラミングパターンズ、インターネットのカタチ、製造現場の事故を防ぐ安全工学の考え方と実践、メタプログラミングRuby 第2版、ビューティフルアーキテクチャ、「タッチパネル」のゲームデザイン

並べてみると意外と読んでた。紙しかない技術書は仕方なくそれを読んだ(3冊ぐらい?)

とにかく目と耳を使うので気がそれなくてよい。読書の習慣からながらく離れていたわたしも、読みあげ併用でなんとか読みこなすことができるようになったし、どうも本を読むのが苦手で、みたいな人は一度併用をお試しあれ。

2016/02

開館直後の海遊館に飛び込んだら体験がよかった。

先日Qiitaにこういうエントリーを投稿しました。

qiita.com

大阪でカピバラといえば海遊館、そうだ海遊館にいこう

ところで、ちょうど金曜日が失業認定日だったので、ハローワークに行かなければなりませんでした。わたしが行っているハローワーク大阪東の最寄り駅が堺筋本町駅で、そこから乗車すれば一本270円で大阪港にいける、これはカピバラ様に会いにいくしかないというわけです。

団体客の前に入らないとDeath

9時に失業認定、終了直前なので就職相談に行ってくださいねとのことなのでそれを済ませ就活実績1個解除したのちに、トロトロ歩いて9時半前後に堺筋本町から地下鉄に乗車、海遊館に到着したのは10時ちょうどというナイススケージュールでした。

平日だと油断していたら、中学生や幼稚園児などの団体客が海遊館前の広場各所で記念撮影を行っており、完全にいやな予感がしてきました。そういえば遠足って大体午前が本番で、あとはお弁当食べてぼちぼち帰るみたいな感じだった。

チケット売り場にもすでに列ができてこれはアカンかもと思いながら年間パスポートでスルッと入館、人多かったらとにかく早く通り抜けて帰ろうと思いながら早足て先に進みました。

カピバラさま〜

しかしとにかくカピバラ神のご尊顔は拝んで帰らないと話にならないので、興味ないところはがんがん飛ばして早足で歩いていくと思ったより人が少なくて歩きやすい。

f:id:mmmpa:20160226101108j:plain

うぉーカピバラさま〜こっちむいてくれ〜〜(結局ずっとメシ食べてはりました)

人がいない……

まぁ用事はすんだしぼちぼち見ながら帰ろうとしたところ、人がいない。ペンギンコーナーあたりででかいカメラもった人と出会った後は、本当に誰もいない。

f:id:mmmpa:20160226101410j:plain

ここらへんまで来た時に、あれ人いなくね?と気づいて慌てて撮りだしたので、地上部の写真はなしです。

f:id:mmmpa:20160226101415j:plain

f:id:mmmpa:20160226101519j:plain

いつもは人々がはりついて場所の奪いになりがちなジンベイザメコーナーもこの通り。

f:id:mmmpa:20160226101645j:plain

だいたい混雑してるトイレ前のコーナーにも誰もいない。

f:id:mmmpa:20160226101650j:plain

f:id:mmmpa:20160226101722j:plain

f:id:mmmpa:20160226101608j:plain

下にいくほどに自分一人しかいない感が高まってきて、人々があわてて去っていった世界の直後を舞台にしたなにかっぽくて本当によかった。(BGMが流れてるのがすこし残念だった)

f:id:mmmpa:20160226101928j:plain

f:id:mmmpa:20160226102001j:plain

f:id:mmmpa:20160226102112j:plain

鏡張りのコーナーも人がいないので、その気になれば無限遠に挑戦できるかも。

f:id:mmmpa:20160226102049j:plain

f:id:mmmpa:20160226102321j:plain

とにかく歩いている間、ほぼ完全に貸しきりで本当に滅亡後世界のゲームっぽくて、これはちょっとかなり贅沢な散歩だなと思いました。

狙ってできるようなものでもないかもしれないけど、またやりたい。

おわりに

早く進みすぎたようで順路のエスカレーターにこのような看板が立っていました。

f:id:mmmpa:20160226102239j:plain

じゃあトイレにも行きたいし、ちょっと戻りましょうかと思ったところ、直前に下りのエスカレーターに乗ってきたので戻る経路がない。

バグ技で早く進めすぎたゲームで詰むみたいなことになりわたしのぼうけんはこれでおわってしまった。

いまから数時間はもっとも平和な時間です。

先日の記事で土曜日の自殺者数は少ないと述べました。

自殺者素のチャートを表示するサイトですが、比較するには不便だったので改修して、こうなりました。

自殺を知る、自殺を考える :: 自殺者数チャート

曜日ではなくて時間帯では?

f:id:mmmpa:20160220172301p:plain

自殺を知る、自殺を考える :: 自殺者数チャート

仕事が終わる時間帯にどんどん下がっていって、ちょっと冷静になった時間に盛り上がる。

そして深夜を経てから、通勤時間には下がって、始業してからまた上がる……。

今日と明日は一週間でもっとも自殺者が少ない曜日です。しかし月曜日が来た瞬間に人々が死にはじめる。

ちょっとした思いつきでチャートにするサイトをつくったわけだが、視覚化されることによって、今まで思いもしなかったことがわかりはじめている。

つらい。つらいが、統計に関しては素人なので、まったく的はずれな見当をつけているのかもしれない。

今日は週末だから自殺者は少ないと思ったんだけど。

無職ももう半年になり、体がすっかりなじんできましたが、失業手当も残すところあと30日ほどになりなじんでる場合ではなくなってきました。

内閣府の統計をもとにグラフを描画するサイトをつくった

最近はおもにReactを触りつづけているのですが、チャートを描画するライブラリを使ってみようと作成しました。

自殺を知る、自殺を考える

比較をしたいんだけど数字の表じゃよくわからない……みたいなことがよくあったので、自分が思う範囲で好きなように並べて比較できるようにした感じです。

で、動作テスト中に何度も何度もチャートをみてて思ったことなどを。

予想通り月曜日の自殺者は多い

f:id:mmmpa:20160212032451p:plain

自殺を知る、自殺を考える::性別別の自殺者数を曜日で並べて表示

月曜日はやはり多い。

週末にかけてどんどん減っていって、土曜日が一番少なく、日曜日に微増するというのは興味深い点ですね。

土曜日に目覚めた時の、今日は休みだし、明日も休みという安心感は何ともいえないものがありました。

日曜日に少し増えるというのは、あの日曜日の昼を回ってからの「残り時間」が表示されるような妙な焦燥感を考えると、わかる気がします。

男性だと特に顕著

f:id:mmmpa:20160212032455p:plain

自殺を知る、自殺を考える::性別別の自殺者数を曜日で並べて表示

月曜日にドーンと増えて、週末にむけてどんどん減っていきます。

自殺者は働いているような年齢が大部分を占める( 自殺を知る、自殺を考える::性別別の自殺者数を年齢層で並べて表示 )ので、おおいに反映されていそうです。

女性は……?

f:id:mmmpa:20160212032500p:plain

自殺を知る、自殺を考える::性別別の自殺者数を曜日で並べて表示

曜日という、勤め人であるかどうかで影響に差がありそうな要素なので、男性ほど大きく差がついていませんでした。

月曜日に多いのは男性と同じですが、金曜日に増えるという傾向があり、これはとても興味ぶかかったです。

同居している男女がいるとして、男性は心待ちにしている休日が、ともに長い時間を過ごす女性にとってはストレス要因だとすると、これはなんだかすれ違いが感じられて陰鬱な気分になりますね。

いろいろ眺めてみると興味深い

というわけで、チャートで表示できるようにしたところ、今まで思いもしなかったようなことが見えるようになりました。

ただし、統計の勉強をしたわけではないので、見方を間違っている可能性があります。しかし、増えた減ったおまえのせいあいつのせいという言説に対して、自分で資料に当たれる能力は身につけていきたいものです。

2016/01

角川インターネット講座第2巻「ネットを支えるオープンソース」読んだ。

例の安く入手できた15巻セットの2巻目。

今回はプログラマー文化史

まずインターネット講座なので、インターネットを構成するソフトウェアの解説が入ります。

そしてプログラムとはどんなものであるか、そのプログラムを書くプログラマーとはどんな人種であるか、その人種の特性が生んだオープンソースとはどういう文化であるか、というのが巻として大きな流れですね。

サブトピックとして、プログラミング教育と、オープンソースと企業の関わり方みたいな章が挿入される感じで。

読み物としてはあいかわらずおもしろい

第1巻にひきつづき、一般教養が身につく本という印象です。

しかし定価で買わなくて本当に良かった。

角川インターネット講座第1巻「インターネットの基礎」読んだ。

21600円が2700円になっており、レビューも悪くなさそうなので買った。

定価ではどう考えてもめちゃくちゃ高い読み物本。

しかしこの値段なら、タイトルで釣ったり自分の経歴を盛ったりするような山師じゃなくてバックグラウンドがちゃんとしている著者がほとんどなので、安心して楽しめる(たぶん)。

プロジェクトインターネット

前半は今のインターネットにいたるまでの歴史が語られる。

世界中とつながるまでの色々な出来事が、直接関わっていた人間の口から語られるので、おもしろくないはずがなかった。

インターネットが発展する上で通底してる考え方や、問題をどう解決してきたかなどなど、おもしろい。

開発者向けではなさそう

多分このシリーズ全体がそうなんだろうけど、読みやすい分それだけの濃さという感じがする。

開発者ではなく、なんとなくやってきたホームページ屋さんが現状を知るとか、ネット事業に参入しようとしてるけどあまり詳しくない人とか、そういう人がなんとなく「インターネット」を知るのに向いていると思う。

あと俺みたいな、やっていってはいるけど、基礎知識が穴だらけの人間にもおすすめ。

でももうセール終わってるのか。

買っておいてよかった。

これからフロントエンドをやっていく人にもおすすめ。「Game Programming Patterns ソフトウェア開発の問題解決メニュー」やり終えた。

これから、と書いたのは、多分業務でやってきた人ならコードや先輩やその他で、すでに身につけていたり知っていたりすることが多いんじゃないかなと思ったからです。

知らず知らず使っていたパターンに名前がつく

わたしの場合は、GoFによるデザインパターンの名前と機能?ぐらいは一応知っていたものの、実際にはどういった実装をするのか知らない部分がありました。

この本を読み、ゲーム内での問題を例とした実装を書いていくことであやふやだった部分が明確になりました。

また、プログラミングをやってきて自然と使っていたパターンもあり、それに名前がついたことで、自分の中でも整理がついた気がします。

Webアプリのフロントエンドのそのままつかえる

最近React、Reduxもろもろを触っていてよく思うのが、治安維持につとめないと本当にすぐ大騒ぎになるなぁということです。

オブザーバー、イベントキュー、ステート、コンポーネントといったパターンは、治安維持のための手がかりとなります。

特に非同期処理のタイミングや発行の取りあつかいにはイベントキューがとても役立ちそうです。

しっている、やれるだけでは駄目

パターンの多くは、分離や隠蔽により、コードの混乱を抑えるはたらきがあります。

しかしそれも実際にコードを書くプログラマーのさじ加減ひとつでどうにでもなります。

パターンの中には、柔軟性の維持のために複雑さや取り扱いの難しさが上がってしまうものもあります。

パターンによってもたらされた秩序を十分に維持するには、規約や、意図を十分に伝えるドキュメントもまた必要であると思いました。

よくあることですが、難しいのはそれを行うことではなく、「忘れずに」それを行うことです。

Game Programming Patterns ソフトウェア開発の問題解決メニュー (impress top gear)

Game Programming Patterns ソフトウェア開発の問題解決メニュー (impress top gear)

ターボムイン(室内自転車トレーナーの一種)を毎日2時間半年ちょっと漕いでいたら壊れた場所。

ここ5年間ぐらい、毎朝2時間、エアロバイクの類を漕いでいます。

有職時は主に早朝に漕いで仕事に行くというパターンを続けていました。無職の今もその生活パターンは変えていません。

はじめに

まずターボムインを使っている理由みたいなものを書きます。

エアロバイクは割と壊れる

最初のころは普通の価格帯、1万円〜2万円のエアロバイクを使用していたのですが、毎日2時間こぐと、大体半年ぐらいでペダルの軸部分がブレはじめ、1年ぐらいで漕げないぐらいの異音を発しはじめます。

保証対象外になるような長時間の運動なので(だいたい1度に30分までとなっている)返品するわけにもいかず、かといって特殊な装置なので修理部品もなく、ということで毎年買いかえていました。

固定ローラーは普通の自転車と組みあわせて使う

固定ローラーというのは基本的に普通の自転車を固定して、室内で負荷をかけたトレーニングをする道具です。

エアロバイクで壊れた、負荷をかける部分ではなく自転車部分を普通の自転車にすれば、壊れた時に修理できて、結果的にお得だろうという判断で導入しました。

商品写真をみると大体どんな感じかわかってもらえると思います。

B60-R 固定式サイクルトレーナー

B60-R 固定式サイクルトレーナー

エリート TURBO MUIN(ターボムイン)

エリート TURBO MUIN(ターボムイン)

上は従来型の固定ローラーで、タイヤにローラーを押しあてて負荷をかけます。

下は最近出てきたタイプで、装置に後部ギアが直接つけられていて、自転車のタイヤをはずしてチェーンをひっかけることで、ダイレクトに負荷がかけられる装置です。わたしが今使っているのはこれです。

ターボムインのいいところ

ターボムインの前は固定ローラーを使用していましたが、比較して、まずタイヤがいらないのが大きい。タイヤは消耗品なので、固定ローラーとはいえ毎日漕いでいると擦りきれ、パンクの原因となります。

さらに、微妙なセッティングのちがいで負荷が変わることがないのも大きい。何しろチェーンがダイレクトですから、ちょっとした角度やずれでペダルが軽くなったり重くなったりすることがありません。

なお固定ローラーで小径自転車を用いると、固定ローラーからの熱が冷めきらずに、タイヤが溶けます。溶けて裂けてパンクします。5本ぐらい溶かして諦めて、固定ローラー用に自転車を買いました。

予定通り壊れた

ある日、ペダルが空転しにくくなって、固定ギアの自転車みたいな挙動をするようになりました。

しかしそれに構わずに漕ぎつづけていると(…)、購入後半年ぐらいのある日、ペダルにまったく抵抗がなくなって、前に漕いでいるのに逆回転しているかのように抵抗がなくなってしまいました。

本体じゃなくてよかった

本体が壊れていたら終わりだな……と思いながら自転車を外し、ギア周りを触ってみると空転するだけでなく、めちゃくちゃぐらついている。これはなんとかなるかもしれないと思って説明書を読みながらギア周りの部品をはずしました。

これはフリーボディとかフリーハブボティとかカセットボディとか呼ばれているもので、ラチェットの原理を利用して、ペダルを止まっている=チェーンが止まっているときに後輪のギアを空転させるための装置の、成れの果てです。

f:id:mmmpa:20160125122445p:plain

おわかりいただけるだろうか。

f:id:mmmpa:20160125122438p:plain

正常な状態では商品画像のように、漕ぐときにはひっかかって、逆転時には空転させるための爪があるのですが、それが全部もげていました。結果として空転していたわけです。

修理に3000円

というわけで適当なカセットボディを通販で購入し、取りかえることで無事復活したしました。

エアロバイクを買いかえることを考えれば、とりあえずは狙いどおり安く修理出来ました。

今日また壊れた

f:id:mmmpa:20160125122450p:plain

今度はなんとチェーンが切れました……。

修理に2000円

さいわい交換用のチェーンのストックがあったので速やかに交換、無事今日の運動ノルマを果たせました。正確な値段を覚えてないんですが、10速用のやつなので多分2000円ぐらい。

しかしターボムインを修理したのが年末ぐらいだったので、結構消耗品が消耗していく速度が早いなという印象です。

次の故障

つけくわえて言うと、エアロバイクがそうなったように、ペダルの軸ももうガタガタになっていますので、そろそろ交換しなければならない雰囲気が出てきています。

なかなかうまくいかないものですね

寒波がこないので各炭の感想を書きます。

今冬から火鉢で暖をとっています。

無職なので常時自宅待機、ほぼ毎日つきっきりで炭をもやした印象を書きます。

ちなみに、わかりやすいニュースにならないために、換気はちゃんとしています。

まとめ

練炭 オガ炭 ナラ炭 クヌギ 備長炭
火付き とてもいい よくない いい ふつう いい
保ち ふつう いい ふつう いい いい
香り ない ない ない いい ない
みため よくない よくない ふつう いい ふつう
ない ない ぱき ぱちん ない
ふつう ふつう ふつう ふつう あつい
ロマン ない ない ある ある ふつう

感想

練炭、オガ炭

どちらも加工炭です。特に練炭はニュースでおなじみですね。

練炭は一度着火すると、すぐに全体に火がまわる、空気供給用の穴があるなど実用に振ってあります。実際便利。最初の火が回るときの煙を把握していれば、ユーザビリティにすぐれる炭です。最初に買って、それ以来買っていません。

オガ炭はオガクズを固めて炭にしたものでオガ備長炭などと名付けられて売られています。練炭とはちがって炭と同じように火がつきづらい。調理でガンガンつかう以外には特にありがたみがなさそうで、1kg焼いた後は追加購入していません。

どちらもロマンはまったくない。

ナラ炭

クヌギ炭、備長炭とはちがって径が大きめの木を使って作られているため、どちらかといえば四角い形をして売られています。

その形状からバールなどを差しこんで割ると、大きさはそのままで薄めに割れるため、初期着火にとても便利に使えます。

薄く割ればあとは手でも簡単に割れるので、火鉢に配置するときも場所に困らないし、立てやすい。

燃えて行く時のパキパキとした音は、一度聞きつづけるとやみつきになります。

大きさの割には結構軽いので、燃え尽きるのも早いのが欠点といえば欠点ですね。

クヌギ

香りが良い。火付きは見た目とちがって割と悪い……でも火鉢に配置してしばらくすると、いい香りがします。

割らずに着火するのは困難だけど、着火できるとルックスがとても良い。

最初に買ったまともな炭がこれで、これのおかげで炭の着火の困難さに打ちのめされました。

備長炭

炭といえばこれ。硬い、重い、高い。

しかし、他の炭とは段違いの熱量を誇っていて、自分の熱で自分を燃やし続けられます。

他の炭は2つ以上に着火してお互いをあっためないと結構簡単に消えます。

火鉢の中に一欠片入れておくと、火が消えそうになった時のリカバリー時に便利。

というわけでおすすめは

岩手産(でなくてもいいけど)ナラ炭です。

音がいいのが一番よくて、個人的には雨音の次に捗る作業BGMです。

あと保ちの悪さも、一人暮らしの火の始末を考えるとむしろ利点です。

炭 岩手切炭 楢(なら)堅一級品(純国産品) 6kg

炭 岩手切炭 楢(なら)堅一級品(純国産品) 6kg

はてなブックマークページの、ひどい広告表示。

Googleの広告がどうのという記事があり、ひどいAdsense表示をバンする話かな?と思ったら、広告自体の話でした。

jp.techcrunch.com

さて、今日はお腹の調子とシリアナの治安が悪く、便座から離れられないため、めずらしくスマホはてなブックマークを読んでいたのですが、はてな社、ちょっとこの広告表示はひどくないですか。

あわせて読みたいボタン

なにかGoogle Adsense関連のおもしろい記事があるかも?と思って、ページ下部に表示されている「あわせて読みたい」ボタンを押そうとしたところこうなった。

f:id:mmmpa:20160122152049p:plain f:id:mmmpa:20160122152100p:plain

ページ内コンテンツにかぶるかたちで表示される広告はめずらしいものではありませんが、Fixedで表示されているボタンの上にかぶせるのは、いくらなんでもあんまりじゃあありませんか。

無料サービスの広告表示自体には特に感情がなくて、気になるものがあったら率先してクリックしていますが、押したくもない広告を誤って押してしまうというのは、気持ちがいいものではないですね。

このスクリーンショットChromeで撮りました。

その引き金をひいた人を叩いても平和はおとずれない。「製造現場の事故を防ぐ安全工学の考え方と実践」を読んだ。

バス事故があって、バス会社が叩かれているのを見て、思うところがあって最近読みなおしました。

たしかオーム社が去年にセールをしていた時に買ったはず。

最後の引き金と、引き金をひいた人だけに注目していてはなにもわからない

わたしは「衝撃の瞬間」や「メーデー」といったような、悲劇的な事故がなぜ起こったかを解説するドキュメンタリーが好きです。

そのような番組で必ず言われることが、「大事故が単一の原因で起こることはほとんどない。いくつかの要因が重なって大事故を引き起こす」ということです。

引き金をひいた原因の原因の原因の……

長野スキーバス転落事故では速度超過があったのではないかと言われています。

また、渋滞回避のためか、予定したルートとちがった道を走っていたそうです。

時間通りの運行のための、現場判断によるなんらかのショートカットが行われたのでしょう。

時間のプレッシャーの中、さらに運転手の技術不足がありました。

プレッシャーによる疲労、疲労による判断力低下、速度超過とそれを吸収できなかった技術不足が事故の直接原因でしょうか。

運転手の技術不足

これは本人の慢心などが原因ではありません。不慣れであるとは乗務以前から明らかであり、そんな運転手を乗せた社の責任です。

採用担当者のみならず同僚も知っていたということなので、社内ではわりと知られた事実であったのではないでしょうか。

そんな中でも、不慣れな運転手を起用せざるを得ない理由が社にもありました。

先のバスの大事故を受けて乗務員を2名、必ず乗せなければならなくなったこと、さらに仕事が思うようにとれない中でさらに、規制範囲を超えた安価で仕事を請け負わざるをえない状況にありました。

運転手を補充しようにも、いわゆるあそびの職員をつくるわけにもいかず、ギリギリで運用しなければならなかった。

こういうのはどんどん遡っていくと人類は死すべしみたいな結論に行きつくしかなくなるんですが、とにかく失敗をしようとして失敗する人はいないし、失敗した人だけが原因になっていることはまずありえないという話がとうとうと語られます。

実例や具体例も語られる

事故の発生に至った理由(本人のルール無視が原因としても、そのルール無視に至った理由)の解説を伴った考察は、本当の安全対策とはなにをすべきなのかというのがよくわかります。

人間はどうしても忘れるということを前提として実際に行われている対策などはそのまま使えます。

事故ゼロは無理

パーフェクトソフトウェアでも「完全なテストはない」と語られていましたが、安全工学においても同じようなことが言われます。

本当に致命的な事故は、もちろん最優先で防止しなくてはいけませんが、事故ゼロを目指すあまり作業者に煩雑な手順が増え、逆に事故の原因になることもありえます。

ただ手順を減らせというわけではなく、手順がある場合はその理由も必ず教育すべきであると語られていました。

 

ひとたび事故が起これば人命にかかわる工業での安全工学をソフトウェアにそのまま使うことはできませんが、事故を起こすのは人間だが、その人間に事故を起こさせる環境がまずあり、そこから改善しないと意味がないという視点は同じだと思いました。

製造現場の事故を防ぐ安全工学の考え方と実践

製造現場の事故を防ぐ安全工学の考え方と実践

たとえば泥酔した人を介抱しはじめると保護責任が。「裁判員のためのかみくだき刑法」読んだ。

あけましておめでとうございます、いまだ無職です。

今月ようやく雇用保険が30日分ちょっと支給されて楽になりました。

まず裁判員裁判となる可能性のある事件を教えてくれる

日本にはさまざまな犯罪があり、さまざまな刑事裁判が行われていますが、裁判員裁判となる裁判は種類が限られています。

そこで、対象となる裁判で必要になる刑法に絞ったうえで、平易な言葉で解説してくれるのがこの本です。

一般的感覚とのちがい

全編を通して、一般的にはこう感じられるでしょうが実際はこうなります。という解説がおおく挟まれており、実際「こう感じてた」のに課される刑の範囲が全然ちがう(ほとんど思ったより軽い)ことが多いのが印象的でした。

放火により多人数を殺した場合でも死刑にはならないとか、毒物混入により多人数を殺した場合でも死刑にはならない場合があるとか、そういう「故意」による振りわけが思ってる以上に重みがある感じでした。

泥酔者は病者あつかい

表題の件は「泥酔した人を介抱しながら歩いていたが、振りはらわれ踏切内に座りこんだ。放置したところ電車にはねられ死亡」から保護責任者遺棄致死罪という事例です。

泥酔者は病者扱いで、介抱をはじめた時点で責任が生じるので、こういう判決になります。

「面倒みるなら最後まで」あるいは、「酔っぱらいにははじめからかまうな」というところ。

みたいなみもふたもない著者のコメントがよかった。

刑事裁判結果がふんわりと読めるようになる

だいたい殺人などの判決は軽すぎでしょそれみたいな感想を抱くわけですが、どういうことを争点としてそうなるのかが多少わかるようになります。

その判断基準が良いとか悪いとかではなくて、とにかくそうなっているんだなということがわかる。

しかし一般感覚で裁判すると、殺人に限らず強盗も窃盗もなにもかも、おおむね死刑に落ち着くと思うので、刑法の融通の効かなさも大切なのかなと思いました。

裁判員のためのかみくだき刑法 (学研新書)

裁判員のためのかみくだき刑法 (学研新書)

2015/11

人間たちよ……という気分になる。「パーフェクトソフトウェア」読みおえた。

テストはすばらしい、テスト大好きな人間からすれば書く理由など探す必要もないぐらいなのに、なぜ納期前のバグの嵐や納品後のクレームを体験しつづけているような職場で、なお「書きましょうよ」といわなければならない状況があるのか。

なぜテストを書かない人々がいるのか、なぜ工期短縮するためにはテストをしなければよいなどと言えるのか、それらがテストを拒否する人々のキモチを知ることによりふんわりわかる。

この本はテストチームが開発チームとは別にいる状況を書いている

だからテストを拒否するキモチは、開発者自身が書く自動テストとはちょっと事情が違うかもしれない。

ただ「書きましょうよ」が開発者どころか経営者にもはねられることがあるのはなぜかというのがわかる。すべてキモチである。

後半はそういう人々のキモチをえんえんと語っているが、前半ではテストがどのように行われるべきか(技法ではなく定義のような話)というのが語られている。

テストは目的ではない

テストやテスト結果は、活用されなければただの時間と人材を浪費するコストである。

テストを増やせばただちになにかいいことがあるかというと、そうではない。

不必要なテストや不適切なテストは、まちがった安心感を与える可能性がある。

得たいと思う情報を得るために行うのがテストであり、「テスト」的な行動自体やそれ以外はテストではない。

テストは情報を得るためにある

動くか、動かないか、やるべきことをやっているか、やるべきではないことをやっていないか、レスポンスは早いか遅いか、やろうとしたことをやりおえることはできるか、やりなおせるか。

これらのテストはソフトが仕様通りであるかや、ソフトウェアの品質の高低を判断する情報を得るためにある。

完全なテストはない

はい。

パーフェクトソフトウエア

パーフェクトソフトウエア

生涯現役だもんで。

ZBrushをいじってたときのデータが出てきました。

f:id:mmmpa:20151109162212j:plain

f:id:mmmpa:20151109161818j:plain

f:id:mmmpa:20151109161822j:plain

f:id:mmmpa:20151109161826j:plain

われながらいい顔できてるな。

あとZBrushの操作独特すぎて予想とかできなくて直感で全く操作できなくてすごい。

どれくらい直感を拒否するかというと、イラレの10倍ぐらいの強度です。

2015/10

説明力が足りないのでCacooで図を描いたら便利だった。

RedisのSorted Setで挿入コストが低い木構造(ただし実質有限)を入れ子区間モデルで。 - Qiita ということを言葉だけで書こうとしたら、自分でもなにを言ってるかわからなくなりました。

そういえばCacooって有料で使ったままだったなーと思って久しぶりに使ったらやっぱり便利だった。埋め込みビューアーはこれなんの役に立つの?みたいな疑問を抱かないでもないが、作成した図を以下のように画像としてそのまま表示できるのでとてもよい。

(上のを描いた時点ではこの便利機能に気づいてなくてちまちま切り貼りしていた)

埋め込みビューアー

cacoo.com

シート別に、画像として直接表示できる

1

https://cacoo.com/diagrams/63VaqzdfVJehFSB7-F1EAC.png

2

https://cacoo.com/diagrams/63VaqzdfVJehFSB7-10F33.png

3

https://cacoo.com/diagrams/63VaqzdfVJehFSB7-32EAC.png

4

https://cacoo.com/diagrams/63VaqzdfVJehFSB7-9C921.png

病と処方箋。「SQLアンチパターン」読んだ。

実装した本人はなにか悪意があってそうしたわけではない。ちょっとした問題を、自身のひらめきで、あるいは伝承された秘伝で解決した結果、そうなってしまう。

頻出する「問題」があり、ある種の人々がたどりついてしまう「解決」(決して完全な未解決ではない)であるからこそパターンと呼ばれる。

SQLアンチパターン

SQLアンチパターン

アンチパターンには明確なマイナスがある

アンチパターンは、それぞれ何かが致命的に欠けているためにアンチパターンとされている。

ある条件下(しかも頻度が高い)で確実にパフォーマンスが低下する、スケーラビリティが極めて低い、明確なバグの原因になる、実装の把握が困難である(保守が難しい)、その他。

開発においてはコストを増大させることが多く、運用においては速度低下などでその価値を下げてしまうものもある。

最高のカタログ

これをそれぞれ開発経験や先輩からの口伝で一つずつ学ぶには膨大な時間が必要だろう。なかには悪いと気づかずに何十年も開発を続けてしまう人もいるだろう(いた)。

それをこういう綺麗な形でまとめあげて、しかもそれぞれの問題に対するわかりやすい処方箋までついている。

避ける方法だけではなく、良くするための攻める方法まで載っているというわけで、最高。

「あえて」の道もある。

実際の話、アルゴリズムの選択と同じく、空間コストと時間コスト、そのほかにも保守性や堅牢性のなどさまざまな要素の組み合わせの中でのトレードオフがあり、あえて選択されるパターンもある。

ただそれは「あえて」であって「なんとなく」とか「こうしてきたから」ではない。メリットデメリットを把握した上での選択であれば、理路整然とその理由を説明できるし、貴重なご意見も怖くない。

でもやっぱりアンチパターン

解決策に入る前にアンチパターンのまずい点をことこまかに説明してくれているおかげで、なぜその解決策を用いた方がいいのかよくわかる。

そして、この本ではアンチパターンを最良の方法ではないとしながらも、しかし、それを選択しうる場合も紹介している。

やはりアンチパターンアンチパターンであって、ごく限られた場合でないと使っていけないなという感じはした。

連絡のテンポ

2015/10/19 02:23

なんとしても雇用保険のアレをもらいたい!というわけじゃないので、仕事のくちさえあれば、スポットでもすぐにでも仕事をする。(もちろん内容は選ぶ)

なので、先日、仕事があるんだけどやらない?というメールがきた時には「ちゃんと検討してできるかどうか返答するから、要件を教えてください」という内容で返信した。

仕事があるんだけど、とメールが来るからには、もう仕事の内容は知ってるはずなのに、数日経っても返信なし。

なんなのか。

▲ 2015/10/19 02:22

仮にこの仕事を受けたとして、連絡の窓口がその人になるのであれば、まったく連絡に対する考え方がちがうので、そこらへんがボトルネックになる可能性がある。

▲ 2015/10/19 02:25

詳細を知らないまま、ただ人がいるかどうかだけど問いあわせされて、それで俺がまだ無職かどうか確認しただけという可能性もあるか。

その場合、現段階ではメール主からクライアントに連絡が行って、それの待ちということではあるけど、それでもあまり長くかかるなら全体的に連絡が遅いということで絶望しかない。

メモまとめ

2015/10/18 05:54

気合いれて筋トレしたら体調が完全に悪くなった。

▲ 2015/10/18 06:00

体調悪すぎるときは自分の意志で手を動かすのはあきらめて、技術書読みあげを聞くことにしよう。

▲ 2015/10/18 20:04

体調悪いのに出かけた結果、大事故を起こしそうになったが、なんとかトイレを確保できた。

しかし家にたどりつけるのか。

▲ 2015/10/18 06:49

なんとか帰ってこれたけど、外のトイレ使うと自動洗浄されるので体調悪いときはちょっと困る。

汚い話ではあるが、排泄物の状態をみてどの食事が原因であるとか、どのような不良が発生しているかとかがわかる。

日常的に同じようなメニューしか食べていないので変化がとらえやすい。

2015/10/18 06:47

このメモ帳、もう一つセッティングしておいて読書感想文用のメモストアにしたらよさそう。

手動でメール送信できるボタンがあれば良い。

▲ 2015/10/18 06:49

とりあえずローカルで動かしてるやつをそれようにするか。

メモ18枚(2015/10/17 05:47~2015/10/18 01:51)まとめ

2015/10/17 05:46

糖尿病患者(特定の人物)、自撮りを見るかぎりではまた太りだしており、これは節制してないなと思っていたら、とうとう病院に連行される雰囲気をかもす発言をしている。

昼から日本酒飲んだり、妻の買ってきた小麦粉とバターのかたまりのようなお菓子を一人でたいらげてしまったりしているので、そろそろ入院や検査などのイベントが発生しないと逆に不自然ではあった。

▲ 2015/10/17 05:47

COPDや生活習慣性の糖尿病に関してはなにひとつ同情する要素がなく、ただただペイバックですよねという視点しかない。

濃い人生とやらを送るのも結構なことだが、その後には思ったよりつらい人生が長めにある。

▲ 2015/10/17 05:47

叔父は、3年ぐらい前に、肺がんで苦しみぬいて死んだ。

それを目のあたりにしているはずのいとこ2人、そして人づてではあるが知っている妹夫婦は喫煙者のままだ。なので、どういう結末を迎えうるか知ってるというのは、特に喫煙をストップさせる要素にならないと思っている。

おれは最高にこわがりなので、ちょっとした息ぐるしさに不安をおぼえて眠れないことがたびたびあった。それで10年ぐらい前にもうやめたいと思って、やめた。

怖がりドリブンは依存症に勝つ。

2015/10/17 20:21

無事Heroku上で動くようになったが、config.ruだけ書いてProcfileを用意するのを忘れていたため、Railsアプリケーションと判定されてしまっていた。

Gemfile.lock内のrailtiesがあるとRailsと判定されるため、Semberが必死にbin/railsをさがしてくれていた。本当にすまない。

web: bundle exec rackup config.ru -p $PORT

このようなProcfileを書くことにより、自分が起動したいものを起動できるようになる。つまりforeman->Procfile->config.ruとなって幸せになる。

2015/10/17 20:23

なんか変なエラー出た……

▲ 2015/10/17 05:50

どこかのタイミングでメッセージが複製されてしまうのがあるっぽい。

▲ 2015/10/17 07:41

これは多分配列の連結をしくじってるやつで、よくやるやつだ。

▲ 2015/10/17 20:24

挿入対象が末尾のときにindexがとれなくて-1になっているのに気づかないやつだった。

2015/10/17 20:27

メモ単位で文章をまとめていくのは、思ったより具合が良い。

並べかえや主従関係の調整はできないが、それはreplyなどを適当に使って調整すればなんとかなる。

▲ 2015/10/17 05:46

最初の案ではedit機能はなかったんだけど、editでメモ単位で修正できるのは思ったよりよかった。

メモが一つの考えの単位となって、ほかと必要以上に混ざらないのがいいのかもしれない。

2015/10/18 00:11

子持ちのメッセージ削除時には直属の子供のreply_toを外す。

その上でscoreを更新して、indexをロードしたときに再取得される位置に持ってきておけば、親削除時の親無し子問題は解決するかな。

繰り上げだと一つ以上の子がいる場合にどうにもならないから、開放が一番よさそう。

2015/10/18 00:24

開発において、仕様などが流動的なのは常であるので、じゃあそれにちゃんと対応できるようにということで、早め早めに情報が欲しいと思っている。

早めに欲しいのはなにも作業中だけじゃなくて、作業に入る前のはじめての打ちあわせでも、事前情報をもとにある程度の調査をして選択肢を用意できるので、もちろん欲しい。

前職では、事前情報なしで打ちあわせに入って、その時にならないと概要がわからないというのがよくあった。なんでだろう。

こういう伝達の反射速度とかテンポとか粒度とか重要視してる程度に剥離があると、一緒に仕事をするのは結構つらい感じがする。

2015/10/18 00:25

replyとeditを押したとき、それのキャンセルができないのでできるようにする。

▲ 2015/10/18 01:54

した。

けどこういう単純なリセットも親父に処理を投げたほうが良いのかな。まわりくどい感じがするけど、とりあえず統一のためにそうした。

2015/10/18 01:51

やはり入れ子区間に問題があるので、scoreの間が詰まったときはちゃんと自動的に再分配するようにした方がいいかな。

基準としては親メッセージに含まれるリプライの総計か、新しくつくられたスコアの値と、その基準値との間が詰まりすぎてるか。

再調整は同期的にやるか、イベントつかって処理の外でやっちゃうか、どっちだろうな。現段階ではブラウザリロードしか全部取得しなおす方法がないし、はてさて。

▲ 2015/10/17 21:01

スコア調整された場合は、調整されたエンティティすべてを返すという施策が考えられる。

スコア調整が発生するのはreplyとreplyのeditをかける時だから、実質的にpostでもputでも起こりうる。

現在の作成対象、編集対象のみを返す形式ではなくて、変更が生じたエンティティすべてを返すようにするかな。

2015/10/18 01:51

Herokuに乗せてみてわかったけど、ajaxするならちゃんとロックしないといけないな。連打で死ぬ可能性が大きい。

▲ 2015/10/18 01:53

React.createClassの第3引数以降は可変長で中身を指定できるのは知っていたけど、単純な配列投げただけでも展開してくれた。

メッセージpost時のYap!ボタンの中身切り替えが容易だった。

手編集メモまとめ

なんかできあがったと思ったら調整項目山盛り出てきておわりまてん。

2015/10/16 11:10

まっさら。

▲ 2015/10/16 11:10

ブログにメモを全部投げた後はRedis.current.flushallするという、なげやりな初期化手法によりRedisの健全性がたもたれる。

▲ 2015/10/16 11:09

あとはHerokuにのっけてHeroku Schedulerでちゃんと動くかみたらQiitaにメモってあがりみたいな寸法である。

(Sinatra+Redis+React)+Herokuかなり身軽な感じがしていいな。とくに今回のようなちんまりしたSPAつくるときはRailsじゃなくてSinatraでうすくルーティングするのがかなり具合がよかった。

ページ遷移するようなやつだとまたちがうのかもしれないけど。

2015/10/16 11:24

ただスキャンをしただけの電子書籍を極度に憎んでいる。

  • 電子書籍化ずみ」という印がついてしまって今後epubなど文字情報をもった版が発売されることがめちゃくちゃに期待薄になること
  • 「これで電子書籍化ってことでいいんだ」とギョーカイ人が思ってしまって、そのような本が増えてしまう原因になる

というのが理由。

はっきり害。

▲ 2015/10/16 11:24

最近出た技術書では、ほとんどの場合がepubベースになってて非常に読みやすくてありがたいことこの上ない。

オライリーはまだpdfのみの場合もあるけど、達人出版社その他kindlegoogle play bookの双方で展開してるところはepubベースというかリフロー版が多くて、読み上げ機能をフル活用できるおかげで読みやすい。

KADOKAWA半額祭りで購入した、はじめてUNIXで~、Heroku入門、Redis入門はすべて読み上げ併用で高速かつ効果的に読めた。最高。

2015/10/17 13:09

リプライ機能は新規メッセージのスコアを必ず+1することによって、リプライにおいては

  • 次に表示される親である可能性があるスコア(リプライ先のスコア-1)
  • 次のメッセージのスコア(つまりおなじ親から派生したリプライ。リプライのリプライを含む)

のリプライ先のスコアとの差が小さい方を基準にして、その中間地点を新しいメッセージのスコアにすることによりsorted単独でリプライ関係を維持することができた。

▲ 2015/10/17 11:49

自分で書いててあれなんだけど、何言ってるかさっぱりわからん。

図にしたら一発でわかるんだが。

メモ11枚(2015/10/15 13:47~2015/10/16 05:39)まとめ

このエントリーはShort Message Storeから自動的に投稿される

2015/10/15 13:47

高速メモ帳ができあがった。

▲ 2015/10/16 05:32

リプライ機能もアルよ。

2015/10/15 15:36

両面テープを買いにいったのに両面テープ以外のものを買って両面テープを買ってこなかった。

2015/10/15 15:42

ちょっと前に机の上でシルバー磨き汁の漏出事故があって、その時はたいした被害もなくてよかった、酸だしな。と思ってた。んだけど、木曜日恒例の鼻毛切り祭りを開催したところ、おきにいりの鼻毛切りハサミがガビガビになってた……。

いわゆる先が丸まった安全バサミの切りにくさを嫌って眉毛整え用の先がどするでーハサミを使ってたんだが、これがあんまりコンビニで見あたらなくて、見失う度に買いなおすことができなくて必死に大捜索するハメになっていた。 まぁ室内紛失にかんしてはちゃんと管理しましょうというか管理するようになってかなり頻度はさがったんだけど、今回みたいに使用不可能になるとつらい。 どこで買えばいいんだ。

ぐぐれば一発で出てくるとは思うんだけど、それも負けた気がするしな。

2015/10/15 15:42

このアプリ、実装の手間という定数係数をきらって最初はVueを使って書いてたんだけど、結局いろいろな機能性や無駄な処理を刈る実装を考えると、Reactが最適であろうということで生Reactで書いた。

ちんまりとしたアプリなので、MainComponentというオヤジを一人つくって、たいがい処理からsetStateまでややこしいところは全部一任、ぶらさがるコンポーネントは基本的にprops見てるだけでよい+オヤジから渡された関数を使えばよいということにしたらシンプルに実装できてめでたかった。

2015/10/15 15:42

とりあえず両面テープを買ってきて左手デバイスをタブレットに貼りつけることからはじめようかな。本棚を移動して、右手でマウスが操作できる台を用意すれば、とりあえず不自由なくいけそうかな。

あとは手元に小型キーボードを貼りつけておけばなんとかなりそう。

2015/10/15 15:43

久しぶりのアプリケーションを久しぶりにインストールして久しぶりにさわったら完全にショートカットを忘れていて、操作に手間取ることこの上ない。

ショートカットが作業効率をあげるパワーは今更語るようなことではないんだけど、昔の知り合いに、かたくなにショートカットを覚えることを拒否し、クリッククリックでツールを切りかえて絵を描いてる人がいた。

いまもクリッククリックしているのだろうか。

2015/10/15 16:16

シンプルな機能のみでゴールがはっきりしているせいか、このアプリケーションはとてもつくりやすかった。

あとReactもややこしいのを2回ぐらいつくってたせいか、ちんまりとした実装を手早くやるぶんには特に負荷を感じなかったので、いつも使う道具箱にいれとくみたいな感じでいいと思う。

ただCoffeScriptが死に体になってるのがなぁ……まぁ速度優先でとりあえずもうちょい使おう。

2015/10/15 16:22

redis-rbはredisのコマンドを名前そのままで持ってきて使えるようにしてるgemで、redis-objectsはもうちょっとrubyライクに使えるように調整してあるんだけど rangebyscoreっていうメソッド名はさすがにマチタマエみたいになった。

あとドキュメントがほぼなくて、サンプルもわかったようなわからないようなみたいな感じなのでぐぐるより各型のソース読んだ方が早い。

2015/10/15 17:02

このアプリケーションに、あとはリプライ機能をつけようと思ってるんだけど、必要なのかな。編集機能があるから、それで追記すれば済むような気もするな。

しかし思考というか思いつきの遷移が可視化されるのはおもしろいのかもしれない。ただずらずらっと書いてあると、それがいきなり完成形でスッと出てきたみたいになるけど、はじまりではこう思ってました、しかしちゃんと考えた、あるいは時間が経ってからはこうなった、というのが見えるのは案外大切かもしれない。

2015/10/16 05:39

ポセイドンアドベンチャーの2ってどういうことだよと思ったけど、沈没船、あるいは沈没しかけてる船を狙ういわば火事場泥棒もいておかしくないよな。

知名度を借りつつ繋げるのはうまいと思った。

CGIとしてつくってSinatraにのせた時に気づいたことメモ。

フレームワークに頼りきりだったので、リクエストとレスポンス、アプリケーションの入力と出力などを個別に意識できず、それぞれの責務をうまく分離できていなかった。

リクエスト

ユーザーのアクセスした場所やポストされたパラメーター、自動的に送られるクッキーなどが関心の対象になる。

フレームワークやライブラリによって、参照する方法や保持の形がちがう。

未加工でつっこんだ時の問題

生のリクエストを入力としてそのままアプリケーションに突っ込むような処理にすると単一の環境やその類型の環境でしか動かない。

動かなければそれでいいか、類型の環境で「動いているように見える」と問題が複雑化する。

整形する仕組みの分離

リクエストをアプリケーションが取りあつかえる形の入力に整形する層は、必ず分離して実装する。

整形された入力からさらに必要なルーティングやフィルタリングを行い、アプリケーションにつっこまれる。

整形はアプリケーションにとってのドメインではないので、その点は留意しておく。

レスポンス

HTTPのページとしてユーザーに結果などを伝えなくてはならない。ステータスコードやクッキー、HTMLが含まれる。

必ずしもアプリケーションの出力がそのまま反映されるとは限らないが、リクエストに対するレスポンスなので、アプリケーションの出力がレスポンスを決定することに間違いはない。

出力は必ずしもHTMLとは限らない

コマンド的なリクエストの場合、アプリケーションは何らかの変更を行って、それの成否のみを返すのみということがある。

この場合、アプリケーションの出力はただの成否なので、それをそのままHTMLに吐き出すということはなく、そこからステータスコードやHTMLを決定する機構が必要になる。

レスポンスを返す仕組みもちがう

リクエストの扱いと同じく、レスポンスの扱いも環境によってちがう。

クッキー、ステータスコード、ボディなどそれぞれを作成する層と、それをユーザーに返せるように整形する層は分離するべきである。

何をどこまで切り分けるか

臨機応変に対応するのがいろいろなバランス取りの問題からも最良だとは思うが、これは分けると決めておいた方が一貫性が保たれるので整理される。

初期化と実行

これは本当にまるで意識できてなかったので苦労した。

CGIで動かしたときの立ちあがりの遅さもあるが、そもそもの永続化などインフラ的な準備をどうするか?という視点が全くなかった。

今回特に設定ファイルから自動的にカラムなどが決定される仕組みだったので、本当は初期化がかなりキモで気を遣ってやらなくてはならなかったのではと思った。

最終的に永続化の仕組みはとっぱらってメール送信のみにしたのであまり踏み込めてない。

Sinatraにマウントできるかという観点

Sinatraの起動で一緒に初期化、各エントリポイントで出力 = SomeApplication.new(入力).excuteできるような実装にすると分離ができてすっきりできそう。

入力と出力のフォーマットを統一しておけば、リクエストとレスポンスがどうなっても整形層一個つくれば済んでよさそう。

Herokuにポンとのせる、主に静的メインのサイトに、ちょっとしたといあわせとかを処理する小さなアプリケーションを付け加えていくような運用なら使えるのかな。自分で書いてみて特に(運用上は)メリットが見えない。

Herokuを使う予定のない人もTwelve-Factorは読んでおいて損がない。「プロフェッショナルのための実践Heroku入門」読んだ。

The Twelve-Factor AppはHerokuが提唱する、モダンなアプリケーション開発において守らなければならないドグマみたいなもんです。

なおネットで無料で和訳ページがあります。

12factor.net

見よう見まねでやってた実際的な開発手法がカリッとまとまっているので、再就職して新しい流儀が必要になるまでは守っていきたい。

でHeroku本体。

Heroku内でどのようにアプリケーションが起動するかわかる

おれはRailsでHerokuを知ったのでRailsgit push heroku masterすれば勝手に起動して動きつづけて便利で最高程度の認識しかなかった。

この本を読むことで実際pushされたアプリケーションがどのように起動されるかわかるので、必要最小限なにがあればいいかわかるようになる。

ぐぐればいくらでも出てくるんだけど、Procfile+Sinatraで最小限の静的サイトを起動することもできる。

github.com

実際的なよくある問題に対する対処法がわかる

これもぐぐればいくらでも出てくるんだけど、例えばドメインを割り振る方法であるとか、起動単位であるDynoがファイルを保持しないとか、コンパイル時間でこけるとか、そういう問題が書いてある。

こういうのは直面した各々が好き勝手に対処法を書いているので一貫性に欠ける部分があり、一応公式にこうしたら良いよねというのが一貫性を以ってまとめられているのを読んだ方がよさそう。

うすいし高速で読めるのでぐぐってばらばらの知識を手に入れるより読むほうが良い

KADOKAWAの半額祭りで買ったんで高速で読み終わっても損した感じはなかったんだけど、なんか祭りが終わったいまでも半額みたいです。

Kindleの使用感を我慢できる人は買おう。(おれgoogle play book派)

最低限知っておいた方がいい知識。「はじめてUNIXで仕事をする人が読む本」読んだ。

あくまで最低限なので、これだけ知っていれば大丈夫ということもないし、これを知らないと就職できないというわけではない。

知識のでこぼこをそっと埋める

あとがきに書いてあるんだが、これは著者先生たちの創夢という会社における新人研修のテキストがもとになっている。

あとがきでは、まるでUNIXへの造詣がないような人間は入ってこないだろうし、基礎以前の話は省いたとも書いてある。

一方でそのように学生時分からさわっているような人間は、逆にすっぽり抜けおちてる部分がある場合があるので、そういう人たちにも助けになるだろう的なことが書いてある。

俺の場合は仕事の必要に応じて自分勝手にやってきたのが殆どなので、確かに知っている情報は多かったが、TCP/IPの歴史やちょっとしたユーザー権限の話など、そうだったのかみたいな部分もまた多かった。

ので読んでよかったと思う。

LPICの無料配布PDFを高速で読んで、この本でUNIX常識みたいな豆知識を含めた情報をさらった上で、さらに勉強をすすめるのがいいと思う。

今朝のメールフォームをHerokuで動くように変更した。

http://sinatra-liaison.herokuapp.com/

github.com

データベースへの保持をしなくなったので、Herokuにかんして特につまずくことはなかった。

CGISinatra間でのちょっとした違いでちょっと時間がかかった。

CGISinatraでのパラメーターの扱いのちがい

チェックボックスは配列としてうけとるのだが、CGIライブラリはname="hobby"複数選択で配列扱いになる一方で、Sinatraではname="hobby[]"のように明示的に配列であることを示さなくてはならない。

CGIでの値の扱いにはクラスを用意していたので、name="hobby[]"で統一しておいて、CGIで動くときは[]を消すことにした。

ActiveRecordからActiveModelへ

最初に思いついた、動的にattributesとvalidatorを追加するという案に固執していたので、サーバー上のデータを確認する方法もないのにDBに保存していた。

バリデーションを適切に行うだけならActiveModelを用いればよい、と割りきってフォームの内容の保持はやめて全てメールで飛ばすようにした。

CGIの立ちあがりも0.2秒ほど早くなった気がする。

トークンの掃除はDyno更新まかせ

ポスト時の確認用トークンの保持は巣のファイルで行い、送信成功時の掃除の他にはなにもしていないのでどんどんたまる。

Dyno更新時にまっさらになるのにおまかせといったところだが、CGI動作時のことが考慮されていない。

ビューからCGI.outを外に出す

仕事の分離ということで、最終的なレンダリングCGIではapp.rbが、HerokuではSinatraがそれぞれ行い、ビューはHTMLを生成するだけになった。

割と小奇麗になった

まだまだ甘いところもあるが、作成開始日のあの雑然とした感じに比べればかなり綺麗になった。

CGIとしてRubyでメールフォームした。その後、立ちあがりが遅いのでSinatraで動くようにした。

最近バックトゥ基礎ということでC言語の学習やデータ構造、アルゴリズムの勉強をしていたのですが、そういえばCGI時代に生きてきたけどCGIで何かをつくったことがないと思い当たった。

それに、徳丸本やAPIの本、Railsガイドなどを読んでフレームワークが良い仕事をしていることを知り、それに乗っかって生きるのがベストプラクティスであることはわかったのだが、いや自分でもちゃんと書けた方がいいよねとなったのでメールフォーム作成するに至った。

未使用トークンの破棄とかの処理抜けてるけど、以下雑然としたメモ。

github.com

CGIでももちろん自動テストの威力が炸裂する

CGIがアクセスの都度全てを読み込むことを考えると、ペライチでコードを書いた方がよかったのかもしれないが、まぁええやろということでいつもの通り細かくクラスでファイルを分けていく。

細かく分けていくとテストがしやすいし、RailsでやってるノリでRSpecが書ける。

CGI作成の機微がつかめなくて何度も大きく書きかえたが、テストのおかげでわやにならずに済んだ。自動テスト最高。

リッチなライブラリはrequireだけで重い

一から作成するとはいえ、一からデータベース処理などを書くのはあきらかに前時代的で無駄な再発明だし、さすがに時間の無駄なのでactivevホゲ系のライブラリは積極的に使った。

しかし中でもActionMailerだけはrequireするだけで劇的に立ちあがりが重たくなる。0.5秒~1.0秒かかったので、requireはメール送信処理の直前に行うように変更した。

前準備CGIをわける

データベースに対するマイグレーション処理などもアクセスごとに判定していたのだが、さすがに無駄っぽいのでわけた。

設定を書いてアップして前準備.rbにアクセスして削除するという、なんだかCGIっぽい感じになった。

処理をクラスメソッドではなくインスタンスメソッドで行う

クラスメソッドでずらずらっとやってしまうのではなく、入力や設定を処理クラスに渡してnewした上で、それを走らせる。

CGIだから変数の残留とかそんなに考える必要はないんだろうけど、こっちの方が収まりがよかった。

後で行ったSinatra化もこれのおかげですんなり行えたと思う。

ちょっとコストをはらって早くするSinatra

サーバーとしてアクセスを待ちうけるから、立ちあがりの遅さがなくてよい。

Railsをバーンと立ち上げる規模の話じゃない小規模な処理でも、Sinatraでサーバーとして動かした方が速度とのバランス的によさそう。

テストを簡単にする目的でポストされたデータの処理やクッキーの処理は処理クラスの外に出していたので、Sinatraにはめるのもすんなりいって良かった。

オフラインモードを回避する処理をSinatra側に入れたら、CGIとしての動作もできるままでSinatra上でも動くようになった。めでたい。

横着したせいでHerokuではうごかなかった

SQLite使ってたんだけど、PostgreSQLに切り替える気力が残っていなかった。

とはいえ、せっかくHeroku入門を読み終えたのだからHerokuでも動く何かをつくりましょう。

Debian + PhantomJS 1.9.2でPHPのセッションがうまく維持されなかったがPhantomJS 2.0.0にアップデートしたらうまくいった。

題名のとおりなんですが、そんな感じです。

アカウントの作成などを完全に外側から行っていましたがどうもうまくいかない。それでPhantomJSが結構なバグ持ちだということを思い出したわけです。

コンパイルは簡単です

Linux向けのコンパイル済みのバイナリがなく、だらだらと1.9.2を使っていましたが、めんどくさがらずに早くコンパイルすればよかったなぁ、という感じです。

Build | PhantomJSにある通りにコンパイルしてパスを通せばそれで使えるので簡単ですね。

sudo apt-get install build-essential g++ flex bison gperf ruby perl \
  libsqlite3-dev libfontconfig1-dev libicu-dev libfreetype6 libssl-dev \
  libpng-dev libjpeg-dev python libx11-dev libxext-dev
git clone git://github.com/ariya/phantomjs.git
cd phantomjs
git checkout 2.0
./build.sh

久しぶりの仮想ブラウザでのテスティング

自動テストでスクリーンショットを山ほど撮るのが大好きなので、久しぶりのテスティング楽しいです。

無償なのが難点ちゃあ難点ですけど、まぁ……。

ふわふわの基礎にロードローラー。「定本 Cプログラマのためのアルゴリズムとデータ構造」やりおえた。

こんにちは、素養。

定本 Cプログラマのためのアルゴリズムとデータ構造 (SOFTBANK BOOKS)

定本 Cプログラマのためのアルゴリズムとデータ構造 (SOFTBANK BOOKS)

いろいろな想いがあり、いろいろな実装がある

時間とリソースをはかりに乗せ、望まれるバランスにより近い実装を行うのがよりよいプログラミングである。多種多様な条件に柔軟に対応していくためには、多くの選択肢を知らなければならない。

世の中にはさまざまなメリットデメリットを持つ、データ構造やアルゴリズムがある。それらの世界で視野を広げるための大切な第一歩となる、基本的な知識を与えてくれるよい本だった。

パッと見たときのコードの多さはC由来

純粋にアルゴリズムだけを知りたいなら、ほかの本がいいかもと思わないでもない。あるいは参考だけにして他の言語で書きなおすとか。

しかし使用リソースを最小限に整列や問題解決をやっていくのも、また大切な技術だと思うので、これはやはりCで読んでCで書いてよかったなという感じがする。

そもそもいきなりCやりだしたのも、この本はCで読んだ方がよいのでは?とぼんやり思ったからだった。

mmmpa.hatenablog.com

なにかがなんとなくわかるようになる

各種言語でなんでこんな仕組みなの?みたいに疑問に思っていたところがなんとなく察せられるようになった気がする。

特にアルゴリズムよりもデータ構造のメリットデメリット由来なんだろうなというのが多い。

正規表現で学ぶ有限性オートマトンがよかった

オートマトンとかステートマシンとかはspreeのコード読んでたときにふんわりと知った。

ふんわり知ることができたので、また後で勉強しようと本だけ買ってある状態で置いてあったんだけど、この本読んでたらいきなり出てきて興奮した上に、正規表現という割と身近でややこしいものの実装を通して、その全体像がうっすら見えた気がする。

というわけで次はオートマトンの数学っぽい本を読みます。

いろいろなアルゴリズム最高

最後の部になる第6部では

について解説されているが、とくにメモリ管理アルゴリズムで解説されている断片化と詰め直し、キャッシュで解説されているバッファリングとLRU法は、そのまま他の実装で有効活用できそうでよかった。

断片化と詰め直しはスケジューリングアプリケーションを作成してるときに手探りで実装したけど、整理されたやりかたを示されるとなるほど~となってよかった。

つぎの本

オートマトンの本はまぁ読むとして、途中でアルゴリズムクイックリファレンスをぱらぱらと読んでたら同じ実装でもびみょうにやり方がちがったりしたので、そっちもちゃんと読みたい、じゃなくてやりたいなという感じ。

アルゴリズムクイックリファレンス

アルゴリズムクイックリファレンス

なにができるか、なにをしてくれているか。「Railsガイド 電子書籍版」全部流し読みした。

サイトでも読めるし電子書籍でも読める。本の体裁になっている方が「全部流し読む」のは楽な印象。

EPUB版をGoogle play bookに突っこんで暇があれば読んでいた。

tatsu-zine.com

railsguides.jp

読んだらよさそうなところ

すでにRailsを多少業務でやっていってる俺みたいな駆け出しメンズが読めばよさそうなところ。

第14章 Active Support コア拡張機能

便利機能もりだくさんすぎて、これ書かなきゃいけないかなと思ったらとりあえず調べてみる価値がある。

第19章 Rails セキュリティガイド

自動的に行われているおもてなしなどを網羅。

Railsの外へとびだす人も再読することにより出先での即死を防止できる。

第21章 Rails アプリケーションを設定する

思ってた以上に設定でどうにかなる。

第33章 API ドキュメント作成ガイドライン

わかりあうためのコメントの書き方など。

スタイルを確立していない俺みたいな人は、Railsに限らず従うといいと思う。

なにができるか知らなければ調べることもできない

暗記する必要はないけど、こういうのはRailsでできるっていうのが頭の片隅にあれば、時間を節約できて生産性を稼げると思いました。

2015/09

それはきっと自分であける穴。「体系的に学ぶ安全なWebアプリケーションの作り方」を再読した。

発売当初に買って読んだんだけど、当時はホームページ屋勤務で、Javascript書くことでぐらいしかダイレクトにかかわることがなかった。

バックエンドもいじるようになった今、当時とはちがった視点で読めた。

あらゆる入力がとんでくる

入力は制御できないので、あらゆる値がとんでくることを想定して開発しなくてはならない。

自分で穴をあける

定番のライブラリがあるにもかかわらず、セキュリティにかかわる部分を自社開発した結果、穴をあける事例が多いとのこと。

現代のWebアプリケーション開発では、人気のあるすぐれたフレームワークにのっかって行うのが主流だと思う。人気のあるすぐれたフレームワークやライブラリは、すぐれた技術者がよってたかって開発したり攻撃したりするので、定型の攻撃はまず間違いなく想定され、対策を施されている。

そんな安全な環境が用意されているにもかかわらず、「いや自分でやった方がええし、この部分はワシがやったる」という血気盛んな人間や「ライブラリの使い方調べるのめんどくさいし、この部分はワシがやったる」というめんどくさがりなんだかなんなんだかわからない人間により、穴があけられる。

かつての戦いの歴史

すぐれたフレームワークにより、多くの攻撃は自動的に封じられている。自動的すぎてまるでそれが存在しないかのように開発を行うことができるが、もちろん一歩外に出ればそれは存在する。

フレームワークを使わなくなった途端、ああそれはフレームワークが自動的にやってくれた部分だよね、というバグ(そして脆弱性)を仕込んだ事例を、実際目の当たりにしたことがある。

そういう戦いがあり、今も普通に攻撃を受けうるということを知っておかなければならない。フレームワークの一見わずらわしい部分の理由がわかる。

読後に野良セキュリティチェックしてたら

CSRFを数件見つけたので連絡しました。

徳丸本2が出るそうです

こっちも買うしかない。

徳丸浩のWebセキュリティ教室

徳丸浩のWebセキュリティ教室

可読性をさげてまでコード量を少なくしたかった先達の気持ちになる。「C実践プログラミング」やりおえた。

シルバーウィーク全部使っておわらしたるぜ!と意気込んでいたが見事にはみだした。

まぁ無職だから関係ないんですけどね。

C実践プログラミング 第3版

C実践プログラミング 第3版

いろいろな処理を自分で書くということ

本に出てきていないライブラリ、関数類は使わないしばりで勉強していたので、「こんなことでこんだけ書くの……」みたいなコードを多く書いた。

本文中に「関数はなるべく短く。そう、たとえば2ページ以内ね」という記述があって面食らう。しかし、これだけ書くことがあって、さらにわかりやすいコードにしようとするとそれぐらいの見積りになるのかなとふんわりと思った。

コーディング作法

「関数を短く」もそうなんだけど、著者先生は「わかりやすく書くんだ」「コメントを書くんだ」「略語を使うんじゃあない」など、正統なコーディング作法を叩き込んでくれる。

またモジュール作成においてはヘッダファイルによる必要最小限のインターフェース公開など、the 作法という感じだった。

そしてショートコーディングをひどく憎んでいて、特にポインタの記法に関しては何度も苦言をていしていた。

ポインタ

やってる途中でポインタの話題がタイムラインに流れていた。

ポインタより人間がややこしいので人間が滅びよという感想は変わってないんだけど、ポインタのポインタなんてつかわんだろと思っていたところ、「文字列の配列」てだけですでにポインタポインタが出てくるのでポインタリーディングに励みましょうという感じです。

昔よりコンパイラがかなりよしなにしてくれているので楽

なはず。

CLion

どうしてもコードフォーマッティングと補完が欲しかったので、CLionを使いました。

コンパイルと起動まわりは、gulpで雑にまわしてたのでエディタとしての機能しか使ってないんだけど、補完便利かつ早い。

このままCいじりつづける、もしくはC++も触ってみるなら買うかも(30日無料です。)。

www.jetbrains.com

Cが読めるとコードがそのまま読めるものが多くなってよい

名著とか、アルゴリズムの本を読もうとするとCで記述されていることが結構ある。

もちろんそれを頭の中で知ってるコードになおすことは不可能ではないんだけど、そのままよめれば非常に楽なので、その点において、きっとCをちょっとだけ書けるようになったこと以上に価値があると思っている。

どこに置くかよりどこに置かないかが参考になった。「片づけの解剖図鑑」読んだ。

よみものとしてめっちゃおもしろかった「住まいの解剖図鑑」の続編みたいなテイで売られてたのをかって放置していたのを読んだ。

いや続編にはちがいないんだけど、作者が違うのでテイと。でもおもしろさはあった。

片づけの解剖図鑑

片づけの解剖図鑑

住まいの解剖図鑑

住まいの解剖図鑑

一番最初の話題の「物の一時停止場所としての収納」が興味深かった

人の導線にあって、ちょっとものを置いておきたい、ってのが生じる場所がある。

代表格が玄関で、持っているものを一時的に置く場所を確保するのがいいという。かなりなるほど感があった。

俺の場合それが積み重なって玄関がダンボールの山になってるんだけど、そういう場所をちゃんと設定しておけば、ものが増えてくれば移動するし、ダンボールもたまれば捨てられる。

ただ積みかさねていくだけだと際限がないので、ついつい溜めつづけ、部屋のI/Oに重大な影響が出ていた。

最初の章を読んだ直後に早速玄関の物品を片付け、一時スペースと中期的に滞留するスペースをつくった。これでなんとかなればいい。

だが片付けられるようになる本ではない

前書きでも著者が書いてるけど、具体的な整頓方法などは書いてなくて、ほとんど「住まい」を設計するにあたってこういうことを考えると収納しやすい==住みやすいですよみたいな話です。

知識としてはおもしろいけど、密度はちょっと低くて、すぐに読みおわる。

しかし著者の設計に対する考え方は、顧客の希望の向こう側にある、実際にはどう使われるか、その生活に各種ストレスが生じないか、に焦点をおいた正しい顧客第一主義であり好感度が高い。

キラー・クエスチョン(HPの人が書いた方)読んだ。

書いてる人の視野の広さが720度ぐらいあってアンテナの高さがめちゃ高いってことを差しひいて読まないとだめっぽさがある。

キラー・クエスチョン 常識の壁を超え、イノベーションを生み出す質問のシステム

キラー・クエスチョン 常識の壁を超え、イノベーションを生み出す質問のシステム

イノベーションを起こすというよりか、起こったものがイノベーションとなれる

FIREというメソッドが紹介されていて、手順としては

  1. 範囲の限定、および調査
  2. 調査内容にもどづいた、アイデア出し
  3. 出されたアイデアから、一定の価値観による選定
  4. 段階的な実行

これでようやくプロダクトが誕生し、その中でも(そうは書いてなかった気がするけど)成功したものをイノベーションと読んでる感じがする。

限定、調査 → アイデア出し

出発点として興味の限定が結構なキモっぽい。興味の限定により思考に深さが出て、逆に発想が広がる。確かに縛りプレイ中に出てくる何かは異端であることが多い。

徹底的な調査の重要性は「アイデアのつくり方」という名著でも熱弁されていた。

集まった材料を少し繋ぎかえて価値を出せないか探っていく。そこにはかならず「誰に」リーチするか、しうるかという視点がある。

アイデアのつくり方

アイデアのつくり方

選定、実行

「一定の価値感」とは個人の好き嫌いではなくて、顧客にとっての価値はどうか、市場を変えるかとかそういうやつで、いくつかある「キラークエスチョン」セットの一つ。

ここらへんは組織依存の部分が多そう。

選定においては出された多数のアイデアから一つだけを選ぶ、ということはせず、そこそこ妥当性のあるアイデア複数選び出す。

そこから更なる調査でふるいをかけ、さらにいくつかを実際に製作、いけるとなれば本稼働となる。この段階的な実行はゲートファンディングというシステムで、リスクを抑える働きがある。

組織人としてやっていくための助言が多く含まれている

「このような人がイノベーションを邪魔する、まったくしょーもない人間たちだよな」という慰めではなく、同じ組織の人間としてやっていくためのとても実務的な人間関係のすすめかたなども書いてある。

とにかくいろいろなことに興味を持つ人だった

ここらへんは個人の資質なんだろうけど、とにかく好奇心と実行力がある。

あと割と温和な外見をしてて(食堂での逸話で)「わたしは話しかけられやすいんだ」みたいなことを書いていたけど、そんな感じがした。でも結局は積みかさねた態度の結果でもあるんだろうな。

とにかくいろいろ傾聴して見逃さないようにしなさいとのこと。そうしたい。

エクストリームプログラミング読んだ。

プログラマーがチームとして、人としてどうあるべきかという哲学書みたいな本だった。

もちろん、実践的な手法も数多く記されている。

エクストリームプログラミング

エクストリームプログラミング

XP

XPとは、効果のない技術的/社会的な古い習慣を捨て、効果のある 新しい習慣を選ぶことである。
XPとは、自分が今日やるべきことを十分に理解することである。
XPとは、明日をよりよくしようとすることである。
XPとは、チームのゴールに貢献した自分を評価することである。
XPとは、ソフトウェア開発で人間としての欲求を満たすことである。

リスペクト

チームメンバーは互いにリスペクトせよとある。

これは同時にチームメンバーにリスペクトされるようにふるまえという意味で、それには態度ばかりではなく、技術力も必要になる。

テストを書き、バグをださない。チーム全体としての信頼性を下げない。イテレーションを完了させる。実に多くのことが要求される。

リスペクトさせられる、という視点ではなく、自分はリスペクトに足る人間かと考えると話がわかりやすくなる。

XPでは成長が要求される。

プラクティス

実際的な話では、変化に追従できるようにするための多くの実践すべきプラクティスが紹介されている。

なかでも必ず実践すべきとされている主要プラクティスがあるが、そのほとんどが、開発はチームが行い、チームは人間のあつまりの集まりであり、人間同士の意思疎通の正常化が重要、ということを強く意識したものとなっている。

そこで挙げられているものをただ箇条書きにしただけならば、あまり受けいれられないのではないのかな、と思う。確か同じようなベストプラクティスを他所で見たときに、そんなあほなとおもったおぼえがある。

しかしそのプラクティス解説に至るまでの熱い語りを読み、その意図するところに共感していれば、確かに、という感じになる。なった。

基本的にチーム全体がやっていくための話

しかしストーリーの見極め、ビルドの短縮、こまめなデプロイ、テストファーストなど、個人として実践できる、また当然のように要求される習慣がある。これはすぐにでも一人でもはじめられる。

基本的に熱い

最初の方を読んだ時の印象はこうです。

男には地図が必要だ。……荒野を渡り切る心の中の地図がな。

地図です。

プログラミングの基礎やりおえた。

丸一週間かかった。

最初はこれ終わったらプログラミングに「再入門」したとか言おうと思ってたんだけど、終えて振り返ってみると、俺今になってやっとプログラミングに「入門」したんだっていう感想になった。

プログラミングの基礎 (Computer Science Library)

プログラミングの基礎 (Computer Science Library)

こういうプログラミングの入門の本を、最初から最後まで写経して、全部の問題をちゃんと解いたのってうまれてはじめてだ。

まともなプログラマーなら最初の最初に通りすぎてそうな、挿入ソートとかフィボナッチとかエラトステネスのふるいとか、そういう有名なアルゴリズムを自分で実装してみるってことを、はじめて体験した。

いままでなんとなくで書いていた再帰関数の組み立て方を理解した。

とにかく得るものが多かった。

進めかたとかは、またqiitaにまとめようと思ってる。

この本の題名は圧倒的に正しい

確かにOCamlをつかって学ぶんだが、Ocaml関数型言語という題名や副題がつくより、「プログラミングの基礎」というパリッとした題名がよく似合う。

説得とヤル気の科学読んだ。

おもに自分のヤル気をどうにかしたかった。

説得とヤル気の科学 ―最新心理学研究が解き明かす「その気にさせる」メカニズム

説得とヤル気の科学 ―最新心理学研究が解き明かす「その気にさせる」メカニズム

Susan Weinschenk (スーザン・ワインチェンク)——心理学博士(行動心理学専攻)。心理学と神経科学の最先端研究をビジネスシーンに応用する方法を研究している。ワインチェンク研究所創設者兼所長。大企業、大学や政府機関、NPOコンサルタントを務める。神経科学の最先端研究に精通し、 脳 ( ブレイン ) に関する知識をビジネスシーンや日常生活に応用していることから、クライアントの間では「ブレインレディー」と呼ばれている。米国の心理学専門誌“ Psychology Today ”の電子版で「Brain Wise: Work better, work smarter」と題する連載をもつ。また自身のサイトでもブログ( http://www.theteamw.com/blog )を執筆中。 高校卒業後バージニア工科大学に入学。その後、ノースイースタン大学の心理学部で学士号、ペンシルベニア州立大学で修士号と博士号を取得。

習慣をあらためるには上書きが効く

1作業を終えるごとにtwitterを見てしまって脳みそが旅立ってしまう癖があったんだけど、そんなときって脳のブドウ糖補給のタイミングなので何もしないほうが良さそうということがわかった。

というわけで40kgぐらいの軽めのグリッパーを買って、その間隙に握るようにしたら必要以上に作業から離れず、スッと戻れるようになった。

あと眠くならない副作用もあった。良い。

新しい習慣をはじめるには他の行為にフックすると良い

とくにはじめたい習慣はなかったんだけど、フックするとすんなりと定着するというんで、軽めのダンベルをセットしておいてトイレに立つたびに1セットやるようにした。

習慣づいてるのかどうかはわからないけど、とりあえず立つたびとかトイレのたびとかにはできてるので、いけてると思う。

魅力と態度は大事

他者に対する話としては、外見的魅力が全方向でプラス補正ある話はみもふたもなくてよかった。

態度に関しては、その態度どおりの人物だと見られるというのはそうだとしても、とった態度によって自分もその気になってくるというのがおもしろい話だった。

とにかく会って話す

肉声とか表情のか身振り手振りの効果はとりあえず大きいらしいので、大切な話はそうしましょうという感じ。前項も合わせて。

ネットでみる限りただのうさんくさい人間なのに、なんで?みたいな人は過剰な情熱とか声質とかいろいろなものがあるんだろうなーとふんわり考えた。

プライム効果

直前に見聞きしたことが(全く関係のない事柄でも)判断を左右するのだとか?

それは別にどうでもいいんだけど、そんな無意識レベルで影響があるんだったら毎日読んでるtwitterとかさぞかし影響おおきいんだろうな~と思ったので、とりあえず不快な言葉とか話題に対して入念にフィルタおよびミュートなどした。

心が少し平和になった気がする。

おもしろかった

ライフハック系のエントリではかじった程度の知識で適当に書かれてるような話題を、バックグラウンドある人が箇条書きでちゃんとまとめて書いてるって感じの本だった。

「第4章 物語の力」「第7章 熟達願望」あたりは自己改善に、「第5章 アメとムチ」「第6章 本能」「第8章 心の錯覚」あたりはコンテンツ作成時の判断に使えそうであった。

「第2章 帰属意識」を読んだあとに、ひどい新人研修の話をよんで渋い顔になったのはまた別の話。

初回失業認定日をおえた無職

おれだ。

自己都合退職なので給付までいまから3ヶ月

ながい……月10万ぐらいでなんとか生活していかねば死。

給付を絶対にもらってやるっていう鉄の意志があるわけじゃなくて、もちろん就職はしたいが前職中に感じた素養のなさはできるだけ潰したいので勉強時間がほしい。

ちょっと相談したら求職活動に認められた

初回なので相談受けて帰ってくださいねとのことだったのでちチョロッと顔出したらそれで1回分になった。

初回講習で1、今回で1、12月の認定日までにあと一度求職活動すれば、とりあえず給付は受けられることになるな。

必要以上に慌てずに、でも計画的にやっていこう。

Web API: The Good Parts読んだ。

昨日の朝に読みはじめて今朝読み終えた。

Web API: The Good Parts

Web API: The Good Parts

いまからweb apiやってく人のための地図

やっていくにあたって考えることを避けられない問題が紹介されてる。

どちらかというと砂利レベルの細かさだし、実装の話はほぼないので自分でやっていく段で適宜砂を詰めていく必要があると思う。すでにapi納品をやっていってる人におきましては得るものは少なそうだ。

「入門」詐欺みたいな本が多いけど、この本にこそ「入門」をつけとくべきじゃないか。

デファクトスタンダード

これはこうするのが正しい、これはこうしたらアカン、これは諸説あるけどデファクトスタンダードはこれ、これは色々あって議論がある、などと解説してくれるので親切。

とくに決めかねがちの問題は世論的にも決めかねがちってのがわかってよかった。好きにやっていく。

デファクトスタンダードでもなんでも、各事案で実在のでかいサービスではどのようにやっていってるか例を出してくれてるので、困った時とか納得いかない時にお好みの選択ができるようになってる。

先達に学べるの便利。

セキュリティまわり

Railsつかってると自動的によしなにされてる部分が大きくて、そういうのを忘れてると即死しそうなので再認識できてよかった。

LSUDsとSSKDs

俺はただのユーザーとしての歴史しかないので、web apiといえば誰も彼もが使うことを想定したLerge Set of Unknown Developersでやるもんだと思っててその発想しかなかった。

そうしなければならない!と思ってやたらと親切なapi考えてたりしたんだけど、そうなるとあんじょうやらなければいけないことが多すぎて無理っぽさが出てくる。

しかし俺は別に広範な人々じゃなくて、スマホアプリとか他のwebサービス連系を目論んだやつを作らなくてはならないので、Small Set of Known Developersでやればええやんということがわかった。

考えてみれば当たりまえなんだけど、せまい視点のまちがった正しさで一人でツラくなりがちな俺みたいな技術者はもっと本を読むべきだと思った。

関係ないけどLerge Set of Unknown Developersの場合は「命じさせるな調べさせろ」になってSmall Set of Known Developers「命じさせろ調べさせるな」みたいになるのかなと思った。内部開発者むけweb apiてほぼ扱ったことないのでよくわかんね。

4章 HTTPの仕様を最大限利用する

この章、最初から最後まで得るものが多くて大満足だった。

多分駆け出しマンはこの章読むだけでも価値がある。

indexとメタ情報

配列json返すとセキュリティ的にあれなんでオブジェクトかえそうって話があって、へぇ、じゃあページネーション用のメタ情報そこにいれられていいね。

ってなったんだけど、レートリミットとかはヘッダに入れる一方でページネーションになるといきなりボディに入れるの整合性とれんなってなって結局配列は配列でかえしてメタ情報はヘッダに入れようという自己完結になった。

読書メモ

ありあまる時間にあかせてがんばって文字を読んでたら、ちゃんと本を読める感じがもどってきたので、読んだ本を記録。メモなのでタイトルを題名に入れるのはやめとこう。

解説はドメイン駆動設計・俯瞰編 - Strategic Choiceがわかりやすいので、俺みたいに素養が欠けてる人間はカンニングしながら読むといいと思う。

ドメイン駆動設計

  • はっきりとした要件なんて最初からはっきりすることなんてないけど、互いに通じる言語を定めたコミュニケーションをもって掘り起こしていけ。
  • 掘りあてた要件に柔軟に対応していくために、従来からある疎結合とかリーダブルなコードとかのテクを使って手を入れやすいようにしていけ。
  • リファクタリングはきっとつらいだろうけど歯を食いしばってやっていけ。

というのが基本で、プロジェクト内での問題切り分け、切り分けた問題を統合する方法とかいろいろもろもろ。

3回ぐらい歯を食いしばってたと思う。

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

実践ドメイン駆動設計

ドメイン駆動設計をより具体的な例で、わかりやすい順番で解説した本。

DDDのキモであるところの大域での切り分けかたからはじめて詳細にわけいっていくのでわかりやすい。

一度目にドメイン駆動設計を読んだときはどうもパリッとしなかったんだけど、ドメイン駆動設計 -> 実践ドメイン駆動設計 -> ドメイン駆動設計したら分かった気がするので実践から先に読めばよかった。

関係ないけどエヴァンスさんに比べるとかなり我が強い。

実践ドメイン駆動設計 (Object Oriented Selection)

実践ドメイン駆動設計 (Object Oriented Selection)

こういうの読むと、ついついフレームワークのレールに逆らって(たとえば境界づけられたコンテキストに基づいた)おれおれディレクトリ構造とかしちゃいそうになるけど、フレームワークに逆らうのはツラくなるしやめておけって書いてあった。

いま

アプリケーション アーキテクチャ ガイド 2.0を読んでる。

第2部は「設計の基礎」ってなってるけどほんとは「レイヤー型アーキテクチャの基礎」になってる。レイヤーごとで気をつける点とか、わりと同じことを繰り返し繰り返し言われるけど、逆に言えば丁寧なので、全体的な経験値が足りない俺みたいな人にはおすすめ。

msdn.microsoft.com

タバスコという調味料がすきなんだけど、つかうと胃がいたくなる

加齢を感じる。

2015/08

ことあるごとにマインスイーパーつくってるっぽい

今回のはReactの勉強用につくりました。

http://mine.mmmpa.net/

f:id:mmmpa:20150806154621p:plain

qiita.com

前回は2-3年前で、Flashで右クリックが取れるというのでつくりました。

http://old-mine.mmmpa.net/index.html

f:id:mmmpa:20150806154558p:plain

最古のは8年ぐらい前のやつでとにかくつくることが目的だった気がする。

http://old-mine.mmmpa.net/mine.swf

f:id:mmmpa:20150806154946p:plain

マインスイーパーが好きです。Windowsよ永遠に。

しかし仕事の関係でDebianがメインになった。

いまは無職です。

2015/07

プログラミング問題とその回答に「ん?」と思うところがあったのでメモる

問題はこれ。

qiita.com

自分の回答はこれ

def split_price(price_text)
  unit = (price = price_text.to_s).slice!(/[^0-90-9]*\z/)
  price == '' ? [unit, ''] : [price, unit]
end

みんなたちの回答に「万」「円」が入ってる……

自分的には「値段」があって「それ以外」があるという認識でコードを書いた。

テストを通ることが条件なのだからそれ以外に何も考えることはなくて全く問題はないのだが、「単位」ありきの回答でちょっと驚いた……。

それだけです。日記っぽい。

RailsのControllerからSweetAlert呼び出せるやつ書いた。

最終出社を済ませ、母と妹に詳細を知らせ、本格的に無職になりました。

こんな感じで雑に呼び出せるのでroughという名を関してます。

github.com

def create
  User.create!(user_params)
rescue ActiveRecord::RecordInvalid => e
  swal{ error '不正な値が含まれています', '項目を確認の上、再度送信してください' }
  @user = e.record
end

きっかけ

退職直前に関わってたプロジェクトは本当にだめで、CURDにおいても画面が遷移後何が起こったかわからない感じでした。例えばCの後にindexに戻るなら「まだ」わかるかもしれないのですが、全然別の画面に飛びます(エラーで真っ白とかね)。

想定されるユーザーはPC慣れしていないシニア層だったので、マイクロインタラクションにおけるフィードバックは大げさにでも行うべきだと思って、まずControllerから呼び出せるやつを書きました。せっかくなのでそれをgemにした次第です。

今回のgemでの学び

著名なモジュールではconfigureメソッドが用意されているので、それをこのgemにもいれました。デフォルト値や、プリセットのメソッドを設定するのに使いました。

こうやって標準的に有名なモジュールが普通に備えてる機能を取り入れるときにコードをちゃんと読んで真似すると良いのかもしれない。

2015/06

Viewのテンプレート内で下から上に挿入するgem書いた。

Kaizan

github.com

改ざんです。self.output_buffer.gsub!するとCould not concatenate to the buffer because it is not html safe.って怒られたりHTML素通しだったりするので、そこら辺の対処用。

こんなの何に使うんだっていうと、いま作ってるFormBuilderはHTMLに書き出し済みのerrors以外を抽出できるのがあるので、それをフォームのケツじゃなくて頭に出したかったので、遡って変更できるやつが欲しかった。

で、普通にself.output_buffer.gsub!したら怒られたのでgemになりました。なぜ怒られるのかはこっちに書きました。

qiita.com

小さなgemを作りまくっていて思ったこと

結構汎用性がありそうな機能でもlibとかに入れてそのまま放置して、次に使うときにコピペまんどくせとかコピペ失敗して時間を無駄にするとかやってた気がするので、こうやって簡単にgemにできることを早く知っていればなぁ、という感想。

あと、ちょっとした機能を完全に独立した物と考えて作るようになるので、テストとかしやすい。

会社をやめることにしたのでとりあえず引きつづき小物 gem を書いてる。

ActiveRecordSamplooper

github.com

ActiveRecord#sample したり、アルバムランダム再生みたいに 1 周で 1 回だけしか出てこないランダム抽出をしたりするやつで、自動テストで毎回書いてた奴をまとめた感じです。

前回の Tanemaki は直後に似た感じのはるかにいい奴みつけて即死したので、今回もさがせばあるんだろうなとは思いつつ Rails Plugin 初体験ということで。

どうでもいいけど辞めたあらすじ

前任者が全員出向していなくなった失敗感あふれるテスト皆無でなんか動きもあやしい自社開発プロダクトを押し付けられた仲間たちとチャットで文句を言いあいながらも昼夜休日問わず改修していたら社長がやってきてこれが100%赤字になってるのはお前らの責任だし文句ばかり言わずにちゃんとしろわたしに責任は全くないなどと言われて憤懣やるかたないみたいになった。

ほとほと会社のために働くことがいやになってとりあえず休暇中には自分のことをしましょうと思った。

自己評価が異常に高い俺ははじめての gem に本当に気持ちよくなってしまって仕事に立ちむかう気持ちもしっかり回復して、寝て月曜日 3:30 ぐらいに起きて業務チャットをみたら「会社厳しいし給料遅れるから。あと税金対策とかで合資会社作ってそこに移籍してもらうから 7 月頭から。あと給料下がる」とそんなメッセージがあってテンション上がりまくって 4:56 には退職届が完成していた。

うまれてはじめて gem をつくって公開しました。

Tanemaki といいます

github.com

README.md に書いてあるとおり、seed.rb で CSV に使って大量の初期値を楽にぶちこむためのやつです。業務で 10 keys ほどもつ Hash を Hoge.create に投げてるのをみて、これは 3 度ぐらい seed.rb を読むと正気を失う羽目になる、と思ったのがモチベーションです。

(何度も seed.rb を読む == カラムを追加、が頻繁に起こる可能性というのは理解しがたいかもしれませんが、テーブル設定をはじめ、ビジネスロジックのコントローラーへの漏出は当たり前、機能は不完全な Rails プロジェクトの担当になってちゃんと動かすのが今の仕事ですと言えば、しなければいけないことの種類と量が大体わかっていただけると思います)

やってること

主体のオブジェクトの指定したメソッドに引数を、CSV を元にガンガン投げてるだけです。最初はキーワード引数だけしか使えませんでしたが、これはもしかして FactoryGirl にぶっこむために使えたら素敵なんじゃない?と公開した次の日(今日)に気づいて、普通の引数にも使えるように変更しました。

業務のコードに対して何度か使ってみましたが、とりあえず自分の業務の範囲内では無駄な労力を刈り取れてるみたいで満足しています。

本当のモチベーション

転職活動をするにあたって、ちゃんと見せられるものがないからです。1 から何かアプリケーションを作るには仕事などで時間がないので、こうなれば仕事の問題を解決するやつを完全に余暇でつくって仕事を進めつつ自分のポートフォリオ的なやつにしようと思ったからです。

転職のプラスになるに足る出来ではなくとも、作って、業務で使って、改善すれば、自分のプラスになります。

下に長々と泣き言みたいな愚痴を書きましたが消しました。要は出社前、退社後、休日などに業務にかんする作業をしてると恩着せがましい気持ちが多めになって、多少許せないことが絶対に許せないことになるよね、ってことです。

自社開発案件が開発中に 100% 赤字なのは当然だし、開発を失敗して完成させられずに人員を全取っ替えしてそれが問題になるのはあなたの職員の能力把握、人員配置、経営が不味いからだろ。

最後の代休消化して海遊館に行ってきた。

クラゲです。

kuragewww.flickr.com

自動テスト神の一人です。

capybarawww.flickr.com

github.com

神を見ながら自動テストに励みましょう。

以上です。

代休消化して天王寺動物園に行ってきた。

去年の 9 - 10 月にほぼ休みなしで出ていたせいでたまっていた代休もあとわずか。しかし、代休消化して家でモリモリ業務コード書くというわけのわからないことになってたので、強いて外に出ることにしました。

500 円で 2 時間ぐらい楽しめたのでよかった。あと思ったよりくさくなかった。

P6120033www.flickr.com

P6120142www.flickr.com

P6120065www.flickr.com

P6120130www.flickr.com

15 時頃に行ったせいで猫たちは大体寝てた。

P6120071www.flickr.com

P6120064www.flickr.com

これは檻の外にいた関係のない猫ですが寝てた。

P6120123www.flickr.com

Squeel 導入したら ActiveRecord#select で死んだ

User.select(:first_name, :last_name)

みたいなことをしておった状態に squeel をいれたら

ArgumentError (wrong number of arguments (2 for 0..1))

と言われました。

コードをみたら引数一個しか取らない感じになってる。他はだいたい*argsなのに、なんでselectだけこんな仕打ちを……。

        def select(value = Proc.new)
          if block_given? && Proc === value
            if value.arity > 0 || (Squeel.sane_arity? && value.arity < 0)
              to_a.select {|*block_args| value.call(*block_args)}
            else
              relation = clone
              relation.select_values += Array.wrap(DSL.eval &value)
              relation
            end
          else
            super
          end
        end

どうしても squeel 使いたかったので下にあった where に習って *rest を加えましたが、ひどすぎる仕打ちなので俺がなにか間違っているのかもしれない。

require 'active_record'

module Squeel
  module Adapters
    module ActiveRecord
      module RelationExtensions
        def select(value = Proc.new, *rest)
          if block_given? && Proc === value
            if value.arity > 0 || (Squeel.sane_arity? && value.arity < 0)
              to_a.select {|*block_args| value.call(*block_args)}
            else
              relation = clone
              relation.select_values += Array.wrap(DSL.eval &value)
              relation
            end
          else
            super
          end
        end
      end
    end
  end
end

2015/05

ババ抜き駆動開発

最後に担当した人が負け。

2015/04

Linux + IntelliJ IDEA + 4K 液晶で使ってたらフォントが細すぎて目が潰れた

debian とか ubuntu なんですけど、小さい字でアンチエイリアスをかけてくれないので、リフレッシュレート 30 Mhz のちらつきと相まって本当に見えなくなった。

小さいフォントでもアンチエイリアスしてくれるように設定するには、IDEA のインストール先の bin にある idea.vmoptions もしくは idea64.vmoptions をエディターで開いて

-Dawt.useSystemAAFontSettings=on
-Dswing.aatext=true
-Dsun.java2d.xrender=true

を書き加える。

これで極小フォントでもアンチエイリアスをかけてくれるので、非常に読みやすくなりますし、高 dpi のおかげで、今までと違ってアンチエイリアスも美しく見えます。

MS ゴシックの 9 px を愛用していましたが、4K でついにフォントのアンチエイリアスが美しくなって、アンチエイリアス無しのフォントを使わずに済むようになりましたね。

gulp で C 言語を build on save する。

腸炎で完全にダウンしており、リモートワークしようにも頭がボケボケなので仕事の方は完全に投げて、入門としては良書だよと紹介されていた定本 C を読んでいます。ついでにせっかくだから C も触ろうとしたんですが、はてコンパイルとかどうするんだろう、自動とかあるの?

で、atom とかでのやりかたがわからなくてコマンドラインからのしか見つけられなかったのでやっつけで

var gulp = require("gulp");
var watch = require("gulp-watch");
var changed  = require('gulp-changed');
var shell = require('gulp-shell');
var plumber = require('gulp-plumber');

var srcs = 'c/*.c';

gulp.task('watch', function(){
  gulp.watch(srcs).on('change', function(event) {
    gulp.src(event.path)
    .pipe(plumber({
        errorHandler: function (err) {
            console.log(err);
            this.emit('end');
        }
    })).pipe(shell([
        'gcc <%= file.path %> -o <%= outpath(file.path) %>',
        '<%= outpath(file.path) %>',
        'echo '
      ], {
        templateData: {
          outpath: function (s) {
            return s.replace(/\.c/, '.out')
          }
        }
      }));
  });
});

保存後、起動までやってくれて Hello, World などを楽しみました。 定本 C は循環リストの header でおおーってなってるところです。

腸炎はしんどいです。

2015/03

Ubuntu 14.04 で Nginx upload module 付きでコンパイルできないのとそのアップロードの進捗をとれる Nginx upload progress module の設定で詰まった話

はい。

Ubuntu では --add-module=nginx_upload_module-2.2.0 でのコンパイルが失敗する。

によると nginx-upload-module-2.2m というのが用意されているのでそれを使えばコンパイルできます。

git clone -b 2.2 git://github.com/vkholodkov/nginx-upload-module.git nginx-upload-module-2.2m

progress で何故か recieved と size が常に同じ数値でかえってくる

if ($request_method = POST) {
  # upload module の設定略
}

と method でくくっていたのが原因(たぶん)で、これを外せば正常に数値が取れるようになりました。

関係ないけどローカルでのプログレス動作チェックには upload_limit_rate が便利です。

Heroku のファイル永続化用に Nginx nginx-upload-module でアップローダーを用意する

おはようございます。

先週末にはじめて Heroku にデプロイして以来その便利さのとりこになってとにかく何でもつくってはあげつくってはあげしています。自前のサーバーにあげるとリソースを食いあって残念なことになりますが、これなら多くても安心で文明を感じました。

アップロードしたファイルを Nginx に投げて保存してリネームして名前を返してもらって Heroku 側で保持する

さて、Heroku のネックといえばファイルの永続化ですが、外部ストレージとして Amazon S3 という選択が一般的みたいです。しかし従量課金と聞くと「朝……テレホーダイ……ウッ」となるわたしはファイルのアップロードと配信だけを処理するサーバーを用意したらいいんじゃないのと思いついて試しました。

Heroku から外部の Nginx へ

https://outer-uploader.herokuapp.com/new

おもてむきは Heroku で全部終わってる感じにしたいので、RestClient gem などを使って Nginx になげます。ここらへん、一つ無駄なファイルの転送が生じるのでどうにかなるならどうにかしたいですね。

RestClient::Resource.new(Settings.outer.upload_path, @id, @password).post(upload_file: @file)

Nginx nginx-upload-module

Nginx は受け取ったファイルを upload_store に保存し、元のファイル名、一時ファイルパスなどを upload_pass に指定したなにかに multipart のフォームデータとして渡します。

下記は最初に書いた sinatra に渡した時のやつです。(あとになって sinatra たちあげなくても cgi でいいんじゃないのと 書きなおしました)

Sinatra とか cgi でファイルを移動してその場所を返す

upload_file[filename] とか upload_file[tempfile] が渡されるのでそれをもとにファイル移動、json で返して、

{:result=>"ok", :name=>"9addc65e-ddff-44a3-a875-da778f068ad0.png"}

Heroku の DB で保持して使いたいように使う感じです。

https://outer-uploader.herokuapp.com/

Heroku いい

ファイル永続化がいい感じに解決できるのなら開発にも使っていきたいですね。

弊社は給料遅延が生じる感じのアレなので開発においてもいろいろアレっぽくって共生しまくってる結果レスポンスがアレでアレなので Heroku をうまく使ってそこらへんどうにかしたいですね。

sinatra のやつ

cgi にしたやつ

Rails で mountable なのを作ってる時、test/dummy 以下の FactoryGirl が二重に呼び出されてしまいアレだった。

ネット上で見つけたコードを馬鹿正直にコピペしていたら死んだし俺が悪かったという話です。

いつもの感じで

rails plugin new new_plugin --mountable
cd new_plugin
rails g rspec:install

したままではプラグイン用に生成される factories にパスが通ってないので以下のような感じで rails_helper.rb に加えましょうという記述がありました。

require 'factory_girl_rails'
FactoryGirl.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
FactoryGirl.find_definitions

これ確かに直下の factories にパスが通ってめでたいのですが、すでに通っている test/dummy/spec/factories に factory があった場合、FactoryGirl.find_definitions であらためて読み込まれてしまい死にます。かといってこれをはずすと肝心の直下の factories が反映されないのですが、テストごとに FactoryGirl がちゃんと更新されるように

RSpec.configure do |config|
  # 略

  config.before(:all) do
    FactoryGirl.reload
  end
end

このようなコードがいれてあると実行前に全部読まれるので大丈夫です。

なので、マウンタブルに rspec + FactoryGirl したいときは

require 'factory_girl_rails'
FactoryGirl.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')

だけで ok っぽい、よかったですねというポカミスのお話でした。

2015/02

plantUML の色をいい感じにする。

いい感じ(主観)にします。

結果です。

使用前 -> 使用後です。
f:id:mmmpa:20150225202054p:plainf:id:mmmpa:20150225201533p:plain

フローのソースです

ソースはこんな感じで、!include color.incの color.inc に設定しておいたスタイルを、ケツに<<hoge>>て具合につけると適用できます。

color.inc はこんなかんじ

とくに一部だけを抜き出しましたが、各項目のケツにスタイル名をつけていくかんじです。

plantUML 便利

おきゃくさまに「図でわかりやすく説明してよ」と言われて図を用意しないといけなくなったんですが、追加削除を考えると Cacoo でやるのも結局変更めんどくさいし一緒だよねとテキストであれできるものを探していたらみつかったのが plantUML さん。

色の替えかたがわかってからはなにかと便利につかっています。

追記

色やフォントの設定名称はマニュアルの 10.3 Color に一覧があります。

plantuml.sourceforge.net

http://translate.plantuml.com/ja/PlantUML_Language_Reference_Guide_JA.pdf

API へのアクセス後にコールバックがいる動作のテスト用にアクセス内容を表示するだけの Rails 用意した。

ギョームおつかれさまです。

これで

http://192.168.30.130:3000/access

こう返る。

{"path":"access","method":"get"}

パラメーター

パラメーターを渡すと

http://192.168.30.130:3000/access?param=param_value

こんな感じ。

{"param":"param_value","path":"access","method":"get"}

ネストは Rails のあれで

http://192.168.30.130:3000/access?params[param_a]=a_value&params[param_b]=b_value

このように。

{"params":{"param_a":"a_value","param_b":"b_value"},"path":"access","method":"get"}

ダメ

action と path は消える

http://192.168.30.130:3000/access?action=post&path=session

Rails パゥワでなかったことにされる。

{"path":"access","method":"get"}

method は切り替わる。

get、post、put、patch、delete。

以上です。

Rails で RESTful な API をつくるときは認証、承認をどうしたらいいの?

認証はまぁともかくとして承認の段で

http://example.com/api/sessions/token/resources/show

みたいなのが一般的ですみたいなこと言われて「?」となったのが発端で考えてました。許可証からなんか出てくるわけじゃないだろ的なあれで。

こう?

Rails では簡単にベーシック認証を処理できるので

http://token@example.com/api/resources/show

みたいな感じで

authenticate_or_request_with_http_basic do |user, pass|
  token = user
end

やれるので RESTful 的には美しいんじゃないかと思ったんですが実際のところどうなんだろう。(2 値で認証するなら分けなくていい気がした)

OAuth なんかだと色々アレしてヘッダーに埋め込んでるんだっけ。

土日は API 叩くフォームつくるやつ書いてた。

API つくる仕事が回ってきて今やってるんですけど実際叩く時にさてどうするかとなりまして、ベタに書いたり postman 使ったりでもよかったんですがめちゃ数が増えそうだったので yaml から自動生成する感じで、とりあえず動くまで。

こういうのから

こういうのつくって
f:id:mmmpa:20150222190845p:plain

こんなかんじです。
f:id:mmmpa:20150222190905p:plain

Ubuntu 14.04 LTS + VMware Workstation で VMware Tools による共有フォルダが有効にならなくて困った。

HGFS というやつが動いていないのが原因で、UbuntuVMware の伝統的な問題みたいです。いまは github で patch が手に入るので、わがでソースに手を入れなくても対処できるようです。

【追記】
Debian(jessie)でも同じく共有ができませんでしたが、同じ方法で対処できました。

以下に載っていた手順です。
HGFS not working on Kali Linux | VMware Communities

パッチ入手

git clone https://github.com/rasa/vmware-tools-patches.git

して patches ディレクトリから vmhgfs 以外を削除。

VMtools をコピー

VMware Workstation のメニューから VMtools のインストールを選んでマウントした後、vmware-tools-patches 直下に VMwareTools 解凍前の tar をコピーする。

cd vmware-tools-patches
mount /dev/cdrom /mnt/cdrom
cp /mnt/cdrom/VMwareTools-9.2.4-1398046.tar.gz VMwareTools-9.2.4-1398046.tar.gz

ゴー

untar-and-patch-and-compile.sh を走らせればあとはよしなにしてくれるというあんばいです。

chmod 700 untar-and-patch-and-compile.sh
./untar-and-patch-and-compile.sh

3年ぐらい放置してた問題が解決しました(Samba で凌いでたので遅かったりした)。


マウント位置をかえる

こう。

mount -t vmhgfs .host:/ホスト側の名前 /home/mmmpa/share_dir

学習意欲の足しになるんじゃないかと思って、学習メモのページのソースも github で管理しはじめた

http://mp.mmmpa.net/

みんなも大好き Contributions の緑色がモチベーションの足しになるんじゃないかと思って。

題材は数学と物理で、本当に完全にわかってないんです。もとは FlashActionscript もメシの種であったりして、数式にお世話になる機会は多かったのです。必要に応じてさまざまなところからコピペしておりました。

しかし理解せずに使うのがどうにも後ろめたくて本などを買ってもみましたが、特に学習はすすまず、分厚い本は本棚の安定感を増す重しと化しておりました。

今のメシの種であるところの Rails もまだまだ理解が足らなく勉強中ではございますが、気分転換とかちがった場所の脳みそを使うとかそんな感じで、36 歳独身男性、再挑戦です。

clientside-haml-js を試そうとしたら undefined がどうのと言われて困った。

結論だけいうと underscore.string のバージョンが 3.0.x だと 640 行目の

          contents = (_.str || _).rtrim(line[0]);

で死ぬ。 underscore.string を 2.4.0 に換えれば健康な生活に戻れます。

で clientside-haml-js 動かしてみましょう + Haml をとりあえず体験したいとできたデモがこれです。 毎回全文パースしてるのでたぶんすごくおもい。

http://haml.mmmpa.net/

ところで最近 markdown が大好きで、自分でおったてたブログ http://peragami.mmmpa.net/ の編集は markdown でできるようにしたし、表に出さないメモは https://wri.pe を使っています。便利。

で自習メモを HTML でまとめようと http://vuejs.org/examples/ を参考に markdown をパースするだけのやつを書いたんですが、簡単な記法のままでもうちょっとリッチに書きたいよねということで Haml とあいなりましてまず動作させてみた感じです。

Authlogic の create、save で undefined method true' と言われて困った

各 Input の下にエラーメッセージ出すだけの FormBuilder を書いていた

Rails Plugin のつくりかたを学ぶ一環として書いていた。

これが

f:id:mmmpa:20150207153930j:plain

こうなる

f:id:mmmpa:20150207153940j:plain

仕事で Rails を触りだしてからとにかくフォームをつくる機会が多い。サイト訪問者が使う層はともかくとして、サイト管理画面になるとなんか嫌になるほどフォームをつくる羽目になって半ば死んでいた。

もっと早く Rails Guide をちゃんと読んで FormBuilder のことを知るべきだった。とにかくまずは自分の仕事を楽にする方向でコードを書いて学んでいきたい。

ノーマルの Redmine の Wiki は読みにくいのでユーザー css を書きました。

今年になって新しく入った人(以前、協力会社として来ていたのが縁)がすごくちゃんとした人で、プロジェクトの要件をしっかりまとめてくれるのですが、肝心の Wiki がプレーンすぎてよくわからん感じになってたので書きました。

件の人はちゃんと階層構造で書いてくれるので、これだけで十分読みやすくなりました。幸せです。

メールアドレスは一致してるのに commit しても GitHub Contributions が緑にならなくて困った

ここじゃなくて

f:id:mmmpa:20150205133015p:plain

ここを確認

f:id:mmmpa:20150205133041p:plain

見事にちがうメールアドレスでしたね。

Rails の FormBuilder をテストしようと思ったら new にわたす template をどうすればいいかわからず困った

view 内の form_for から instantiate_builder を経てこういう形で呼びだされます。

builder.new(object_name, object, self, options)

self is 何?かというと view から呼びだされるのでもちろん view ですが、FormBuilder 対象のテストなので view なんてありません。ので用意します。

class TestHelper < ActionView::Base; end

あとはいい感じにインスタンスを作成してメソッドを呼べば HTML が返ってきました。

require 'rails_helper'

class TestHelper < ActionView::Base; end

RSpec.describe WritingErrorFormBuilder::Builder do
  before :each do
    @test_model = build(:test_model)
    @f = WritingErrorFormBuilder::Builder.new(:test_model, @test_model, TestHelper.new, {})
  end

  context 'text_field' do
    it 'not validated no error' do
      expect(@f.text_field(:text)).not_to have_tag('ul.errors.text')
    end

    it 'validated get error' do
      @test_model.valid?
      expect(@f.text_field(:text)).to have_tag('ul.errors.text')
    end
  end
end

SASS 記法でマップの書き方がわからなくて困った。

SCSS ではこうですが、SASS では怒られる。

$black: (
    name: 'black',
    light: #666,
    dark: #000
)

SASS 記法では 1 行でおねがいしますということでした。

$black: (name: 'black', light: #666, dark: #000)

ちまたでは SCSS の記事ばかりですが {} 書きたくないという気持ちがあるので SASS を推していきたい。(: は我慢する)

ところでこういうことをしたいのだけどやりかたがわからない。

eval('$' + 'black')

のでマップを使ってる感じです。

Authlogic の Session.create! を失敗したときに Session のインスタンスを例外オブジェクトに持たせたかった。

要は ActiveRecord::RecordInvalid#record のように使いたいんでモンキーパッチで

module Authlogic
  module Session
    module Existence
      class SessionInvalidError < ::StandardError
        def initialize(session)
          @record = session
          super
        end

        def record
          @record
        end
      end
    end
  end
end

です。

こうしてたのが

 def create
    @user_session = UserSession.new(user_session_params)
    if @user_session.save
      redirect_to entry_index_path
    else
      render :new
    end
  end

こうなった。

  def create
    UserSession.create!(user_session_params)
    redirect_to entry_index_path
  rescue Authlogic::Session::Existence::SessionInvalidError => e
    @user_session = e.record
    render :new
  end

2015/01

途中から attr_encrypted を導入したら配列アクセス演算子でアクセスできなくて困った。

最近はドット演算子で attribute を参照する統一をしているので問題ないのですが、初期に書いたのは配列アクセス演算子 [] を結構使っていて難儀しました。

もともとはworker[:name]つう感じで生で扱われてました。

  attr_encrypted :name, key: 'a secret key', attribute: 'name_encrypted'

これだけだとこうなる

pry(main)> worker.name
=> "お名前"
pry(main)> worker[:name]
=> nil

のでこんな感じのを加えて難をしのぎました。

  def [](index)
    case index
      when :name, 'name'
        self.name
      else
        super
    end
  end


  def []=(index, val)
    case index
      when :name, 'name'
        self.name = val
      else
        super
    end
  end

そういや find_by したいとき用に find_by_name は用意してくれたんだけど find_all_by_name なかったんで where(name_encryped: encrypt_name('探したい名前')) でどうにかこうにか。

ActiveModel の errors.messages に入ってるエラーメッセージが、どの validation でひっかかったかテストで知りたい。

という思いがずっと前からあって下記エントリーのような対応をしていました。

Rails 触りはじめのエントリーでとても懐かしい。

それはいいとして expect(model.errors_on(:attribute)).to include("can't be blank")みたいのは絶対に書きたくなかったのでおおむね満足していましたが、これレイルウェ~イから逸脱しすぎで全くよくないよねって思い至ったので、テスト時のみわかればいいやと思って model 側を触らないような対応をしました。

spec/rails_helper.rb とかその他ふさわしい場所に追加。

module ActiveModel
  module Validations
    def has_error_on(attribute, error)
      self.valid?
      self.errors.messages[attribute].try(:include?, error)
    end
  end


  class Errors
    def generate_message(attribute, type = :invalid, options = {})
      message_table[type] || type
    end


    def message_table
      {
          present: :absence,
          accepted: :acceptance,
          invalid: :format,
          not_a_number: :numericality,
          not_an_integer: :numericality,
          blank: :presence,
      }
    end
  end
end

generate_messageI18n にわたして翻訳とかするんですがそれはテストではいらないので翻訳につかうキーをそのまま返すようにしました。message_table メソッドは validator の名前と関係ないシンボルを渡す(なんでやねん)のがいくつかあったのでそれ戻す用です。

expect(model.has_error_on(:name, :presence)).to be_truthy って感じの書けばテストできておおむね満足しました。

Rails の form_for でエラー出たときに、エラーはちゃんとエラーが出た項目の下に出したい。

エラーは各フォームの近くに表示したいけど form_for の中身の記述を増やしたくはないのでカスタムフォームビルダーを作成する。

Rails Guide でやり方読む

フォームヘルパー — Rails ガイド

どうやってカスタムフォームビルダーをつくる

FormBuilder を継承してタグを書きだす部分を override する。

class LabellingFormBuilder < ActionView::Helpers::FormBuilder
  def text_field(attribute, options={})
    label(attribute) + super
  end
end

どうやってカスタムフォームビルダーをつかう

ブロックはそのままで form_forbuilder を指定するだけ。

使用前

<%= form_for @model do |f| %>
  <%= f.text_field :name %>
<% end %>

使用後

<%= form_for @model, builder: LabellingFormBuilder do |f| %>
  <%= f.text_field :name %>
<% end %>

中身に手を加える必要がない(重要っぽい)。

素の FormBuilder はどんな感じ

つかえる変数

@objectでモデルのインスタンスにアクセスできることがわかる。

# 抜粋
      def initialize(object_name, object, template, options)
        @nested_child_index = {}
        @object_name, @object, @template, @options = object_name, object, template, options
        @default_options = @options ? @options.slice(:index, :namespace) : {}
        if @object_name.to_s.match(/\[\]$/)
          if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
            @auto_index = object.to_param
          else
            raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
          end
        end
        @multipart = nil
        @index = options[:index] || options[:child_index]
      end

各書きだしメソッド

タグを書きだすメソッドは Array から eval されている。今回特に使えそうな項目はないので単純に super のままで OK。

# 抜粋
      class_attribute :field_helpers
      self.field_helpers = [:fields_for, :label, :text_field, :password_field,
                            :hidden_field, :file_field, :text_area, :check_box,
                            :radio_button, :color_field, :search_field,
                            :telephone_field, :phone_field, :date_field,
                            :time_field, :datetime_field, :datetime_local_field,
                            :month_field, :week_field, :url_field, :email_field,
                            :number_field, :range_field]

      (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
        class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
          def #{selector}(method, options = {})  # def text_field(method, options = {})
            @template.send(                      #   @template.send(
              #{selector.inspect},               #     "text_field",
              @object_name,                      #     @object_name,
              method,                            #     method,
              objectify_options(options))        #     objectify_options(options))
          end                                    # end
        RUBY_EVAL
      end

カスタマイズする

まずためす

  class WithErrorFormBuilder < ActionView::Helpers::FormBuilder
    def pick_errors(attribute)
      return nil if @object.nil? || (messages = @object.errors.messages[attribute]).nil?
      lis = messages.collect do |message|
        %{<li>#{message}</li>}
      end.join

      %{<ul class="errors">#{lis}</ul>}.html_safe
    end

    def text_field(attribute, options={})
      return super if options[:no_errors]

      super + pick_errors(attribute)
    end
  end
<%= form_for @model, builder: WithErrorFormBuilder do |f| %>
  <%= f.text_field :name %>
<% end %>

いい感じに書きだされた。

素の FormBuilder にならって一気に定義する感じにする。

  class WithErrorFormBuilder < ActionView::Helpers::FormBuilder
    def pick_error(attribute)
      return nil if @object.nil? || (messages = @object.errors.messages[attribute]).nil?
      lis = messages.collect do |message|
        %{<li>#{message}</li>}
      end.join

      %{<ul class="errors">#{lis}</ul>}.html_safe
    end

    (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
      class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
        def #{selector}(attribute, options = {})
          return super if options[:no_errors]

          super + pick_error(attribute)
        end
      RUBY_EVAL
    end
  end

いけました。

除外されてる check_boxradio_buttonselect と同じ感じに一発で書けたら素敵なのでそうしたいですね。

Rails Engine を RSpec でアレしてたら controller spec で No route matches が出まくって困った

rails plugin new --mountable からつくる Rails プラグイン作成を試しています。

自動テストにはいつも RSpec を使っているので使えるように調整して model spec は問題なく終了したのですが、controller spec でひっかかりました。

generate にまかせた spec で

module ChatPlug
  RSpec.describe RoomsController, :type => :controller do
    describe "GET new" do
      it "returns http success" do
        get :new
        expect(response).to have_http_status(:success)
      end
    end
    # 略
  end
end

rspec すると以下の感じで死亡。

  1) ChatPlug::RoomsController GET new returns http success
     Failure/Error: get :new
     ActionController::UrlGenerationError:
       No route matches {:action=>"new", :controller=>"chat_plug/rooms"}
     # ./spec/controllers/chat_plug/rooms_controller_spec.rb:8:in `block (3 levels) in <module:ChatPlug>'

以下のページに 3 つの解決法が提示されていたので、routes { EngineName::Engine.routes } を追記するやつを選びました。

module ChatPlug
  RSpec.describe RoomsController, :type => :controller do


    # 追記部
    routes { ChatPlug::Engine.routes }


    describe "GET new" do
      it "returns http success" do
        get :new
        expect(response).to have_http_status(:success)
      end
    end
    # 略
  end
end

通りました。

Capybara と Phantomjs で ChatWork をアレしようと思ったら Capybara::Poltergeist::StatusFailError とかいわれてなにも取得できなくて困った。

取得できる HTML は

<html><head></head><body></body></html>

こんなんだし、status_code は nil だしで完全に困っていたが debug: true オプションをつけると題名のようなエラーが出ていたのでグーグル先生に聞くとこれ。

SSL のハンドシェイクでしくってるのでなにも取得できてなかったというわけで下記のようにすることで解決するようです。

Capybara.register_driver :poltergeist do |app|
  Capybara::Poltergeist::Driver.new(app, {
                    js_errors: false,
                    timeout: 1000,
                    debug: true,
                    phantomjs_options: [
                              '--load-images=no',
                              '--ignore-ssl-errors=yes',
                              '--ssl-protocol=any']})
end

解決しました。

2014/12

実家の猫です

f:id:mmmpa:20141222070136j:plain

最近体調が悪いってんであらためて写真撮るのもゲンクソ悪いっつーかなんつーかソレなんですけれどもめずらしくおとなしく撮られてくれました。

Haxe で無名関数を arguments.callee でアレしようと思ったらダメだったので困った

ほぼ一年ぶりぐらいに Flash でなにかつくろうかと思いまして、せっかくだしここは一つ Haxe を使いましょうということになって使っていましたら困りました。

すでに記憶がさだかではないのですが、MOUSE_UP_OUTSIDE 的なものがなくなった関係で、stage にイベントを付加して MOUSE_DOWN での描画をどうにかしておりました。stage のイベントは残留してはいけないのでその都度はがす感じです。

this.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void{
  doHoge();

  stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void{
    undoHoge();
    stage.removeEventListener(MouseEvent.MOUSE_UP, arguments.callee);
  });
});

無名関数に自分自身を呼ばせるためにarguments.calleeを使っていたのですが Haxe でそれ相当の仕方がわからないので以下のようになりました。

this.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):Void{
  doHoge();

  var mouseUp:Dynamic -> Void = function(e:MouseEvent):Void {};
  stage.addEventListener(MouseEvent.MOUSE_UP, mouseUp = function(e:MouseEvent):Void{
    undoHoge();
    stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUp);
  });
});

以下ではダメでした。

// Local variable mouseUp used without being initialized
this.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):Void{
  doHoge();

  var mouseUp:Dynamic -> Void;
  stage.addEventListener(MouseEvent.MOUSE_UP, mouseUp = function(e:MouseEvent):Void{
    undoHoge();
    stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUp);
  });
});

他にもMouseEvent.ROLL_OUTなどが HTML 5 書きだしで取れないなど色々前途多難でアレ。周りにユーザーがいないとこういうときにツライ。

カスタム Validator をつくったら自動的に translate されなかったので困った。

ネット先生いわくこのようにエラーメッセージを追加しなさいとのことだったが、直接 errors に << すると ActiveModel::Errors#normalize_message を通らないのでメッセージが未消化のまま出てくる事態となる。

class StrNumValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors[attribute] << (options[:message] || 'string number error') unless number_only?(value)
  end
#略
end

ちゃんと add をつかうと add メソッド内でよしなにしてくれるので rails ウェーイという感じになれる。

class StrNumValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors.add(attribute, :str_num, options) unless number_only?(value)
  end
#略
end

add はこんなの。

    def add(attribute, message = :invalid, options = {})
      message = normalize_message(attribute, message, options)
      if exception = options[:strict]
        exception = ActiveModel::StrictValidationFailed if exception == true
        raise exception, full_message(attribute, message)
      end

      self[attribute] << message
    end

ちなみにこの validator でやりたかったことは inclusion で事足りました。よかったですね。

TypeScript で名前付き引数っぽいことをしたかった

とにかく記憶力と注意力に問題があるので間違いは出して欲しいしその場でなにが投げられてるかわかりたい。

クラスでこう書くと

class ValidatorCallback {
    public valid: Function;
    public invalid: Function;

    constructor(params: { valid: Function; invalid: Function }) {
        this.valid = params.valid;
        this.invalid = params.invalid;
    }
}

こう書くときにサジェストがちゃんと出て幸せになれた。

new ValidatorCallback({
    valid: () => {
        $('#content_error').html('');
    },
    invalid: (errors: string[]) => {
        $('#content_error').html(errors.join('<br>'));
    }
});

undefined を許容するにはこう。

class ValidatorCallback {
    public valid: Function;
    public invalid: Function;

    constructor(params: { valid?: Function; invalid?: Function }) {
        this.valid = params.valid;
        this.invalid = params.invalid;
    }
}

あさいちメモ移動

http://peragami.mmmpa.net/

2014/11

Ruby on Rails の Routes を Controller スペックで使える感じに内部で取得する。

ログイン時、非ログイン時の振り分けなんかは全自動総当たりテストしたいので。

routes = Rails.application.routes.routes.map do |route|
  path = route.path.spec.to_s.gsub(/\(\.:format\)/, "")
  params = path.scan(/:([a-zA-Z_]+)/).flatten.collect(&:to_sym)
  verb = %W{ GET POST PUT PATCH DELETE }.grep(route.verb).first.downcase.to_sym
  action = route.defaults[:action]
  controller = route.defaults[:controller]
  {
      #path: path,
      verb: verb,
      action: action && action.to_sym,
      controller: controller,
      params: params
  }
end

あとはパスなりコントローラーの名前なり適当に振って status code とかみてアレしてゴー。

RouteInspector ってやつを使う感じのはうまくいかなかった。

洗濯に行って帰ってきました

f:id:mmmpa:20141124082511j:plain

f:id:mmmpa:20141124082516j:plain

酒買いに行って帰ってきました

f:id:mmmpa:20141123181409j:plain

f:id:mmmpa:20141123181413j:plain

f:id:mmmpa:20141123181416j:plain

Capybara で feature から 2 つ以上の scenario を回すと Capybara::Poltergeist::DeadClient で死ぬ

用もないのに Poltergeist などで全ページスクリーンショット撮るのが半ば趣味です。

さて今日も今日とて楽しくフィーチャスペックを書いておりましたところ死にました。

Failure/Error: auth_manager(page, kill: true)
Capybara::Poltergeist::DeadClient:
PhantomJS client died while processing 略

よくわかりませんが 2 つ目のシナリオの最初で必ず死ぬので

before :each do
  page.driver.browser.clear_network_traffic
end

としたらとりあえず回るようにはなりました。原因とかはわかりません。

2014/09

validates いじってないのに valid? で false になるようになった

作業をすすめていると突然テストが通らなくなって、valid? が false 返すのはいいけど errors みてもなにも message ないし本当にどうしようかと思いました。save(validate: false) でその場をしのいて、結局あとで一つずつコメントアウトしたりして、これ。

Railsでbefore_validationコールバックを使う時の落とし穴 - ぼくたち宇宙人

つまりこれでした。save 時点で is_hoge? 用の boolean を保存しておこうとそういう処理を before_validation にいれとったわけなんですが、わるいことにその代入を一番最後に行っていたので false が代入されると validates は全て通ってるにもかかわらず、valid? は false になっていました。

before_validation :set_is_hoge

def set_is_hoge
  self[:is_hoge] = self[:a] == self[:b] # false だと validation が中断されて死
end

原因をみつけないとぐぐろうにもなにをぐぐっていいかわからない感じで難儀しました。

2014/08

弊社、アジャイルっぽいことをしたいらしい。

テスト一枚も書かない文化の弊社でそれやると、サイクル毎に臨死体験が楽しめると思う。

おれも頭数で聴衆として参加した Ruby ビジネスフォーラムで、講師 matz さんがアジャイル推ししていたのをきいて思いついてしまったのだろうか。同時に、テストあってこそアジャイルでまわせる、すなわちテスト文化がある Ruby 最高と言っていたのだが(たぶん)、そういうことはおろか弊社の現状もわかっていないらしい。

おれはおれのプロジェクトではテスト書きまくってるけど、ちいさな会社だからもちろん他のプロジェクトにもかかわることになるわけで、そういう時にテストがないとおれまで死ぬのでちょっと勘弁してほしい的な雰囲気が漂ってきている。

2014/07

滅裂というツイッタークライアントをつくりました。

http://m2r2.ore-omae.com/

ツイートの単語を入れかえて文意が変わる感じのやつです。
Twitter の連係保持とかアカウント確認テストの試しように作りました。

ツイートの単語を入れかえて文意が変わる感じのやつです。

単語のやつを入れかえてツイートが変わる文意の感じです。

みたいになります。

vue.js + typescript をためす。

ここ丸一ヶ月ほど完全に rails おじさん化していたのでこれではいかんと vue.js をさわりなおすことに。とりあえず簡単なものをつくって雰囲気をつかんだりしたい。

vue.js + typescript 神経衰弱

データバインディングやはり最高だろうという感じで早く ie8 がこの世から去りますようにという感想しかなかった。

ところで vuew.js へのモデルの持たせ方なんだけど、data に自作クラスを渡すと容赦なく中身を書き換えられてしまう上に prototype のメソッドも吹き飛ぶ(Vue.js が data に渡した値を激しく書き換える件について)のでメソッドを持たせたい場合は

class Card{
    constructor(){
    }

    open(){
        // 処理
    }
}

ではなく

class Card{
    constructor(){
    }

    open = () => {
        // 処理
    }
}

のようにあくまで変数として格納する必要があった。ここらへん回避方法ないのかしら。確か nofollow 的な prefix でなんとかなったような記憶もあるんだけどはてさて?

ちなみに data に直接指定せずにネストした形にしても vue.js さんはちゃんと追っかけて監視用に書き換えてくれるので無駄です。参照が及ぶ範囲に独自クラスを入れるのはやめましょう。

2014/06

Ruby on Rails で Stylus をつかう。あと、nib も。

Stylus は簡潔な記法で CSS をナニできるアレです。HTML コーディングが主だった前職でもウィンド~ズ + Sublime Text で使ってました。いまも使ってますが。
http://learnboost.github.io/stylus/

最近の Rails では stylus_rails じゃなくて stylus を使います。

gem 'stylus'

忘れちゃいけないのが node と Stylus のインストールです。

npm install -g stylus
npm install -g nib

Stylus のインストール忘れててなんで動かないんだろうとか首をひねりまくっていた。あと nib 使うなら nib。

initializers に適当なファイルをつくって Stylus の設定を書く。

require 'stylus'
Stylus.compress = true


#プラグインのたぐいを指定する
Stylus.use '/usr/local/node-1.0/lib/node_modules/nib/lib/nib.js'

Stylus のファイル名は「application.css.styl」などとする。
プラグインのたぐいは initializers で指定した上で、.styl 上でもインポートする。

@import 'nib'
global-reset()

body
    background #eee
    font-size 18px

幸せな感じになりました。

html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-weight:inherit;font-style:inherit;font-family:inherit;font-size:100%;vertical-align:baseline}body{line-height:1;color:#000;background:#fff}ol,ul{list-style:none}table{border-collapse:separate;border-spacing:0;vertical-align:middle}caption,th,td{text-align:left;font-weight:normal;vertical-align:middle}a img{border:none}body{background:#eee;font-size:18px}

Rails でアクションの再利用してたら render :hoge and return で DoubleRenderError に足つっこんだ。

任意の view を描画してアクションを中断するには

render :hoge and return

などとするわけですが、これは render がtrueを返すことを期待したandの使用法です。今回は new -> create の流れのなかでエラーを出すために使っていたのですが、エラー表示や hidden の埋め込みのためにrender :newではなくnew()しておりました。

普通に使う分にはそれでよかったのですが、ある条件下で new アクションからまた違う view を render して早期リターンしておりまして、そこではまりました。こんなコードなんです。

def new
  if @model.valid?
    render :verify and return
  end
end

def create
  if params
    @model = Model.new(user_params)
    new and return
  end

  render :new #上で中断されずに死
end

そうです、アホみたいにコピペで and return しておったおかげで new アクションが true を返さず、create アクション側の return が実行されずにしかも create アクションで明示的に render しとるもんですから DoubleRenderError 死んでおったわけですね。

ちゃんと頭使わないと死ぬなぁという話でした。

2014/05

RSpec 3 にアップデートするにあたっての章が「RSpec による Rails テスト入門」に追加されたらしいので早速ためした。

読むと RSpec 2.99 beta2 を導入すると RSpec 3 でなくなったりする API を使ってたりすると警告を出してくれるらしいので早速ためしました。

うまれてはじめてテスト書き出いてから 1 週間とか 2 週間なので、変更するポイントはほとんどなくて、指示に従ってこういうのを

expect(collection_owner).to have(0).errors_on(:name)

こんなふうに書きなおしたり、

expect(collection_owner.errors_on(:name).size).to eq(0)

be_true be_falseをそれぞれbe_truthy be_falseyにかえるだけで警告はでなくなりました。親切 and 簡単ですね。

移行にあたっての確認方法やくわしい手順は Everyday Rails - RSpecによるRailsテスト入門 を買えばよくわかっていいと思いますので買いましょう。

会社においてあるサーバーに Phantomjs をインストールしたが起動しなかった。

マッシーン曰く

-bash: /opt/phantomjs/bin/phantomjs: cannot execute binary file

なので、ええ依存関係とかちがうのかしら、ubuntu 確か同じバーションでしょ?といろいろな角度に頭をひねりましたが 32bit サーバーに 64bit 用のバイナリをインストールしていただけでした。

そりゃ起動せんわ。

というかバーチャルマシンとサーバーでちがうのインストールしてる俺は一体なにを考えているのかな。まったくわからない。

今日も Capybara に殺された。

CSSposition:relativeでプッシュボタン的な動作をさせているaタグに対しclick_linkしたところなぜか効かない。p click_linkで座標をとってキャプチャで計ってみたところ、クリック位置はaタグのどまんなかにあたっている。

CSS を無効にしたところclick_linkが無事発動したので、一つずつ数値をかえていったところ、topでの上下移動の幅が大きすぎたらしくtop:-10pxからtop:-7pxに変更したところ動作した。

aの高さの問題もあるのだろうがびっくりした。

Rails で MeCab と KEN_ALL.CSV などをもちいて住所の抽出を試みた。

http://13.ore-omae.com
こんな感じの精度が悪いアレです。

形態素解析(なんて言葉も把握してなかったときですけど)を試みようとして、これあきらかに個人では無理やんとあきらめていたところ MeCab というオープンソース形態素解析エンジンを発見したのでさっくりとコンパイルしてコマンドを叩いたところ、おどろきの性能に文明を感じました。

http://mecab.googlecode.com/svn/trunk/mecab/doc/index.html

というわけで http://13.ore-omae.comスクレイピングした本文をもとに住所抽出を試みました。みなさんおなじみの KEN_ALL.CSV を全て入れたデータベースを用意して、MeCab でとりだした地名を投げるとここじゃねーのという候補を返すサーバーを非公開ポートでたてて note 13 から通信する仕組みです。

この手の Rails to Rails の通信てこういう web API 的なアクセスしかないんですかねぇ。rake タスクで呼びだそうとしたけどまったくうまくいかなかった。

抽出は都道府県でザクッと絞り込んだ後に、出現回数をカウントして簡単な順位つけをして上位のやつを表示するって感じですが、まぁ精度が悪い。MeCab が出してくれる地名が市や町を省いたものなので、これを抽出後に本文参照して補ってやるといいのかもしれない。

今日もテストを書いてて Capybara に殺されそうになってる。

コントローラーのテストの章が終わったのでフィーチャテストに進んだのですが、しょっぱなから Capybara 内でおこなった Model#create が visit 先のビューに反映されない事例に遭遇して死にそうになった。

Capybara でもコントローラーもビューも全部 Rails.env == 'test'なのにビューだけからっぽでなにが起こってるかさっぱりわからない。

泣きそうになりながら検索をすすめているとActiveRecord::Base.establish_connectionがどうのとかいう記事がでてきて、それでは解決しなかったんだけど connection で毎日 RSpec 内を検索するとデータベースクリーナー使うならこのモンキーパッチ当てなきゃ共有されなくていろいろとダメよという記述があった。

class ActiveRecord::Base
    mattr_accessor :shared_connection
    @@shared_connection = nil


    def self.connection
        @@shared_connection || retrieve_connection
    end
end
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection

データベースクリーナーはつかってなかったものの共有されてない症状っぽいのでためしにモンキーパッチングしたら無事に Capybara 内でのモデル追加がビューに反映された。

なんでこんな挙動になるのかさっぱりわからないしひっかかってる人もいないし、本当に一人で地獄に迷い込んだっぽくなってつらかった。

UbuntuMySQL 14.14、Capybara 2.2.2、Rails 4.0.4、Ruby 2.1.1 などの構成です。

テストが通るたびにレッドライト!グリーンライト!ってさけんでるんだけど RSpec でわからないことがある。

Ruby を全面に押し出してる会社に転職してはや半年ほど、Ruby というか Rails 力が足りないので他の職務をこなしておりますが、そればっかりやってもいられないということで最近は空き時間をみて Rails をさわっています。

いちおう慣れてきた感じはするので RubyRails といえばテストだろつうことで「Everyday Rails - RSpecによるRailsテスト入門」読みながらテストをもりもり書いています(書き方が正しいかわからないのでテストファーストとはいかない)がわからないことがある。

validates :fullname,
        presence: {message: 'お名前を入力してください'},
        length:   {minimum: 2, too_short: 'お名前が短すぎます'}

でこういう validates で二重に条件を設定した場合のエラーをみるときに

user = User.new(fullname: '')
expect(user).to have(1).errors_on(:fullname)

user = User.new(fullname: '姓名')
expect(user).to have(0).errors_on(:fullname)

これはいいんですが

user = User.new
expect(user).to have(2).errors_on(:fullname)

have(2) になっちゃうじゃないですか。この数値、手計算してかえるの……。

have_at_least つかやあいいじゃねぇかという話もあるんですけど、have(1) もそうなんですけど、エラーの内容を特定して指し示してるわけではないのでどうもきもちがわるい。

message 比較以外で特定する方法があったら知りたいけど検索力が足りないので見つけられなかった。仕方がないので

validates :fullname,
        presence: {message: TaggedString.new(:presence, 'お名前を入力してください')},
        length:   {minimum: 2, too_short: TaggedString.new(:length, 'お名前が短すぎます')}

タグをもった TaggedString という文字列クラスをでっちあげて presence エラーと length エラーを区別できるようにしてとりあえず難を逃れたわけですが、もっとなんか普通にスマートな方法がどこかにあると思う。

「Everyday Rails - RSpecによるRailsテスト入門」まだ全部読めてないのでどこかに出てくるかな。

なお付与したタグがI18n.translateにぶちころされたので予期せぬモンキーパッチ童貞喪失にてタグ消えないように対処した。(そういえば最初は message に連想配列をいれて対処していたところ、手動で errors.add する場合はいいんだが、ビルトインの validator 使うと自動でI18n.translate通すのでそこで死んだ。)

これも TaggedString 側でなんとかできそうなので要勉強。

そういえば参考にしようと会社の Rails 業務ソースを読んだ

ずらっとならぶ *_spec.rb を前にしてわくわくしながらひらいたら、scaffold かなにかで自動生成したものらしくて、特になにも書きくわえられていなかった。

横にずらっと並んでいる 1KB がまぶしい。

Rails の Rake からツイートさせるようにしました。

http://13.ore-omae.com の追加内容を http://twitter.com/mmmpa にツイートします。

必要な gem は twitter だけなので Gemfile に書いて

gem 'twitter'

あとは twitter.rake かなにかを用意して引数でツイ~トできるようにする。

namespace :twitter do
  task :tweet, ['message'] do |t, args|
    require 'twitter'

    YOUR_CONSUMER_KEY = ''
    YOUR_CONSUMER_SECRET = ''
    YOUR_OAUTH_TOKEN = ''
    YOUR_OAUTH_TOKEN_SECRET = ''

    client = Twitter::REST::Client.new do |config|
      config.consumer_key = YOUR_CONSUMER_KEY
      config.consumer_secret = YOUR_CONSUMER_SECRET
      config.oauth_token = YOUR_OAUTH_TOKEN
      config.oauth_token_secret = YOUR_OAUTH_TOKEN_SECRET
    end

    client.update(args.message)
  end

  task :tweet_test do
    Rake::Task['twitter:tweet'].invoke('from rake')
  end
end

http://13.ore-omae.comスクレイピング用の rake から呼び出してツイート。更新件数が多い場合の連続投稿は 3 件までとしました。

定期的に動くなにかを Rails でつくりました。

http://13.ore-omae.com

twitter でロムしてる優れ技術者のなかには定期的に動く何かを飼っている人がおり、ワナビーとしてはやはり形から真似しなければなるまいと、とりあえずスクレイパーのたぐいをつくることにしました。

定期実行には Whenever

Rails の定期実行には Whenever という gem を使いました。

#config/schedule.rb 生成
wheneverize
#書きこまれる内容の確認
whenever
#crontab を更新する
whenever --update-crontab
#crontab から削除する
whenever --clear-crontab

Whenever や Rails 自体が動き続けるのではなく、config/schedule.rb に書いた内容にしたがって Whenever が実行内容をコーディネートして crontab によきにはからってくれるという感じです。

HTML 解析には Nokogiri

jQuery でもおなじみの CSS セレクターが使えます。

doc.css('ul.mdMain li a')

open-url で文字化け

charset 拾って parse して~とやってるとどうやっても文字化けしたので、バイナリから kconv でアレという感じにすると解決しました。

require 'open-uri'
require 'kconv'
page_html = open(page_url, "r:binary").read.toutf8

summerfruit256.vim で使われてる色。

Vim を使うのはずいぶん昔にあきらめて IntelliJ を使っているわけですが、SummmerFruit256 が使いたかったのでとりあえず使われてる色をピックアップ。

000000
002300
0086d2
0086f7
008800
008ffd
00bdec
22a21f
2b6ba2
2f5f49
3687a2
3b916a
3c78a2
438ec3
43c464
4aa04a
51b069
66cd66
70796b
708090
800000
92cd35
9bd4a9
aaaaaa
b7dce8
bf0945
c0d9eb
c3daea
cb2f27
d40000
dbf3cd
dd7700
e50808
ee0000
eeeeee
f0e68c
fb660a
fbf4c7
fd8900
ff0007
ff0086
ffae00
ffffff

ファイルアップロード機能をつけた。

先日 Rails で作成したブログにファイルアップロード機能をつけた。
おもに画像をアップロードして記事内に貼りつけることを目的としているので、画像の情報を取得する必要があった。

https://github.com/sdsykes/fastimage が使えるっぽいので使った。

FastImage.size('/www/img/hotei.jpg')
=> [853, 480]

これをタグっぽい感じにして記事作成画面の textarea に放りこむ感じで実装。 楽に画像を貼りこめるようになった。

http://ore-omae.com/s/%E7%99%BA%E6%83%B3%E3%81%8B%E3%82%89%E5%AE%9F%E7%8F%BE%E3%81%BE%E3%81%A7%E3%80%82

2014/04

Rails の練習に最小限の機能しかもたないブログをつくりました。

http://ore-omae.com/

転職先ではいまのところ ActionscriptJavascript を書く生活ですが、他のチームでは Rails が猛威をふるっており Javascript との連携でもこの先使えるし使えるようにならないとねってことで自習をすすめています。

ということで以下のように勉強していました。

  1. http://railstutorial.jp/
    有名チュートリアルを開始。
    どこが Rails 部分かわからないので、とにかくくまなく読んでいる内にはげしく飽きてしばらく中断。
  2. http://dotinstall.com/lessons/basic_rails_v2
    おなじみドットインストール。
    動画と同じ速度で手を動かすことを念頭にとにかく全動画を連続で見ました。
    おかげで一通りの機能を俯瞰。
  3. http://railstutorial.jp/
    もどってきた有名チュートリアル
    Bootstrap とかそういう部分はとばして Rspec はそこそこにして一通りやる。
  4. http://railsgirls.jp/devise/
    会社でつかってた認証機能が Devise のものだったのでそれを使い方を調べる。
    今日からおれも Rails Girls。
  5. http://yss44.hatenablog.com/entry/2014/01/06/135734
    つかっているサーバーが Nginx だったのでそこらへんであたりをつけて、unicorn の使い方をしらべた。

3 が終わったあたりで会社の EC サイトの素材を利用して Rails で再構築してみたりして、とりあえず作成から公開まで体験してみようということでブログをつくりました。

4、5 は泥縄式に作りはじめてからしらべました。

Ubuntu + rbenv + ruby-build 用コピペ

git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
source ~/.bashrc

2014/03

AngularJS + Jasmine でテストに入門する準備。

とりあえず動かしたいなと思って AngularJS を動かしている HTML に describe などをコピペすると Uncaught ReferenceError: describe is not defined などと言われる。

先生方におたずねすると angular-mocks.js を読み込みなさいと言われるが、それでもだめ。Jasmine 本体を別途ちゃんと読み込まないとダメなのでした。

Jasmine は AngularJS とちがって勝手に動き出さないので、サンプルから起動部分をコピペしてとりあえず動かす。

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="/css/jasmine.css">
<script src="/js/jasmine.js"></script>
<script src="/js/jasmine-html.js"></script>
<script src="/js/angular.js"></script>
<script src="/js/angular-mocks.js"></script>
<script>
	angular.module('app', [])
		.factory('model', function () {
			var store = [];
			return {
				add:function (thing) {
					store.push(thing);
				},
				getAdded:function () {
					return store;
				}
			};
		});

describe('test', function () { var model; beforeEach(module('app')); beforeEach(inject(function (model) { model = model; })); it('add and store', function () { var thing = {a : 'b'}; model.add(thing); expect(model.getAdded()) .toContain(thing); }); });

(function() { var jasmineEnv = jasmine.getEnv(); jasmineEnv.updateInterval = 1000;

var htmlReporter = new jasmine.HtmlReporter();

jasmineEnv.addReporter(htmlReporter);

jasmineEnv.specFilter = function(spec) { return htmlReporter.specFilter(spec); };

var currentWindowOnload = window.onload;

window.onload = function() { if (currentWindowOnload) { currentWindowOnload(); } execJasmine(); };

function execJasmine() { jasmineEnv.execute(); }

})(); </script> </head> <body ng-app> </body> </html>

CentOS 用コピペメモ

社で使ってるからとりあえず把握するために vm で動かすだけ用の適当設定。

tmux

yum install wget gcc make ncurses ncurses-devel
wget https://github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
tar xvzf libevent-2.0.21-stable.tar.gz
cd libevent-2.0.21-stable
./configure
make
make install
echo /usr/local/lib > /etc/ld.so.conf.d/libevent.conf
ldconfig
cd ../
wget http://downloads.sourceforge.net/tmux/tmux-1.8.tar.gz
tar xvzf tmux-1.8.tar.gz
cd tmux-1.8
./configure
make
make install

samba

[global]
unix charset = UTF-8
dos charset = CP932

hosts allow = 127. 192.168.

security = share oplocks = no

[www] path = /var/www writable = yes guest ok = yes guest only = yes create mode = 0777 directory mode = 0777 share modes = yes oplocks = no

SELinuxsamba 用にアレする。

setsebool -P samba_enable_home_dirs true
setsebool -P samba_export_all_rw true

iptables

-A INPUT -m state --state NEW -m tcp -p tcp --dport 139 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 445 -j ACCEPT
-A INPUT -m state --state NEW -m udp -p udp --dport 137 -j ACCEPT
-A INPUT -m state --state NEW -m udp -p udp --dport 138 -j ACCEPT
-A INPUT -p tcp --dport 80 -j ACCEPT

あとはいつも通り適当に必要なものを。

CentOS で普通に yum install した Apache(httpd) と php で Wordpress を動かすと、Wordbooker で Facebook ページが取得できない。

とりあえず、こうやったら正常動作しました。

yum install php-mbstring
apachectl restart

Wordbooker は Wordpress の投稿などを Facebook に同時投稿するためのプラグインです。
アプリの許可を与えれば簡単に投稿できるというふれこみで、実際、個人のページへの投稿は簡単にできました。しかし、所有している Facebook ページが認識されない。

f:id:mmmpa:20140330143134p:plain
結果として、このように下部のセレクターがブランクになってしまう。もちろん、上部セレクターにも Facebook ページは含まれていない。

日本語タイトルが問題なのか、はたまた他のなにかが原因なのかそこまで絞り込めませんでしたが、php-mbstring をインストールして apache を再起動、Wordbooker の設定からセッションを一度リセットして再度許可を与えると表示されるようになりました。

Facebook ページへの投稿もできました。

過労

長期的にクソ忙しいけどめっちゃ充実してて全く疲れ感じてなかったときに寝失禁かまして、後日「ああ、やばいぐらい疲れてたんだな」と思ったことがある。

その時点ではなにも思わなかったので、けっこうやばかった気がする。

VMWare と Genymotion を同居させると VMWare からブリッジ接続できなくなる。

window + VMWare Workstation 9 です。ネットワークが複数個ある場合、うまく接続できない問題があるようです。

ネットワークと共有センターをコントロールパネルから開き、ブリッジ接続に使用しているネットワーク以外(今回の場合だと、Genymotion が使っている VirtualBox Host-Only Network)のプロパティから VMWare Bridge Protcolのチェックマークをはずすことにより解決しました。

2014/02

Android でカスタムしたアイテムを表示する ListView 用の ArrayAdapter さわったときのメモ。

ListView は任意の View をよんでそれをアイテムとして表示できる。自作の layout を View にする場合はもちろんわたされるデータもデータをわたされる View もちがうので、データの受けわたしをするメソッドもふくめて調整しなくてはならない。

	public View getView(final int position, View item_view, ViewGroup parent) {
		final PaperQListDat item_dat = this.getItem(position);
		if (item_dat == null) {
			return null;
		}
	<span class="synStatement">if</span> (item_view == <span class="synConstant">null</span>) {
		item_view = inflater.inflate(R.layout.paper_q_list, <span class="synConstant">null</span>);
	}

	TextView tv_q = (TextView) item_view.findViewById(R.id.textViewQ);
	TextView tv_a = (TextView) item_view.findViewById(R.id.textViewA);
	tv_q.setText(item_dat.getQ());
	tv_a.setText(item_dat.getA());

	<span class="synStatement">return</span> item_view;
}

で、そのメソッドの引数にアイテム用の View があるんだけど、これは前もって設定できるとかではなくて、 すでに作成されたアイテム用の View の再利用につかわれる。なので inflater での layout からの View 取得は省略できません。

POST データが切り捨てられちゃうのでなんでかなーと思ったら Ruby とか Rails の制限じゃなくてブラウザごとの違いだった。

Safari 系だと 1 value につき 524288 文字っぽい。DOM で編集した長いテキストを HTML のまま保存しようとしたらひっかかった。value をいくつか用意したら問題なく送信できましたけど、可変長なデータを扱うしなんだかなー。

コマッタナー。

追記 : hidden の input にいれたら普通にいけましたね。

Ruby の GServer で message-body がうまくとれなかった

勉強のために Ruby でブログでもつくってみるやんけ、とまず Gserver で簡易サーバーをたてて POST を取得しようとしたらいきなりつまずいた。

require 'gserver'

class HttpServer < GServer def initialize(port=8080, host="127.0.0.1", *args) super(port, host, *args) end

    <span class="synPreProc">def</span> <span class="synIdentifier">serve</span>(io)
            puts <span class="synSpecial">'</span><span class="synConstant">serve</span><span class="synSpecial">'</span>
            <span class="synStatement">while</span> (line=io.gets)
                    puts line
            <span class="synStatement">end</span>
            puts <span class="synSpecial">'</span><span class="synConstant">serve end</span><span class="synSpecial">'</span>
    <span class="synPreProc">end</span>

end

server = HttpServer.new server.start loop do sleep 1000 end

こうやってアクセスすると、message-body の一行で長大な時間がかかり、serve end 出力までおかしな時間がかかっていた。

最後は nil を返して平和におわるはずなんだけど、nil を返す前に時間がかかっている感じ?原因がよくわからないし、エラーも出ないし、ぐぐってもそもそも GServer を使っているひとがほとんどいないので答えが見つけられない。

head = {}
io.each do |s|
	if (s.length == 2)
		query = {}
		io.read(head['Content-Length'].to_i).split('&').each do |param|
			key, value = param.split('=')
			query[key] = value
		end
		return query
	end
	key, value = s.split(':')
	head[key] = value
end

もう便利メソッドとか見つけられないしよくわからないので、Content-Length をとってヘッダフィールドのおわりをしめす CRLF から数えて文字をとることにしたら、とりあえずいけました。

とにかく message-body のケツを読もうとすると変な時間のかかりかたをするんですが、どういう理由なのかさっぱりわからないですね。

2014/01

SQL を勉強するのに便利でした。

Windows 7 で勉強しています。

本は「SQL ゼロからはじめるデータベース操作」を買いました。

RDBMS サーバーには MySQL を使い、クライアントには「MySQL Workbench」を使用しています。

最初は Teraterm でぽちぽちやってたのですが、MySQL Workbench は GUI で逐一状況を確認できるし、SQL のコード補完もしてくれるし、素人にとってもやさしく、便利です。

再就職していました。

いまは手持ちの武器でお仕事している状態ですが、状況を考えるとこれだけをやっていては社内での仕事がなくなりキビシイことになるので、社が扱ってる仕事に関連する武器を手に入れねばなりません。

さいわい、前の会社とちがって仕事はあっちから来るような盛況ぶり。かつ多岐にわたっているのでとにかくその中から先があるもの + 興味あるものをえらんでさっさとまじめに勉強します。

いまはとりあえず RubySQL を並行して勉強しています。

2013/11

監視カメラの楽しさ。

することがないのでマンションの廊下とバルコニーから見える道路を web カメラで眺める生活をしているんだけど、特にかわった風景ではないのに、ぼーっとみててもなにか楽しい。

自分が不在でありながら視点だけそこにあるっていうのが、かなりよい。

できれば賑やかな通りに面した家に引っ越して、人々や車を web カメラで眺めていたいんだけど、そういう場所はなかなか空かないし価格も高い。

手が届かない。

社外ニートになった。

いわゆる失職。

2013/09

遺産相続が進行している。

祖父より先に親父が死んだので、代襲相続人となった。親父には兄弟、俺からみれば叔父である人物がいて、彼と分割することになる。財産の管理は彼が行っていた。

祖父の死後、彼は遺産相続に関する最初の話し合いで、金で人間関係が壊れることはよくある、金は人を狂わせる、金なんてなかった方がよかった、としみじみと言った。俺は金が大好きなので、最後の 1 つは全く賛成できなかった。

その話し合いの場で彼は祖父の預金額について嘘を言った。親父から伝えられていたのに比べ、ほぼ半分の金額を言った。同席した叔母、従兄弟はだまっていた。俺が父から伝えられていた預金額はこうであると伝えると、叔父はしらばっくれた。叔母は白々しく、預金額はこうじゃない、とほぼ把握しているとおりの金額を言った。

くわえて分与割合についても親父から聞いていた内容を伝えた。叔父が多く取るという分与で問題はなさそうだった。今思えば、なお親父から聞いた約束の内容で行こうとするなんて相当なお人好しだった。嘘を言われた時点で全て終わらせてもよかった。

印鑑証明などを持参しろと言われたが、全財産をまとめたうえで割合を決め、協議書を作成するのが常道ですよと伝えた。叔父はそうなの?と従兄弟にきき、従兄弟はそうだよと答えた。

とにかく全財産を連絡してください、ということで話し合いは終わった。

当たり前だが嘘をつかれた不信感がぬぐえず、財産調査をすることにした。なぜか祖父の口座がいまだ封鎖されていないことを知った(問いあわせにより封鎖された)。これは、と思い残高のみではなく、口座履歴を 2 年分ほど取り寄せることになった。

なるほど、金は人を狂わせるという言葉には説得力があった。祖父の死以前も祖父の入院費の 3 倍以上の出金があったが、祖父の死の直前から口座の封鎖まではさらに出金の頻度があがっていた。祖父の存命中に株式は売却され、ある口座に関しては名義まで書き換えられていた。

なぜ社内ニート化するに至ったのか。

すぐれた技術者の手厳しい部分 *だけ* を見習ってバンバン言いたいことを言っていたら、技術的には特に必要とされていなかったらしくて特定の上司から仕事を振られなくなった結果、ニート同然の生活になったというわけですね。

俺を黙らせようとしてパワハラ同然の発言をしてきたのが最後で、「パワハラ」という言葉を使ってそれを指摘すると抜き差しならなくなると思い遠回しに指摘したら切れられました。

FlashBuilder のカラーリングを変更したい。

クリック一発で変更したい……。

楽しめなかった夏を取り戻すために SummerFruit256 にしたい……。

FlashDevelop で AIR プロジェクトをコンパイルしようとするとこける(解決)。

環境変数 Path に C:\Program Files\Adobe\Adobe Flash Builder 4.7 (64 Bit)\sdks\4.6.0\bin が含まれていたのが原因だった。

FlashDevelop、SetupSDK.bat に SDK へのフルパスが書いてあるので油断していたら、最下段に set PATH=%PATH%;%FLEX_SDK%\bin の文字が!Path として登録して動作させているので、先に登録してあった FlashBuilder のパスに負けるのである。

この FlashBuilder への Path、アンインストールしても消えないので、FlashDevelop と FlashBuilder のインストールとアンインストールを繰り返していたとき、インストールの順番によっては動いたときもあった (FlashBuiler フォルダが不在の時は最後まで通る)。

しかも、FlashBuilder のパスがさしている SDK は特に FlashBuilder でのコンパイルには使われないというややこしい事態 (eclipse\plugins にある) なので、一見うごくんだが invalid namespace だこりゃとか言われる。最新版の SDK 山ほどいれてるのにそりゃねーよという感じ。

まぁ解決してよかったですね。

backbone.js の説明書の写経をした。

その前に、まず「Backbone.js入門 (全22回) - プログラミングならドットインストール」をひととおりみてさわりを学んだ。動画を見ながら聞きながらいっしょにコードを書いていった。

授業と違って待ってくれないのでせわしなかったけど、逆にだらだらしなくてよかった。

なんとなく流れがわかったようなわからなかったような状態になってとりあえず自分でコードを書こうとしたら全然だめだったので、おとなしく説明書を読むことにした。

Backbone.js 日本語リファレンス」を例によって自分の言葉になおしつつ書きうつしながら、説明がわかりにくいところは「Backbone.js」で原本を参照すると簡潔な説明になってたりそれでもわからない場合は livereloadx とともにブラウザで動かしたりした。

あやふやなところはあやふやなまま書いたが、とりあえず backbone.js がなにをしてくれるのかというのがざらりとわかったので、次は「Backbone.js」にのってる Exsamples をみながら実際のコードをさわっていきたい。




super() がないためオーバーライド時には Backbone.Model.prototype.set.apply などとする必要がある。

Event

Collection は内包する Model の全イベントを監視、Model のイベント発火時に同イベントを発火する。

events 単一のイベント名、あるいは半角スペース区切りで複数イベントを指定。

all で全てのイベントが対象。

object.on(events, callback, [context])

events 発火時に実行される callback を登録する。

events 発火時に callback(events, [*args]) で呼ばれる。

object.trigger で発火時に引数を指定できる。

object.off([events], [callback], [context])

events を削除する。

引数省略で全てのイベントの callback を削除する。

object.trigger(events, [*args])

イベントを発火する。

object.once(events, callback, [context])

一度だけ実行される callback を登録する。

object.listenTo(other, events, callback)

other オブジェクトが発火する events に対する callback を登録する。

object.stopListening([other], [event], [callback])

other オブジェクトが発火する events に対する callback を削除する。

引数省略で全てのイベントの callback を削除する。

object.listenToOnce(other, events, callback)

other の events 発火に対して一度だけ実行される callback を登録する。

Event.type 一覧

addmodel, colllection, optionsCollection に Model が追加された。
removemodel, collection, optionsCollection から Model が削除された。
resetcollection, optionsCollection の全体が入れかえられた。
sortcollection, optionsCollection が並びかえなおされた。
changemodel,options Model のいずれかの attribute が変更された。
change:[attribute]model, value, optionsModel の特定の attribute が変更された。
destroymodel, collection, optionsModel が消去された。
requestmodel, xhr, optionsModel か Collection がサーバーリクエストを開始した。
syncmodel, resp, optionsModel か Collection がサーバーと同期に成功した。
errormodel, xhr, options または model, error, optionsModel.save が失敗した。
invalidmodel, error, optionsvalidate に失敗した。
route:[name]params特定の route と一致した。
routerouter, route, paramsいずれかの route と一致した。
alleventName, *argsあらゆるイベントが発火したときに発火する。event の名前を第一引数とする。

Model

Backbone.Model.extend(properties, [classProperties])

Model を継承したクラスを作成する。

propertiesインスタンスプロパティ
classProperties静的プロパティ

new Model([attributes], [options])

Model インスタンスを作成する。

initialize メソッドを設定していた場合実行される。

attributes 作成時、Model にセットされる。

options.parse true にすると、attributes は parse メソッドを通されて set に渡される。

options.collection Collection を渡すと、url メソッドで URL を取得できる。

model.get(attribute)

Model の attribute 値を返す。

key、value は文字列で attriubute を設定できる。

attributes は Object リテラルで複数の attribute を設定できる。

options.silenttrue で change イベントを抑制する。

model.escape(attribute)

attribute 値を HTML に影響を与える特殊文字をエスケープした状態で返す。

model.has(attribute)

model に attribute が存在すれば true を返す。(attriubte 値ではない)

model.unset(attriubte, [option])

  • change

model から attribute を消去する。(attribute 値ではない)

options.silent true で change イベントを抑制する。

model.clear([options])

  • change

model から全ての attribute を消去する。(attribute 値ではない)

options.silent true で change イベントを抑制する。

model.attributes

model の attribute が保持されるプロパティ。

直接変更した場合、イベントが発火しない。

model.id

一意の識別子。

set で model.attributes.id をセットした場合、反映される。

model.id を変更した場合、model.attributes.id には反映されない。

model.idAttribute

model.attributes における id 名。変更できる。

model.id は変更されない。

model.cid

自動的に与えられるユニークな識別子。

model.defaults

model の初期値を Object リテラルで定義する。

model.defaults()

model の初期値を Object リテラル を返す関数で定義する。

model.toJSON()

model.attributes を複製した Object を返す。

model.fetch([options])

  • change

model.url() と通信し、取得結果を model.parse() に通し、model.validate() が実行され、問題なければ model.attributes に反映する。

通信開始時に request イベント、HTTP ステータスが正常なら sync イベント、異常なら error イベント、model.attributes に反映する際、変更があれば change イベントを発火する。

options.silenttrue で change イベントを抑制する。

まず attributes に対し model.validate() が実行され、失敗すれば false を返す。成功すれば model.attributes に即時反映され、変更があれば change イベントが発火する。

バックエンドにリソースの新規作成、更新を要求する。model.id が設定されていると PUT、されていなければ POST で model.url() と通信を行う。取得結果を model.parse() に通し、model.validate() が実行され、問題なければ model.attributes に反映する。

通信開始時に request イベント、HTTP ステータスが正常なら sync イベント、異常なら error イベント、model.attributes に反映する際、変更があれば change イベントを発火する。

options.silenttrue で change イベントを抑制する。
options.waittrue で引数 attributes の反映をサーバーの応答を待って行う。

model.destroy([options])

  • sync
  • destroy
  • error

model.url() と DELETE メソッドで通信し、リソースの破棄を要求する。

通信開始時に request イベント、HTTP ステータスが正常なら sync イベントと同時に destroy イベントが発火され、Collection に登録されている場合は Collection からも消去される。HTTP ステータスが異常なら error イベントを発火するが destroy イベントは発火しない。

model.id が設定されていれば、結果にかかわらず jqXHR オブジェクトを返す。設定されていなければ false を返す。

options.waittrue で model の消去をサーバーの応答を待って行う。

model.validate(attributes)

model.attributes を操作する直前に呼ばれるメソッド。値を返すと失敗扱いになり、error イベントの callback に渡される引数となる。

options.error error イベントに対する callback を設定する。

model.url()

model の url を返す。

urlRoot が未定義の場合は例外を投げる。

model.id が設定されている場合は model.urlRoot/model.id、されていない場合は model.urlRoot を返す。

Collection に登録されており、なおかつ collection.url が定義されている場合、collection.url/model.id となる。model.id がなければ collection.url。

model.urlRoot が優先される。

model.urlRoot

model.urlRoot()

model.url() で使用される。

model.parse(response, xhr)

model.fetch()、model.save() などで取得した値が attributes に反映する前に通されるメソッド。

model.clone()

model と同じ attributes を持った model を複製し、返す。

model.isNew()

model.id が未定義であれば true を返す。

model.hasChanged([attrinute])

引数なしの場合、model.attributes のいずれかが変更されていれば true を返す。

引数なしの場合、直近に変更されたものが model.attributes.attribute であれば true を返す。

model.changedAttriubtes([attributes])

引数なしの場合、最後に変更された model.attributes.attribute を含む Object を返す。

引数ありの場合、model.attributes に反映され、直近に変更があった model.attributes.attribute を含む Object を返す。

model.previous(attribute)

change イベントの callback の引数 model のみで使用でき、変更前の値を返す。

model.previousAttributes()

change イベントの callback の引数 model のみで使用でき、変更前の値全てを返す。

Collection

Backbone.Collection.extend([properties], [classProperties])

Collection を継承したクラスを作成する。

properties インスタンスプロパティ

classProperties 静的プロパティ

new Collection([models], [options])

コンストラクタ。

models が Object リテラルの場合、collection.model のコンストラクタが呼ばれ Model 化されて格納される。。未定義の場合は Backbone.Model が呼ばれる。Model である場合、そのまま格納される。

options.modelcollenction.model を設定する。
options.comparatorcollection.comparator メソッドを設定する。
options.parsetrue で collection.parse メソッドを実行する。

collection.initialize([models], [options])

コンストラクト時に呼ばれる。引数はそれぞれコンストラクタに渡されたものと同じ。

collection.model

collection に格納する Model クラスを設定する。

コンストラクタや collection.add に Object リテラルを渡すと、このクラスのコンストラクタが呼ばれる。

collection.models

model が格納されるプロパティ。

models に対して underscore メソッドが使用できる。

forEach (each) map (collect) reduce (foldl, inject) reduceRight (foldr) find (detect) filter (select) reject every (all) some (any) contains (include) invoke max min sortBy groupBy sortedIndex shuffle toArray size first (head, take) initial rest (tail) last without indexOf lastIndexOf isEmpty chain

collection.toJSON()

collection.models 内の各 model の attributes を複製した Object を含む Array を返す。

collection.sync(method, collection, [options])

Backbone.sync メソッドを実行する。

collection.add(models, [options])

  • sort
  • add

collection.models に models を追加する。Object リテラルが渡された場合、collection.model で指定されたクラスのコンストラクタが呼ばれる。その際 model.validate に失敗すると例外を投げる。

collection.comparator が設定されている場合ソートされる。その際、sort イベントは発火しない。

追加された model は add イベントを発火する。

options.mergetrue で model.id が同一の model に対し attributes を上書きする。
options.atcollection.models のどこに追加するか index 指定する。
options.silenttrue で add イベントを発火しない。
options.sortfalse でソートしない。

collection.remove(models, [options])

  • remove

collection から model を削除する。model 自体は消去されない。

model は remove イベントを発火する。callback 引数の options.index には、実際の削除実行時の collection.models での index が設定される。

models には Model インスタンスか model.id、id を含む Object を渡す。

options.silenttrue で remove イベントを発火しない。

collection.reset([models], [options])

  • reset

collection.models を空にする。models が指定されている場合、それが collection.models に格納される。reset イベントが発火され、callback 引数 options.previousModels で削除前の collection.models を参照できる。

options.silenttrue で reset イベントを発火しない。

collection.get(id)

id または cid が一致する model を返す。

collection.at(index)

collection.models の index 位置にある model を返す。

collection.push(model, [options])

  • add

collection.models の最後尾に model を追加し、add イベントを発火する。

追加された model も add イベントを発火する。追加された model を返す。

collection.comparater メソッドが設定されている場合ソートされるが、 sort イベントを発火しない。

options.silenttrue で add イベントを発火しない。
options.sortfalse でソートしない。

collection.pop([options])

  • remove

collection.models の最後尾の model を削除し、remove イベントを発火する。

削除された model も remove イベントを発火する。

model 自体は消去されない。

options.silenttrue で remove イベントを発火しない。

collection.unshift(model, [options])

  • add

collection.models の先頭に model を追加する。Object リテラルを渡すと collection.model に設定されたクラスのコンストラクタに渡され、追加される。追加された model を返し、collection、model ともに add イベントを発火する。

collection.comparator を設定していてもソートしない。

options.silenttrue で add イベントを発火しない。

collection.shift([options])

  • remove

collection.models の先頭の model を削除し、削除した model を返し、remove イベントを発火する。model も remove イベントを発火する。model 自体は消去されない。

options.silenttrue で remove イベントを発火しない。

collection.slice([begin], [end])

collection.models の指定範囲の model を返す。

collection.length

collection.models.length を返す。

collection.comparator

二つの model が引数として渡される。

-1 を返すと手前に、1 を返すと後に、0 を返すとそのままとしてソート処理する。

collection.sort

  • sort

collection.models をソートする。comparator 未定義の場合は例外を投げる。

並びに変化がなくとも sort イベントを発火する。

options.silenttrue で sort イベントを発火しない。

collection.pluck(attribute)

格納している各 model から model.attriubtes.attribute を取得し array として返す。

collection.where(attributes)

collection.models から attributes が一致する model を返す。

collection.url

collection.url()

collection.fetch メソッドで通信先となる URL を設定する。

格納している model に model.urlRoot が設定されていない場合、代替として使われる。

collection.parse(response, xhr)

collection.fetch メソッドで取得した値が model に設定される前に通される。

collection.clone()

同じ collection.models を持つ collection を複製して返す。

collection.fetch([options])

  • request
  • sync
  • error
  • add

collection.url と GET メソッドで通信し、リソースを取得する。collection.url 未設定の場合は例外を投げる。

collection.reset 後、通信開始時に request イベントを発火、正常 HTTP ステータスで sync イベントを発火し、collection.parse が設定されている場合は通して model に渡す。model.validate に失敗した場合、例外を投げる。異常 HTTP ステータスで error イベントを発火する。

options.silenttrue で add イベント、reset イベントを発火しない。
options.addtrue で reset を行わない。
options.mergetrue で同一 id の model が存在した場合、model.attriubtes を反映する。

collection.create(model, [options])

  • add

collection.url に POST メソッドで通信し、リソースの作成を要求する。

options.silenttrue で add イベントを発火しない。
options.waittrue で model の追加をサーバーの応答があるまで待つ。

Router

Backbone.Router.extend([properties], [classProperties])

Router を継承したクラスを作成する。

properties インスタンスプロパティ

classProperties 静的プロパティ

new Router([options])

コンストラクタ。

router.initialize([options])

コンストラクト時に呼ばれる。options はコンストラクタに渡されたもの。

router.routes

  • route:callbackName

URL hash に対する callback を設定する。

URL がマッチすると route:callbackName イベントを発火する。

hash : callbackcallback
hash/:arg1/:arg2 : callbackcallback(arg1, arg2)
hash/:arg1(/:arg2) : callbackcallback(arg1, [arg2])
hash/*args : callbackcallback([*args])

router.route(route, name, [callback])

個別に route に対する callback を設定する。

route には正規表現を使用できる。

router.navigate(fragment, [options])

  • route

fragment の URL に遷移する。

options.triggertrue で router.route に設定した callback が呼ばれ、route イベントを発火する。
options.repkacetrue で history を追加せずに上書きする。

History

Backbone.history.start([options])

Router がつくられたときに hashChange イベントや route イベントを監視するために呼ばれる。

options.pushStatetrue でpushState を明示的に使用する。
options.hashChangefalse で hash 遷移を抑制する。
options.roothistory の root を設定。

sync

Backbone.sync(method, model, [options])

対象となる model の attributes を非同期に送受信する。

options jQuery.ajax の引数に渡す値。

Backbone.ajax(settings)

jQuery.ajax へのショートカット。オーバーライドしてアクセス方法を変えることができる。

Backbone.emulateHTTP

true で PUT、DELETE のかわりに POST を使う。

本来の値は X-HTTP-Method-Override に入る。

Backbone.emulateJSON

true で MINE タイプが application/x-www-form-urlencoded になり、JSON は model に入る。

View

Backbone.View.extend([properties], [classProperties])

Backbone.View を継承した新しいクラスを作成する。

propertiesインスタンスプロパティ
classProperties静的プロパティ

new View([options])

new View([options()])

コンストラクタ。

options は view.options として格納される。

options.model options.collection options.el options.id options.className options.tagName options.attributes はそれぞれ view のプロパティに設定される。

view.initialize([options])

コンストラクト時に呼ばれる。options はコンストラクタに渡されたもの。

view.options

コンストラクタに渡された options。

view.el

view.el()

view に関連付く DOM 要素。view.events はこれに対して設定される。

view.$el

view.el を jQuery オブジェクトに変換したもの。

view.setElement(element, [delegate])

view.el を再設定する。view.events は旧 view.el から削除され、element に関連づけられる。

dlegate = false でイベントを引き継がない。

view.model

view.collection

view が使用する model や collection

view が作成する DOM 要素の tag、id、attribute、class を設定する。コンストラクタで el を設定していると無視される。

view.$(selector)

view.$el.find(selector)

view.render()

view.el に設定されている DOM 要素に対する操作するメソッド。

view.remove()

view.el および view.events を除去する。

view.make(tagName, [attribute], [content])

DOM 要素を作成する。

view.el をルートとした DOM ツリー内の要素で発火したイベントに対し callback を設定する。

DOM ツリーに新しい要素が追加された場合、自動的に関連づけが行われる。

view.delegateEvents([events])

既存の view.events を除去し、events を設定する。

view.undelegaateEvents()

delegateEvents で設定したイベントを除去する。

view.cid

自動的に振られるユニークな識別子。

冷えたむね肉がおいしい。

鍋に水をはって肉を投入し、湯が沸きたたない程度の中火で 15 分間煮る。火を止めて好きなだけ待つ。やわらかく、肉の味がしておいしい。

冷めた肉をジップロックスクリューなどに山盛り入れ、圧をかけながら閉める。冷えるまで冷蔵庫に入れる。肉はギュッとするが繊維が切れて食べやすい。これもまたおいしい。

そんな感じで毎日 2kg のむね肉を食べている。

本当に単調な毎日です。

なにも起こらないわけではなくて、予定のゆらぎがほぼない毎日をすごしている。

朝の 5 時に起床して、夜の 11 時には就寝する。出社、退社はほぼ定時でずれがない。人付き合いがないので時間をとられない。本屋にもいかなくなった。

朝食、昼食、夕食の時間は 30 分以上ずれない。食べるものも毎日毎食ほぼ同じで、実家にもどる時以外、外で食事をしたりお酒をのんだりすることもない。

underscore.js の説明書写経などした。

説明書の写経になんの意味があるかというとそれ自体にはあまり意味がなくて、読んでいるだけだとすぐ眠くなるしわりと読み飛ばすしけっこう忘れるからです。

なるべく自分の言葉になおすようにすると、ちゃんと読まなくちゃならないのでよい。動作確認は livereloadx 走らせておくと保存しただけで結果が見れてよい。いまいち何が起こってるかわからない場合は console.log(_.templete) などするとソースが見れてよい。

ちゃんと読んだ結果、いちいち自分で書いてたようなやつ大体よりよい形でカバーされてて、とても便利だということがわかった。

ちゃんと知りたいと思ったきっかけが backbone.js なので次は backbone.js さわる。

コレクション

_.each(list, iterator, [context])list の各値を iterator で処理する。
_.map(list, iterator, [context])list の各値を iterator で処理した新しい list を返す。
_.reduce(list, iterator, memo, [context])memo を初期値とし、list の各値を iterator で処理した値を memo に加える。
_.reduceRight(list, iterator, memo, [context])reduce を list のケツから行う。
_.find(list, iterator, [context])list を頭から iterator で真偽テストし、最初にパスした値を返す。
_.filter(list, iterator, [context])list の各値を iterator で真偽テストし、パスした値が入った list を返す。
_.where(list, properties)list の各値で properties が一致するものの list を返す。
_.findWhere(list, properties)list で properties が一致する最初のものを返す。
_.reject(list, iterator, [context])list の各値を iterator で真偽テストし、パスした値を取り除いた list を返す。
_.every(list, iterator, [context])list の各値を iterator で真偽テストし、全てがパスすると true を返す。
_.some(list, iterator, [context])list の頭から iterator で真偽テストし、パスした時点で true を返す。
_.contains(list, value)list に value が含まれていれば true を返す。
_.invoke(list, methodName, [*arguments])list の各値の methodName メソッドをコールする。
_.pluck(list, propertyName)list の各値の propertyName プロパティ値を list にして返す。
_.max(list, [iterator], [context])list の各値を比較し最大値を返す。iterator は比較に使われる。
_.min(list, [iterator], [context])list の各値を比較し最小値を返す。iterator は比較に使われる。
_.sortBy(list, iterator, [context])list の各値を iterator で比較し並べ替えられた list を返す。
_.groupBy(list, iterator)list の各値を iterator に渡し、その返り値によりグループ化する。
_.countBy(list, iterator, [context])list の各値を iterator に渡し、返り値の回数をカウントする。
_.shuffle(list)list をシャッフルした list を返す。
_.toArray(list)list を配列に変換する。
_.size(list)list の値の数を返す。

配列

_.first(arrat, [n])array の最初の要素を返す。n 値で最初から n 個の要素を返す。
_.last(array,[n])array の最後の要素を返す。n 値で最後から n 個の要素を返す。
_.initial(array, [n])array の最後の要素以外の array を返す。n 値で最後から n 個以外の array を返す。
_.rest(array, [index])array の最初の値を取り除いた array を返す。
_.compact(array)false として扱われる値を除いた array を返す。
_.flatten(array)多重 array を一つの array にして返す。
_.without(array, [*values])values と === の値を取り除いた array を返す。
_.union(*arrays)arrays を統合した array を返す。値は重複しない。
_.intersection(*arrays)arrays 全てに含まれる値の array を返す。
_.difference(array, other)other に存在しない array の値の array を返す。
_.uniq(array, [isSorted], [iterator])array 内の重複した値を一つにした array を返す。
_.zip(*arrays)arrays の同 index の各要素を array にしたものを含む array を返す。
_.unzip(*arrays)zip の逆。
_.object(list, [values])list の各要素を key、values の各要素を value としたプロパティを持つ object を返す。
_.indexOf(array, value, [isSorted])array の中で value がある最初の位置を返す。なければ -1 を返す。
_.lastIndexOf(array, value)array の中で value がある最後の位置を返す。なければ -1 を返す。
_.sortedIndex(list, value, [iterator])list の整列状態を維持した上で value が挿入されるべき位置を返す。
_.range([start], stop, [step])start から stop まで += step された数値の array を返す。stop は含まれない。

関数

_.bind(function, object, [*arguments])function の this を object に束縛した関数を返す。arguments がある場合は部分適用された関数が返る。
_.bindAll(object, *methodNames)object 内の methodNames メソッドの this を object に束縛する。
_.partial(function, [*arguments])arguments を引数として function に部分適用した関数を返す。
_.memorize(function, [hashFunction])function をメモ化した関数を返す。
_.delay(function, wait, [*arguments])function を wait ミリ秒後に呼びだす。
_.defer(function)function を 0 ミリ秒後に呼びだす。
_.throttle(function, wait)何度呼ばれても wait ミリ秒に一度だけ function を実行する関数を返す。
_.debounce(function, wait)呼びだされてから再度呼びだされずに wait ミリ秒間経たないと function を実行しない関数を返す。
_.once(function)function を最初の一度だけ実行し、その後は返り値を返すだけの関数を返す。
_.after(count, function)count 回コール後 function を実行する関数を返す。
_.wrap(function, wrapper)function を引数として実行する wrapper を実行する関数を返す。
_.compose(*functions)functions の頭から呼び出し、それぞれの返り値を引数として次の function に渡す関数を返す。

オブジェクト

_.keys(objext)object の全てのプロパティ名を array で返す。
_.values(object)object の全てのプロパティ値を array で返す。
_.functions(object)object の全てのメソッド名を array で返す。
_.extend(destination, *source)destination に *source の全プロパティを複製する。
_.pick(object, *keys)keys で指定したプロパティのみをもつ object を返す。
_.omit(object, *keys)keys で指定したプロパティを取り除いた object を返す。
_.defaults(object, *defaults)defaults の全プロパティの内、object が持たないプロパティを object に複製する。
_.clone(object)object を浅く複製する。
_.tap(object, interceptor)object を引数として interceptor を呼び出し後、object を返す。
_.has(object, key)key プロパティを持っていれば true を返す。
_.isEqual(object, other)object と other のプロパティを深く比較し真偽値を返す。
_.isEmpty(object)object が値を含まなければ true を返す。
_.iselement(object)object が DOM 要素であれば true を返す。
_.isArray(object)object が配列なら true を返す。
_.isArguments(object)object が Arguments なら true を返す。
_.isFunction(object)object が関数なら true を返す。
_.isString(object)object が文字列なら true を返す。
_.isNumber(object)object が数値なら true を返す。
_.isFinite(object)object が有限数なら true を返す。
_.isBoolean(object)object が真偽値なら true を返す。
_.isDate(object)object が日付なら true を返す。
_.isRegExp(object)object が正規表現なら true を返す。
_.isNaN(object)object が NaN の場合にのみ true を返す。
_.isNull(object)object が null の場合にのみ true を返す。
_.isUndefined(object)object が undefined の場合にのみ true を返す。

ユーティリティ

_.noConfrict()Underscore オブジェクトへの参照を返す。
_.identity(value)value を返す。
_.times(n, iterator)n 回 iterator を呼びだす。
_.random(min, max)min から max までの間の数値をランダムで返す。
_.mixin(object)Underscore オブジェクトを拡張する。
_.uniqueld([prefix])呼びだされるごとに一意の prefix + id を返す。prefix による id の区別はない。
_.escape(string)HTML に影響する特殊文字をエスケープした string を返す。
_.unescape(string)エスケープされた HTML に影響する特殊文字を元に戻した string を返す。
_.result(object, property)object 内の property がメソッドなら実行し、それ以外なら値を返す。
_.templete(templeteString, [context])templeteString を処理する関数を返す。

チェーン

_(obj).chain()value() が呼びだされるまでメソッドは obj をラップしたオブジェクトを返す。
_(obj).value() ラップされた obj を返す。

死んだ父の夢をみた。

父は末期の食道がんが発覚して手術し、3 年生きて、肝臓に転移したがんで死んだ。

死ぬ半年ぐらい前に抗がん剤治療もやめた。ひどい副作用を目の当たりにしていたしがんの縮退もみられなかったので、誰もとめなかった。外出する元気があるうちは釣りなどにでかけ、肝機能が落ちて動けなくなってくると、自宅のリビングのソファで本などを読みながら一日をすごした。

死ぬ直前の 2 ヶ月ほどの間、会うたびに肌が黄色くなっていったのをおぼえている。誰の目からも確実に悪くなっているのがわかって、俺も母も妹も、なかば強制的に心の準備をさせられていたと思う。父が死んだときに、誰も泣き崩れ自分を失ったりはしなかった。多少は泣いていた。

父が死んだ日の前日の朝、父の様子がおかしいと母から電話がはいった。早朝物音がするのでみてみると、湯が沸ききっていない風呂に父が入っていたらしい。1 月で水風呂には少し早い。実家に向かうと、父はいつも通りソファに横になっていた。いつも以上にしんどそうだったが、それ以外に異常は感じられなかった。

肝障害に伴う症状は調べてあった。錯乱、意識障害が起こるらしいということは知っていたので母にそれを伝えた。母は父に入院をうながしていたが、父は拒否した。受け答えはまだはっきりしていた。おれは仕事に行くために家に戻った。

翌日の朝、また母から電話がはいった。口調に余裕がない。父が早朝に車ででかけ、妹に頼まれたと言って遠くのコンビニで新聞を買ってきたらしい。コンビニなら少し歩いたところにもある。それに妹は実家を出て彼氏と住んでおり、既に働いているのでそのような頼みごとをするはずがなかった。一気に悪くなった、と思った。

実家に戻り、病院に行くことを拒否する父の意志はとりあえず横において救急車を呼んだ。父は担架には乗らず、自分の足で歩いて救急車に乗った。俺は戸締まりなどをすませ病院にむかい、少し休むことになるかもしれない、と会社に連絡した。診察室で待っている間に父のポケットから車のキーを取った。

医師は、父の症状は完全に深刻で、もう長くはないと言った。日が暮れて、母は家から着替えなどの入院セットを運んで病院にとどまり、かわりばんこに泊まろうと決めてから後から来た妹と実家に戻った。その日の深夜に父は死んだ。

夢の中で俺と父は実家の玄関にいた。一緒にどこか買い物に行くみたいだった。母に見送られ歩いてる途中で、新聞を買いにいったときのことを聞こうと思った。思った瞬間にそこからはじまる一連の入院から死までが思い出されて、ああ父はもう死んだ、つまりこれは夢だし、聞いても駄目だなと考えて、ただ父の後ろを歩いた。

$(selector).on(eventType, handler) ではなく $(document).on(eventType, selector, handler) でイベント登録する。

たとえば bootstrap-button.js の初期化は以下のように行われている。

$(document).on('click.button.data-api', '[data-toggle^=button]', function (e) {
  //略
})

自分が知っているやりかたは以下のようだった。

$('[data-toggle^=button]').on('click.button.data-api' , function (e) {
  //略
})

なぜ $(document)?と思ったんだけど、$(selector).on だと $(selector) で element を 1 周、.on でさらに 1 周の合計 2 周が必要な一方で、$(document).on だと 1 周しながら登録できるから合計 1 周で済むからなんだな。

$('[data-toggle^=button]') を何度も参照しなければならない場合はかわってくるだろうけど、単にイベント一発登録して終わりなら $(document).on が良いのだろう。

.on で namespace つきの eventType を登録する

JQuery を HTMLElement セレクターとしてしか使えてなかったので、勉強になるだろうと bootstrap.js を読んでいる(そのまえにドキュメント全部読めばって話ではある)。知らなかった使い方があればメモ。

.on で namespace つきの eventType を登録する

同じ eventType に登録された複数のイベントリスナーのうち特定のものをはがしたい場合、unbind(eventType) でははがしたくないものもはがされるので、関数へのリファレンスを保持しておく必要がある。

var handler1
, handler2
, $test1 = $('#TEST1')
, $test2 = $('#TEST2');

$test1.on('click', function (e) { console.log('always 1'); }); $test2.on('click', function (e) { console.log('always 2'); });

$test1.on('click', handler1 = function (e) { console.log('test 1'); $test2.unbind('click', handler2); }); $test2.on('click', handler2 = function (e) { console.log('test 2'); $test1.unbind('click', handler1); });

handler1 とか handler2 がそれで、関数自身で自分をはがす場合は arguments.callee を使えなくもないが(悪手らしい)、ほかのイベントリスナーをはがそうとすると手がかりがない。

そこで namespace を使う。

var $test1 = $('#TEST1')
, $test2 = $('#TEST2');

$test1.on('click.test.always', function (e) {
   console.log('always 1');
});
$test2.on('click.test.always', function (e) {
   console.log('always 2');
});

$test1.on('click.test.test', handler1 = function (e) {
   console.log('test 1');
   $test2.unbind('click.test.test');
});
$test2.on('click.test.test', handler2 = function (e) {
   console.log('test 2');
   $test1.unbind('click.test.test');
});

e.type でひくと click が返る一方で unbind などでは区別されているので、unbind('click.test.test') とした場合 click.test.always ははがされずに残る。unbind('click.test') だとどちらも消える。

はがす予定のイベントリスナーにはわざわざメンバー変数などを用意していたのですが、これならその手間がはぶけてとても便利ですね。

社内で仕事を干されるとけっこう困る。

業務でプログラムをしていると、半ば強制的に考えたこともないような問題の解決が必要になり、能力アップにつながると思う。

社内で仕事をほとんど干された状態の俺が学習を進めようとすると、自分で問題を設定して自分で解決しなくてはならない。どうにかこうにか問題を用意しても、どうしてもこれをクリアしなくちゃ~というモチベーションがないのでだらだらする。

なんだかゆっくり死んでいってるような気がしてよくない。

もどるボタンを無効にしたい

もどるボタンを無効にしたいと思ってぐぐると、「もどり先のページに history.forward() を埋めこめば、『もどる』されても強制的に元のページに帰ってきてハッピーだよ」という記事がたくさん出てきて「そうじゃない、そうじゃないんだ」という気分になった。

要はそのページから遷移をさせたくないという意味での「もどらせたくない」であって、もどり先のページが重要なわけではなかった。

もどるボタン自体をどうにかしたり、event.preventDefault() できるような event も存在しなかったので、ページ内リンク (#HOGE) が history に数えられることを利用した。

http://mmmpa.net/js_page/unbackable/

(function (wait, hash_wait, hash_unbackable) {
    location.href = hash_wait;
    var id = setInterval(function () {
        clearInterval(id);
        location.href = hash_unbackable;
        window.addEventListener('hashchange', function (e) {
            if (location.hash === hash_unbackable) {
                return;
            }
            history.forward();
        });
    }, wait);
})(400, '#WAIT', '#UNBACKABLE');

もどったりもどらなかったりする。

ページ表示後しばらくたたないと、history に入らないようで、ウェイトさせる必要があった。history.pushState なんてのがあるみたいだが、現時点ではアレ。

JQuery で td のただしい幅がとれずに往生したのでメモ。

table の特定の行や列を複製したかったのだが、どうもずれる。

で、色々やってたら JQuery ではなくて Javascript の問題だった。Firefox と IE で起こって、Chrome では起こらない。

<table id="TABLE">
	<tr>
		<td>1</td>
		<td>2</td>
		<td>3</td>
		<td>4</td>
		<td>5</td>
	</tr>
</table>

td を 100% / 5 の width:20% にして等幅に設定したあと、table に width:500px とすると、element.clientWidth でそれぞれ 100 が取れる。これは正常。

td は width:20% のまま、table に width:502px など 5 で割り切れない数字を設定するとブラウザによって返す値が変わる。

var tds = document.getElementById('TABLE').getElementsByTagName('td');
var result = [];
for (var i = 0, l = tds.length; i < l; i++) {
	result.push(tds[i].clientWidth);
}
console.log(result);

Chrome では [100, 100, 100, 101, 101] といったように、等幅ではないが合計で 502px となる実際に表示されている横幅を返す。

Firefox と IE では [100, 100, 100, 100, 100] と、等幅ではあるが表示とはことなる数値 (スクリーンショットで測定すると、きちんと 100 と 101 が混在している) を返す。

table を 503px にした場合は [101, 101, 101, 101, 101] と返ってくる。それぞれ、502 / 5 と 503 / 5 した数値の四捨五入だ。

td 内に div を置いてその横幅を測定しても同値が返ってくるので、これはもうどうしようもない。

border-collapse:collapse すると Chrome もいいかげんな数値を返すけど、div を挿入して幅をとると正確な数値を返す。

仕事なし

社内ニートという立場が存在するとニュースで知ったときは「最高じゃねぇか」と思ったけど、実際自分がその立場におかれてみるとあまり最高でもない感じがする。

なにしろ自分がのほほんとやれてきたのは他の社員の技術レベルが低いからであって、世間に放りだされたら再就職先が見つかるよりもそのままのたれ死ぬ可能性の方が高い。小さな会社なので、いつでもクビになる可能性がある。

HTML と Flash と CSS が俺の仕事だった。あわててはてブなどを参照して Javascript などの勉強をしているわけだけど、転職歴はなくて、同僚にプログラマーがいたこともない純粋培養の低レベル技術者 (35) だ。勉強がすすんでるのかどうかもわからない。

そんなわけであまり「社内ニート楽しい」とはなっていない。

てst

てst