patorashのブログ

方向性はまだない

社内勉強会でMacの便利ツールを紹介したのでここにも載せておく

弊社は昨年末からエンジニアの退職が相次いでいるのですが、そういう中でも5月に新たな仲間を迎えることができまして、若手エンジニアの育成やっていくぞ!という気持ちでおります。また、開発部内も製品毎にグループが分かれている関係もあり若干縦割りなので、そういう垣根を取り払って気軽に情報共有していこう!ということで、今回から開発部内で広く勉強会を周知していました。前回までは、いわゆるいつものメンバーでの開催だったのですが、今回からは他のチームからも参加者がきてくれたので、引き続き周知していきたいと思います。

常日頃から思っているのですが、キーワードを知らないと調べることすらできないので、とりあえずキーワードだけでも覚えてもらって、もし興味が出たら調べてみてね、という感じで社内勉強会にて開発時に便利なツールを紹介しました。

無料のツール

fish

fishはshellの1つで、bashよりも使いやすいと感じているので、私はfishを使っています。Macの構成管理(Ansible)でも、fishを使うようにも設定しています。

fishshell.com

便利な点は、強力なサジェスト機能だと思います。あとは、プラグインが充実している点。fishermanというプラグイン管理ツールがあり、便利です。

patorash.hatenablog.com

不便な点は、だいたいのツールがbashzshを前提としてあることが多いので、ツールをインストールしてもそのままでは動かないことがある、とかですかね。そういうのも、プラグインで対応してあるものがあったりはしますが。最近はfishにも最初から対応してあるものが増えてきています。ありがたい。

ドキュメントですが、るびきちさんのfishシェル普及計画のページがありますので、英語が苦手な人でも導入しやすいかと思います。

fish.rubikitch.com

ghq

ghqコマンドラインのgitリポジトリの管理ツールです。gitリポジトリのcloneや、すでにclone済みのgitリポジトリのリストを表示することができます。

github.com

~/.gitconfigに設定を追加しておけば、cloneするときのディレクトリが指定できるので、コードが複数のディレクトリに散らばらないので、とてもよいです。ghqはpecoと組み合わせることでとても便利なツールになります。

peco

pecoは、標準出力から受け取った結果をフィルタリングしたり、選択可能にして次のパイプに渡すツールです。なんかうまくいえる言い回しがわかりませんが。pecoはそれ自体ではほぼ何もできないのですが、他のコマンドと組み合わせることで様々なことができます。

github.com

私がよく使うのは、

  1. ghq list -pの結果をpecoで処理してリポジトリディレクトリに移動
  2. git branchの結果をpecoで処理してチェックアウト
  3. git branchの結果をpecoで処理してブランチの削除
  4. historyの結果をpecoで処理して再び実行
  5. ~/.ssh/configからHostを抽出したものをpecoで処理してSSH接続
  6. psの結果をpecoで処理してPIDを取得してkill

などです。これらはfishの関数化してあり、すぐに呼び出すことができるようにしてます。例えば、1のやつとかはselect_repositoryという関数にしてあります。

gistで、私が作っているfishの関数があるので、紹介しておきます。

https://gist.github.com/patorash/1de94fcb7efeb847501d2fb7900c2deb

pecoは組み合わせ次第でいろんな使い方ができるので、qiitaとかで情報を追ってみると面白いでしょう。

有料のツール

Dash

Dashは、プログラミング言語や開発ツールのリファレンス・マニュアルをローカルにダウンロードして検索できるツールです。使い方がわからないメソッドの情報を、ググらないでもすぐに調べることができます。

kapeli.com

これは実は5〜6年前の社内勉強会のときに、当時パートナーエンジニアとして参加してもらっていた@aguuuさんに教えてもらいました。

Dashのメリットは、なんといってもローカルに保存してあるから検索が高速な点と、ローカルに保存してあるので、ネットが繋がらないところであってもリファレンスを参照できる点です。また、各種IDEやエディタにプラグインがあるので、それを入れておくと、IDEから直でDashを参照できます。例えば、RubyMineでcreate_tableという文字列を選択して、command + shift + Dを押すと、create_tableメソッドの使い方を表示してくれます。

値段は$29.9と、まぁまぁするのですが、メソッドの使い方などを検索する時間を買ったと思えば安いものかなと思います。試してから買えるので、まずは試してみるといいかなと思います。

Alfred

Alfredは、spotlightのさらにすごいやつ、みたいな感じです。キーボードだけでいろんな操作が可能になります。option + spaceで呼び出して、命令を入力したり、検索したりなど。計算もできるので電卓を起動したりする手間もありません(まぁそれはspotlightでもできますけど)。

www.alfredapp.com

これもまた、Okayama.rbで交流のある@logictktさんに教えてもらったツールです。

簡単なところだと、sleepと打つとスリープしてくれたり、restartと打つと再起動、shutdownと打つとシステム終了など…。サジェストも効くので、slと打つだけでスリープできます。

Alfredはアプリ内課金があり、パワーパックを購入するとさらに様々なことができるようになります。ワークフローといって、Alfredの入力を他のアプリに渡すことができたりします。GUIのpecoみたいな存在ですね。

さきほど紹介したDashのワークフローもあって、Alfredを呼び出してdash create_tableとか打つと、Dashで検索してくれます。わざわざDashのウィンドウを探さなくてもいいので、これもまた便利です。pecoと同様、様々なワークフローがネット上に紹介されているので、調べてみるといいでしょう(実は私もDashくらいしか使ってないですが…)

まとめ

以上、社内勉強会で紹介したツールでした。まぁ実際の発表では、めちゃくちゃザックリとした紹介の仕方だったんですが、その後にチャットワークでさらに情報交換が行われたりしたので、紹介してよかったなと思います。

他にも便利なツールがありましたら教えてください!

CircleCIのworkflowを使ってbundle updateのPRを自動化する

以前にHerokuとCircleCIを使ってbundle updateのPRの生成を自動化していました。

qiita.com

しかし、最近はCircleCIのworkflowを使えばHerokuを使わずともCircleCIのみで完結しそう!ということだったので調査してみました。

方法が公開されていた

circleci-bundle-update-prのREADMEで、CircleCIのワークフローを使ってPRを生成する方法が公開されていました。ありがたい!

github.com

.circleci/config.ymlに設定してみる

設定をほぼ丸々コピペしてみました。

  • Rubyのバージョンの辺りを最新の2.5.1に変更
  • restore_cacheのkeysを既存プロジェクトの設定に変更

とりあえず動くかどうか試すためにworkflowsのbuildブロックにbundle updateするjobを設定してpushしてみたところ、エラーで落ちました。

nokogiriが入らない

nokogiriは、C拡張のgemなので、素のruby: 2.5.1-alpineのdocker imageだとコンパイルできずにエラーになりました。とりあえずCircleCIにsshで接続して、必要になりそうなものをインストールしていきます。

参考にしたのは、以下のページ。

dora.bk.tsukuba.ac.jp

alpine-sdkを入れればよさそう。

apk add --update --no-cache alpine-sdk

その後、bundle installしたらnokogiriが無事にインストールされました!しかしまだbundle installの途中でコケる…。

pgが入らない

pgもC拡張のgemなのですが、postgresql-clientがないとコンパイルできないようでした。こちらも入れていきます。

apk add --update --no-cache postgresql-dev

この後、bundle installしたらpgも無事にインストールされ、bundle installが完走しました。

修正後の設定

以下が、修正後の設定です。nokogiriやpostgresqlを使ってない場合は素のままでいいと思いますが、参考程度に。 deployブロックのusername, emailは自身のgithubアカウントに置き換えてください。

また、CircleCIのプロジェクトに環境変数 GITHUB_ACCESS_TOKEN を設定しなければなりません。 GitHubPersonal access tokensより、トークンを発行して、それをCircleCIのプロジェクトのBUILD SETTINGS » Environment Variablesから設定してください。

version: 2
jobs:
  build:
    # snip
  continuous_bundle_update:
    docker:
      - image: ruby:2.5.1-alpine
        environment:
          TZ: "/usr/share/zoneinfo/Asia/Tokyo"

    working_directory: /work
    steps:
      - run:
          name: Install System Dependencies
          command: |
            # See also https://circleci.com/docs/2.0/custom-images/#adding-required-and-custom-tools-or-files
            apk add --update --no-cache git openssh-client tar gzip ca-certificates tzdata alpine-sdk postgresql-dev
            gem install -N bundler
      - checkout
      - restore_cache:
          name: Restore bundler cache
          keys:
            - gems-{{ .Environment.COMMON_CACHE_KEY }}-{{ checksum "Gemfile.lock" }}
            - gems-{{ .Environment.COMMON_CACHE_KEY }}-
      - run:
          name: Setup requirements for continuous bundle update
          command: gem install -N circleci-bundle-update-pr
      - deploy:
          name: Continuous bundle update
          command: circleci-bundle-update-pr <username> <email>

workflows:
  version: 2
  build:
    jobs:
      - build:
          # snip
  nightly:
    triggers:
      - schedule:
          cron: "00 10 * * 5"
          filters:
            branches:
              only: master
    jobs:
      - continuous_bundle_update

まとめ

CircleCIだけで完結するようになったのはありがたいです。また、cronのようにスケジュール設定できるとのいうのも、色々使えそうな気がします。 また、workflow自体も、テストが通ったら自動デプロイができたり、テストが通ったら通知を送って、手動で承認をするとデプロイが動くなど、細かい運用フローにも対応できそうで、よさそうです。とりあえず個人プロジェクトではテストが通ったら自動デプロイするところまでやってみようと思います。

RubyKaigi2018に行ってきたので社内報告会を行った

f:id:patorash:20180602121807j:plain

今年は久々にRubyKaigiに参加しました。場所は仙台。参加費は会社から出してもらえるのでありがたい〜。とても刺激を受けた3日間でした。

戻ってきて早速、社内報告会を行いました。ただし、英語力が乏しいのと、Ruby自体の実装とかCの話とかは本当によくわからないので、めちゃくちゃざっくりとした報告に…。 とりあえず、自分が面白かったと思った話題について。

ちなみに、今回の聞く方針としては、

  1. 機械学習系のやつを聞いて、詳しくないなりに、社内にフィードバックする
  2. Rubocopの話とか押さえたい
  3. 自分たちの手で高速化できそうな話を聞く

という感じで考えてました。

TruffleRubyすごい

3日目のキーノートで話されたTruffleRubyに驚きました。TruffleRubyは、Oracle Labsが開発しているGraalVM上で動くRubyです。

github.com

GraalVMは多言語に対応した仮想マシンです。

www.infoq.com

上記のページに、

とあるので、そこからTruffleRubyという名前がついてるのでしょう。ちなみに、「トリュフ」だそうです。

7倍速い!

Rubyは現在、3x3 Rubyを目標として、Ruby 3はRuby 2の頃よりも3倍速くなることを目指しています。キーノートでは、Rubyベンチマークで使われるOptCarrot(Rubyで作られたファミコンエミュレータ)を使って、パフォーマンスの計測をしていました。

  • Ruby 2.0では、28fps
  • Ruby trunkでは、55fps
  • TruffleRubyでは、開始数秒は1〜10fps程度だけれど、突然180〜200fpsに!

3倍速いどころではなく、7倍速いのにびっくりです。JITコンパイラがめちゃめちゃ効いてるからということでした。そのため、最初は遅いと。どういうふうに最適化が行われているかという話がありましたが、だんだん話についていけなくなり…。わかったのは、まだ実用的ではなさそうなことでした。ただ、もう少ししたらrbenvとかで試せるようにする、ということでした。Webアプリケーションでの恩恵がどの程度になるのかはわかりませんが、期待大だなと思いました!

DeepLearning

深層学習はRubyは弱いというイメージがありましたが、だいぶRubyだけで完結できる世界が近づいてるという印象を受けました。全くの門外漢なので、ChainerとTensorflowという名前だけは聞いたことがある…という程度なのですが、MXNetというApacheプロジェクトのものがあるのを知ることができました。

話は基本的にはmxnet.rbと、Red Chainer(ChainerをRubyでポーティングしたもの)でした。

MXNet

MXNetは多言語サポートをしていて将来的にはRubyもサポートされること、多くの企業・大学がサポートしていること(Amazon、MS、MIT、カーネギーメロン大学など)、利点が多いことなどをあげられていました。Apacheプロジェクトということで、他のデータサイエンス系のApacheプロジェクトのメジャーツールとの連携が期待できることや、ONNXをサポートしていることなどが挙げられていました。ONNXは、ツール間で学習内容の交換ができる形式のようでした。

現在、mxnet.rbを開発中で、開発者を募集中とのことでした。

Red Chainer

Red ChainerはPython製のChainerをポーティングしたものだそうです。開発者を募集していて、gitterで意見交換をしたり、speeeさんのところで毎月イベントをされてるそうです。

Rubyらしく書けるのが特徴ということでしたので、自分がもし深層学習やってみる場合はこの辺りから手をつけてみようかなと思いました。

Cumo

Cumoは、深層学習用のミドルウェアのようでした。CumoのCの字は、CUDAからきています。Ruby/Numoとの互換性を高く置いていて、NumoをCumoと1文字変えるだけで動くことを目指しているとか。

Cumoが必要になった背景は、GPUで演算するRubyのやつがなかったので、作り始めたとか。なぜGPUが使いたいかというところの話で、やはり圧倒的にパフォーマンスが違うようでした。CPUで30日かかる処理が、GPUだと4日程度。その理由はGPUの圧倒的なコア数。

Red ChainerはまだGPUが使えないのですが、Cumoを使うようにすると、パフォーマンスが75倍速くなったとか…。圧倒的すぎる…。

まだまだ課題はあるとのことでしたが、Ruby機械学習・深層学習が身近になりそうな話題でした。

メモリ・GC

Rubyはメモリ消費が多いので、省メモリの話とかも多かったです。アーロンの話では、Ruby2.5から2.6に変えると、Railsの起動時のメモリ消費は4%改善するとか。Rubyをバージョンアップするだけでいいので、これは期待大です。

先に、話に出ていたgemとかを並べときます。

  • allocation_tracer
  • env_mem

あと、Linux環境においてですが、jemallocを使うようにRubyコンパイルするときのconfigureオプションで設定すると、10〜12%くらい速くなったとか。

スピードアップまとめ

その他

あとはDDD用のWebフレームワークのhanamiの話を聞きに行ったりもしたのですが、正直あんまりよくはわかりませんでした…。英語力のなさと、hanamiの事前情報のインプットくらいはしておけばよかったなと思いました。

AnyCableというActionCableの代替の話も、パフォーマンスの違いとか聞けて興味深かったです。自分はまだActionCableを使ったことがないので、サンプル程度でも作ってみようという気になりました。

あとはJRubyの話とか。GraalVMが出てきたので、JRubyどうなってしまうんやろ?という感じはありますが…。

今年はRubocopの話が多めだったなぁという印象でした。弊社でもLint系のものは使っていきたいよねという話は時々しているので、Rubocopの中の話とかが聞けて参考になりました。先人の踏み抜いた知見がフィードバックされて自動コードレビューが行われていくというのは、ありがたいことだと思うので、早めに入れていきたいと思います。

その他、開発したくなる〜!という刺激をたくさん受けるキーノートやセッションが盛りだくさんでした!

会社の報告会では、上記のような話と、Matzのキーノートについてと、ノベルティ配布会を行いました。

来年は福岡で開催なので、弊社からも大勢行けるようにしていこうと思います。

f:id:patorash:20180602180018j:plain

RubyKaigiのスタッフのみなさま、スポンサーのみなさま、ありがとうございました!

赤ちゃんの子育て中にあったらいいものリスト

注意:この記事はまだ書きかけですが、公開してます。随時更新予定です。

twitterのフォロワーさんたちで子供が生まれた人がチラホラおりましたので、短い育児経験ながら、子育て中にあったほうがいいものとか、これは買うのやめとけ!(別の買え!)というもののリストを作ってみようと思います。なお、これは私の主観が入っておりますので、ご了承ください。

買った方がいいもの

ベビーシート

まずオススメなのは、マムズキャリーです。これは車に乗る人のみになりますが、めちゃくちゃ重宝しますのでオススメです。

注意点としては、子供のサイズ的に1年間しか使えないのですが、1年しか使えないからと言ってベビーシートの選択肢から外すのは勿体無いです。むしろ1年後に他のチャイルドシート・ジュニアシートを買った方がいいと思います。

持ち運びできる

カゴのような構造なので、持ち運びできます。ちょっとした移動くらいだったら、ベビーカーに移して…とかしなくてもそのまま運べちゃう!うちでは、病院に行くときや外食するときはこのまま運んでます。外食の場合は、お座敷でも大丈夫な分、ベビーカーよりもいいかもしれません。

また、赤ちゃんが寝てしまっても、マムズキャリーを外してそのまま移動できるから、ほぼ起こしてしまう心配がありません。家に帰ってきても寝ていたら、そのまま家に運んで、起きるまで置いといてます。

最高にコスパいい

6,000円くらいで買えます。うちは西松屋で買いました。正直、ベビーシート、チャイルドシートってどれがいいのかすごく迷うし、しかも高いものはかなり高いです。これ買って1年間を過ごしている間に大いに迷ってそれから次のチャイルドシートを買うのがいいんじゃないか?と思います。私はチャイルドシートにもなるジュニアシートを買えばええんやで」という育児の先輩方のアドバイスに従って、そうしました。

抱っこ紐

抱っこ紐もいろんな種類というか、いろんなブランドがひしめき合っていて、さらに高い…。高いやつは確かにモノはいいのですが、比較しているとだんだんわからなくなってきます。最終的にうちはこれにしました。

コスパがいい!

1万円以下だけれど作りはしっかりしています!ホールド感もよく、安定性がしっかりしていて、他の有名ブランド品と遜色ないと思って決めました。

両手が使えるようになる

実はこれの前にもっと安い肩掛けの抱っこ紐を使っていたのですが、安定感がよくなくて、結局腕が自由に使えなくて困ったことがありました。

抱っこしていないと赤ちゃんが泣くときは、こういう抱っこ紐をつけて部屋を練り歩くというだけでも結構泣き止んでくれます。そういうときに両手が使えるってのは重要なことで、泣き止ませながら他のことができるわけです。私の場合は抱っこ紐をつけて部屋の中をウロウロしながら読書してます!

コードレスクリーナー

はっきり言っちゃうと、ダイソンのコードレスクリーナーがいいです。普通の掃除機にも使えるし、ハンディクリーナーにもなるし、吸引力はすごいので、他のハンディクリーナーを別で買うくらいなら、ダイソンでいいと思います。ダイソンならなんでもいいというわけではなく、オプションをなるべく多くつけて買いましょう。布団用とハンディ用は必須です。

サッと掃除できることが重要!

コードレスがいいのは、掃除したいところだけサッと掃除できることです。コード付きだと、やる気がたまらないとなかなかできません。赤ちゃんが寝る周辺だけ、布団用のヘッドでガーッと掃除して、他は気になるところをチョコチョコと…くらいでいいのです。

あと、成長してきて幼児用のおやつを与えるようになると、おやつの食べカスとかがそこら中に散らばります。そういうときにも、コードレスクリーナーはサッと掃除できるので助かります。

車の中も掃除できる

コードレスクリーナーの強みは、車の中も掃除できることです。チャイルドシートの周りは本当にめちゃくちゃおやつのカスだらけになります。そんなときにもダイソンのおかげで綺麗に掃除できています。

保温調理器

保温調理器は子育て世帯じゃなくてもオススメできますが、子育て世帯はマジであったほうがいいと思います。うちはシャトルシェフを使っています。あまりに便利なので、友人の赤ちゃん誕生祝いにシャトルシェフを贈ったほどです。

ずっと火の側にいなくてもいい

例えば、時間のかかる煮込み料理(カレーやおでんとか)を作る際は、焦げないようにするためにずっと鍋の側にいなければなりませんが、そうはいっても赤ちゃんが寝返りしだすと、ちゃんと呼吸できているか見ないと危険です。なのであんまり長時間台所に立つのは難しいところですが、保温調理器は材料を入れて沸騰したら密閉するだけであとは放ったらかしにできるので、赤ちゃんの側にいられる時間が増えます。

それに、なんといっても時間が作れるところがいいです。昼食作りのときに夕食分の材料も一緒に切っておいて、煮込んで放置しておけば、それが夕食になるので、夕食作りの時間がカットできます。子育て中はいかに自分の時間を確保するかが大事なので、料理にかかる時間が減らせるのはいいことです。

ちなみに、長時間煮込む際にかかるガス代や電気代(IHの場合)もかからなくなるので、長期間で考えると家計にも優しいです。

離乳食作りに最適

うちは子供の離乳食用のお粥を作るときにシャトルシェフが本当に、本当に、大活躍しました。離乳食のお粥は、まずは10倍粥、次に5倍粥、次に3倍粥…のように、徐々にお粥の濃度を濃くしていきます。しかし、水分が飛びすぎたり焦げたりして本当に難しいです。そんなとき、シャトルシェフで沸騰してから放置しておけば、水分が飛びすぎたり、焦げる心配が一切ありません。特に、焦げは保温後に加熱してないのだから、絶対に焦げません。シャトルシェフでお粥を大量に作って、冷凍保存はよくやっていました。

また、他の離乳食でも同様に使えます。離乳食は柔らかめにしないといけないので、長時間煮込まなければなりません。このときも、沸騰したら保温調理器に入れて30分〜1時間放置。これで柔らかい離乳食ができます。

保温調理器は台所のルンバ的存在

ルンバは自動お掃除ロボットですが、これは別に時短にはなりませんが、自動でお掃除してくれますね。シャトルシェフも同様、時短にはなりませんが、自動で調理してくれる、そんな存在です。ルンバはそこそこなお値段しますが、シャトルシェフは大きさにもよりますが1万円以下で買えます。1万円程度で調理にかかる時間を大幅に削減でき、かつ煮込み料理が美味しくでき、しかも光熱費が減らせるので、一石三鳥ですよ!

続きはまた後で

時間ができたらまた書いていきますのでお楽しみに。

Railsのエラー画面の謎挙動にハマッた

Railsのcontrollerで、トランザクションを使っているところで、rescueブロックの処理が書いてあるのにそこに辿り着けていない現象に遭遇しました。

悩んでいることをチャットワークに書いたら、同僚の @kazuhisa1976 さんが、小さいRailsアプリを作って検証してくれたのですが、そこで面白い挙動が見つかりました。

検証環境を作る

小さいRailsアプリを作る

まず、小さいRailsアプリを作ります。

rails new sample
cd sample
bin/rails g scaffold User name:string
bin/rails db:migrate

Userモデルにvalidationを加えます。

class User < ApplicationRecord
  validates :name, presence: true
end

UsersController#createで、検証のためにトランザクションを使ってみます。保存に失敗したら、root_pathにリダイレクトするという設定です。

class UsersController < ApplicationController
  # 省略
  def create
    @user = User.new(user_params)
    ActiveRecord::Base.transaction do
      @user.save!
    end
    redirect_to @user, notice: 'User was successfully created.'
  rescue => e
    redirect_to root_path
  end
  # 省略
end

Railsアプリを起動しておきます。

bin/rails s

これで、とりあえずの検証環境は出来上がりました。

検証する

正常系

まずは、nameを入力して投稿してみます。

f:id:patorash:20180519084703p:plain

当然ながら成功。

f:id:patorash:20180519084744p:plain

異常系

次は、nameを何も入力せずに投稿してみます。nameが空なので、@user.save!が失敗して、root_pathにリダイレクトされるはずです。

f:id:patorash:20180519084635p:plain

おおっと!リダイレクトされませんでした!rescueブロックに辿りつかずに、@user.save!ActiveRecord::RecordInvalidが発生していると言われました。私が遭遇した現象と全く同じ!

f:id:patorash:20180519084959p:plain

原因を探る

エラー画面のWebコンソールで、実験してみましょう。

f:id:patorash:20180519085705p:plain

はい。実はこのRailsアプリには、root_pathが定義されてません。それが原因です。例外の情報が入っている変数eもあることから、rescueブロックには実は辿りついています。

そうなんです。rescueブロックで発生したエラーの原因を表示せず、元の例外が発生した原因を表示してしまうということです。

修正する

このRailsアプリでいえば、routes.rbでroot_pathを定義してあげれば、直ります。

Rails.application.routes.draw do
  resources :users
  root to: 'users#index' # root_pathを定義
end

まとめ

rescueブロックで例外が発生すると、大元の例外の情報を基にエラー画面が作られてしまうので、rescueブロックに辿り着けないようなエラー画面が表示されたら、rescueブロック内にブレイクポイントを置いて1つ1つ検証していくのがいいです。恐らく、今回と同様に変数かメソッドの定義がないなどが原因です。

自分のエラーの原因も、メソッドがないことが原因でした。 作った当初はRails3か4くらいで、paramsがHashを継承していたのですが、Rails5にアップグレードして(今使ってるのは5.1.4)Hash継承ではなくActionController::Parametersになった影響で、mapメソッドがなくなっていました。

似たようなコードを書くと、以下のような感じ。

# users_paramsはfields_forで作られた配列のデータ
def update
  ActiveRecord::Base.transaction do
    users_params.each do |id, values|
      user = User.find id
      user.assign_attributes values
      user.save!
    end
  end
  redirect_to users_path
rescue => e
  @users = users_params.map do |id, values| # ここでundefined method
    user = User.find id
    user.assign_attributes values
    user.valid?
    user
  end
  render :edit
end

参考情報はこちら。

qiita.com

そこで、以下のように修正。

rescue => e
  @users = users_params.to_h.map do |id, values| # to_hメソッドを挟む
    # 略
  end
  render :edit
end

今回は管理画面側のため、網羅的にテストを書いていなかったことが気づけなかった原因だったので、例外時のテストも追加しておこうと思います。

仮説の検証の仕方が確認できた

GWにブックオフで買ってきた本の1冊。アウトプットの質を高めるとあったので、買った。がむしゃらにアウトプットしていてもなかなか質は上がらないよなぁ〜という気持ちだったので、ちょうどよかった。

アウトプットの質を高める 仮説検証力

アウトプットの質を高める 仮説検証力

仮説を立てる際に心がけたいこと

ついついテクニックに走りがちになるらしいのだけれど、とりあえずたくさんのデータがあればなんかわかるだろう…みたいな感じだとよくない。

目的を明確にする

なんのために仮説を立てるのかをはっきりしておかないと意味がない。無意味な計算を繰り返す羽目になる。

仮説の精度は準備で決まる

しっかりとしたデータの整理などをしておけば、おのずと精度は上がる。時間がかかりやすいところではあるけれど、不十分なデータで仮説を立て始めるほうが、時間を浪費する。

周囲と同じ仮説を出していては意味がない

ある程度の傾向が出てくると、みんな似たような仮説を立てるけれど、同じ仮説を立てて安心していてはいけない、と書いてあった。「自分ならではの仮説が立てられないようでは、新しい価値を生み出せない」というメッセージに、確かになぁ〜!と思わされた。誰でも立てられるような仮説は機械学習がやってくれるようになるだろうし、オリジナルの視点を持つことが人間の価値を高めることになりそう。

仮説の検証法は2種類

仮説の検証法は以下の2種類あるとのこと。

  1. 実験型検証
  2. 裏付け補強型検証

仮説は実験して検証するものという認識が強かったので1の実験型検証が普通かと思っていた。PDCAのDとCに当たるものだ。しかし、実験を行うのは時間的なコストや金銭的なコストがかかるので何度も行うのが難しいものが多い。

裏付け補強型検証は、PDCAのPに当たるもので、仮説の精度を高めようというものだ。仮説の精度が高くなければ、結果の要因があやふやになりやすい。

本の中では、例え話で、旅行の計画が順当なものかを検証するようなものと書かれていた。無謀なスケジュールを立ててしまったら旅行に支障が出るため、前もって情報を集め(移動時間は正しいか、美術館はその時間で回れるのか等)本当にそのプランで旅行できるかを検証しようということだ。

定量データと定性データ

仮説を立てるには、まず裏付けとなるデータが必要になる。そのデータは

  • 数値化できる定量データ
  • 数値化できない定性データ

である。

定量データは、来客数とか売上高、市場シェアなど。定性データは、アンケート結果など。基本的にどちらか一方だけではダメで、両方を使っていく模様。

定量データから仮説を立てるには?

まずは必要なデータだけに絞り込むことが大事という。とにかくデータを集めればいいというものではない。このあたり、機械学習の本でも同じことが書いてあった。まぁ機械学習もデータを元に傾向を見て仮説を立てて、インプットが仮説の通りかを検証してアウトプットするものだもんなーと思った。

仮説を立てる前にすること
  • 集約
    • MECE(漏れなくダブりなく、データを分類する)
    • 因数分解(足し算・引き算・掛け算・割り算)
  • 指標化(比較する際に必要)
比較する際に気をつけること

どう比較するか(HOW)、何と比較するか(WHAT)をちゃんと意識すること。

どう比較するか
時系列比較
時系列の比較は基本中の基本。昨年度との売上の比較など。
自分なりに設定した基準と比較
昨年度の売上の110%を基準として、それとの比較など。
外部の比較対象との比較
同業者の売上の伸び方との比較やシェアの比較など。ベンチマーク
何と比較するか
目的に沿ったデータと比較
分析の目的に合ってないデータだったら、比較する意味がない。どういう意図で作られたデータかが大事。
できるだけ同じものと比較
これは、外部の比較対象との比較のところの話。実力差や規模の差がありすぎるものと比較しても、意味のある仮説はなかなか立てられない。同規模で調子がいいもの、同期や1つ上と先輩などをベンチマークとするとよい
数値自体には意味がない

例えば、シェアが13%、みたいな数値があったとして、ここから何が読み解けるかというと、何もわからない。数値は他者や、時系列での過去との比較を行わないと意味がなく、また、比較して読み取れる傾向などが見えて、初めて意味が出てくる。データ比較で使われている縦軸・横軸に注目する。

定性データから仮説を立てるには?

定性データは準備が9割。記事やアンケートなどの定性データは分析にはそのまま使わない。ちゃんと下ごしらえを行うこと。定性データ同士で共通点や相違点を整理する。

定性データには特性がある

例えば、アンケートでいえば、回答者の年齢、性別、職業、身長、体重、趣味などがわかれば、それらで切り分けることができる。それらを特性という。本の中では、新作弁当のアンケートでは男女での違いがあった。男性は「ボリュームが少ない」、女性は「量がちょうどいい」など。これらを特性なしで判断すると、どっちが正しいかわからなくなる。お弁当のターゲットを男性にするか、女性にするかなどの判断材料となる。

アンケートなどのヒアリングを行った際の回答は、定性的データで、回答者の主観が入ってるので網羅的ではなかったりするのだけれど、アンケートも数があれば共通部分がでてきたり、回答者の属性で分類できたりする。

データの意味を浮かび上がらせるように整理する

データを整理して満足になってしまいがち。目的は整理したデータから意味を読み取ること。

定性的なデータの比較

意識するところは3つ。

比較対象としているデータは仮説を立てる目的に合っているものか?
漠然と比較しない
特性ベースで比較する
特性ごとの共通点・相違点を見つけていくことで方向性が生まれる。
意味を読み取る
  1. 全体の共通点から意味を読み取る
  2. 一部の共通点から意味を読み取る
  3. 意外な相違点を捉える

読み取ったら、一言でまとめてみるのがいいらしい。

定量データと定性データを組み合わせて仮説を立てる

どちらも使っていく。定量データで全体的な傾向をつかみ、掘り下げていくときに定性データを使っていく。定量データで見られた傾向に、定性データで肉付けを行っていき、仮説をより強固なものにしていく。

事例では、X社の代理店の売上の推移(定量データ)と、代理店の担当者とX社の営業へのヒアリング(定性データ)が使われていた。定性データの特性は、代理店の規模感と、代理店におけるX社製品の売上成長率・シェアなどなど。この事例での定量データ、定性データのまとめ方は非常に参考になるなと感じた。

裏付け補強型検証

裏付け補強型検証は上のほうでも説明したのだけれど、実験しにくい仮説の精度をまず上げるために行う。 裏付けのない仮説は仮説ではない。単なる思いつき。

仮説の精度を確認する
  • そもそも言いたいことがあるか
  • 目的・問題意識に沿ったものか
  • 漠然とした内容になっていないか(精度の高い仮説は具体性が命)
  • 裏付けの精度はどうか(量・偏り・信頼性)
補強するべきポイントを明らかにする
なにが不足しているか、偏りがあるか、など。具体的に。
入手するべきデータを明確化する
定量データ、定性データの構造化を行い、欲しいデータを定義する。
データを入手する
  • 入手したいデータを考える
  • 具体的な入手方法を考える
  • 代替案を考えておく
入手したデータで補強して仮説の精度を高める
仮説の修正を行う。

どこまで検証を進めるか

際限のないことなので、時間を区切って進めるか、インパクトのある仮説ならば満足する精度だと思うまでやる。

実験型検証

試行錯誤の検証はこちら。実験型検証は日々、みんながやっていることと同じ。しかし、「これで失敗したから同じ失敗はしないようにしよう」みたいなレベルに止まることが多いので、もう一段階高い視点を持っておく。

実際に行う場合に気をつけること

自分なりのこだわりを持つ

仮説の中でも、ここに注意してみよう、これを意識した行動をしてみようなど、具体的な違いを作っておくと後で検証しやすい。

結果だけでなくプロセスを考慮する

結果だけにフォーカスすると、ただ運が良かっただけなのに勘違いしてしまうことが多い。重要なのは再現性があることなので、プロセスが大事。なので、その仮説が成立するプロセスを具体的にイメージしておくことが大切。上の「自分なりのこだわりをもつ」ところは、このプロセスの中で意識することになるのだろう。

プロセスの中にこそ、改善点が埋もれている。

結果の検証

問題点が解消したかどうか
前より改善したのか、そうでもなかったのか。
実験による副作用はどうか
何かをするということは、他のことが起きる。Aが売れた結果、Bが売れなくなった等。
目的の達成度はどうか
問題点が解決しても目的が達成されない場合もある。(Aが売れなかったという問題は解消されたが、Bが売れなくなって目的の売上アップはできなかった等)

実験を繰り返す

  • 実際に効果があるのかを確認
  • なんども行うことで傾向を見る

異なる実験を行う

  • 目的は変えないこと!
  • 意図を明確に変える
    • どの観点に効果がある、ないかを掴むことができる
  • 現状や以前の実験との違い・差分を意識する
    • 同時に複数のものを変えないこと
      • 何が原因かわからなくなるから

うまくいった(いかなかった)要因を把握する

プロセスを具体的にイメージして実験したとしても、うまくいく場合とうまくいかない場合がある。期待したプロセス通りに物事が運ばなかった場合もある。そのあたりの要因を把握する。

  • 他の要因を探る
    • たまたまうまくいった場合
      • 当日イベントが近所であってお客さんが多かった等
      • 景気がよくなってきていた等
    • 逆もしかり。たまたまうまくいかなかった場合
  • 改善ポイントを掴む
    • 次に似たようなことをするときに活かせる

要因の捉え方

  • 自分の立てた仮説は正しかったか
  • 想定通りに仮説を実行できたか
  • 裏付けに見落としはなかったか
    • 責任を自分のせいにして変にいじけないこと
      • 想定外のことは起きる時は起きる
      • 誰だって見落とすことはある
  • 他の要因はなかったか
    • コントロールできる要因
      • 情報の共有など(連絡していれば防げたこと等)
    • コントロールできない要因
      • 天候・集客など

実験結果を活用する

実験結果がうまくいった場合は、似たような場面があれば精度を向上させることができる。他の仮説にも応用できるところを探していく。仮説自体の応用や、プロセスや考え方を応用するなど。

うまくいかなかった場合は、改善ポイントを探して、他の仮説に活かすことができる。

感想

検証といえばPDCAのC(Check)だろうと思っていたのだが、裏付け補強型検証でPの段階でもやるもんだとわかったのがよかった。たぶん、普段から知らず知らずのままやっていたとは思うけれど、とりあえずDoしちゃおうぜ!みたいなことは結構あったと思う。回転速度の早いPDCAならそれでも振り返りできるのでいいのだろうけれど、AIや機械学習の技術が一般化してくると、Pの裏付けが今後は大事になっていくと思う。

また、成功体験が運が良かっただけなのに勘違いしてしまうというのは本で読んだことがあるが、それもちゃんと考慮してプロセスが期待通りだったか、他の要因はなかったかを検証するという視点が書かれているのはよかったと思う。逆もしかりで、たまたま運が悪かっただけというのもあるけれど、運だったのか、計画が杜撰だったのか、その辺りはちゃんと裏付けしていかないといけない。

本のまとめみたいな記事になってしまったが、自分の中でフワッとやっていたことなので要点を整理しようとしただけで長くなってしまった。仮説はアップデートできるし、ブログ記事もアップデートできるので発見があったら更新しとこうと思う。

部分インデックスでミスった話

新たにカラムを追加することになり、その条件が

  • Null許可
  • ユニークであること
  • ただし論理削除を考慮する

となったので、アーハイハイ、部分インデックスですねと思って設定していて、テストもしていたのだけれど、考慮漏れがあったので自戒を込めて書いておきます。

部分インデックスとは

インデックスの条件にwhere句を使うことができるやつです。

よくあるパターンでは、メールアドレスで登録されたユーザーがユニークになるようにしてあるけれど、論理削除を考慮する場合などで使われたり、上位500件のデータのみにインデックスを貼りたいとか、そういうときに使います。

-- 論理削除されていないユーザーでメールアドレスがユニークであること
CREATE UNIQUE INDEX
  email_idx_deleted_at
ON users(email)
WHERE deleted_at IS NULL;

これってemailカラムは普通NOT NULL制約があるので通常はこれでも大体いけますし、Railsのバリデーションでもvalidates :email, presence: trueにしているので、空文字が入ることはありませんでした。

そう、空文字です。

空文字によるエラー

先ほどの条件のカラムをtokenという名前のカラムとします。最初は全く同じ条件で部分インデックスを定義していました。

CREATE UNIQUE INDEX
  token_idx_deleted_at
ON users(token)
WHERE deleted_at IS NULL;

Railsのバリデーションの条件は、以下の通り。

validates :token, allow_blank: true, uniqueness: { scope: :deleted_at }

これで大丈夫かなと思っていたのですが、フォームから空文字が送られてきていて、tokenが空文字のデータが2つ入ることができずにエラーが発生。

テストでは重複NGなことと、論理削除済みならば重複OKであることなどは検証していたのですが、空文字の考慮が抜けていました…。

空文字をOKにする

一通りどうしようか悩みました。

  1. フォームから空文字がきたらparams[:user][:token]を削除する
  2. 空文字を登録できるように部分インデックスを修正する

1で行こうかとも思ったのですが、SQLで直接空文字を入れられたら同じことが起きるので、2のほうを選びました。

CREATE UNIQUE INDEX
  token_idx_deleted_at
ON users(token)
WHERE
  deleted_at IS NULL
  AND
  token != '';

今後は気をつけようと思います。