| @技術/プログラミング

ブログに人気記事を表示するようにしてみた。やり方はめっちゃ雑で、 Nginx の access_log を集計して Bot や Crawler 、 RSS Reader からのアクセス、画像や CSS 、 JS ファイルへのアクセスを除外してアクセス数を集計して結果をテキストファイルに出力し、 Ruby で parse してフッターに表示してる。

仕組み

こんな感じのシェルスクリプトを置いて cron で実行してる。

#!/bin/bash

zcat -f /path/to/access.log* \
  | grep -vE 'useragent:.+?(bot|Feed\s?Fetcher|Crawler|Fastladder|Feed|Ruby|Aol\sReader|proximic|Hatena\sAntenna|Mediapartners-Google|subscribe)' \ # bot や Crawler を除外
  | cut -f5 | sed -e 's/request_uri://' \ # request_path だけ抜き出し
  | grep -vE '(favicon\.ico|index\.atom|\.js|\.json|\.css|\.jpe?g|\.png|\.gif|\.txt|\.php|\/admin|^\-$|^\/$)' \ # HTML 以外へのリクエストを除外
  | sort | uniq -c | sort -nr | head -100 | sed -r 's/^[ \t]+//g' \ # 集計して上位 100 件だけを得る
  | tee /path/to/public/access-ranking.txt # テキストファイルに書き出し

zcat -f しているのは gzip 済みのログファイルも cat したいため。このやり方だと現存するログファイルからしか調べられないので logrotate で設定している期間(自分の場合は 30 日)の集計しかできない。またサーバーを複数並べて運用しているようなアプリケーションではアクセスログがばらけるのでこんな雑なやり方は使えない。

Nginx のログのフォーマットは LTSV にしているので grep でのフィルタリングがやりやすい。まず User-Agent で bot っぽいアクセスを除外したあと、ログから request_uri のフィールドだけを切り出し、静的ファイルなどへのアクセスを除外したあと sort -> uniq -c -> sort -nr してる。

Ruby ( Lokka ) の方では以下のようなコードを書いて access-ranking.txt を読み込んでる。これをやらないと記事のタイトル表示やリンクが生成できないため。

class Entry
  class << self
    def popular(count = 5)
      access_ranking = File.open(File.join(Lokka.root, 'public', 'access-ranking.txt'))
      slugs = {}
      access_ranking.each.with_index(1) do |line, index|
        access_count, path = *line.split(" ")
        slug = path.split("/")[-1]
        slugs[access_count] = slug
        break if index == count
      end
      all(slug: slugs.values, limit: count).sort_by {|entry| slugs.values.index(entry.slug) }
    end
  end
end

フッターは適度にキャッシュしているのでスピードはそんなに遅くならない。

感想

アクセスランキングを表示してみて、意外と Twitter やはてブでバズった記事へのアクセスは継続的には多くないことがわかる。最近だと ARC'TERYX や SIERRA DESIGNS のパーカーの記事が人気があるようだ。これはおそらく寒くなってきててそういうキーワードで検索してたどり着く人が多いのだろう。 GarageBand でのアナログレコード録音の方法は前から人気ある。はてブとかは大して付いてないが、 Yahoo! 知恵袋や 2ch の過去記事・まとめサイトからのアクセスが多いようである。謎なのが痔ろうの記事へのアクセス数の多さ。痔ろうの症状・治療方法を結構詳細に書いたので Google 先生が良記事判定してくれているのかも知れない。家の記事ははてブでバズって 2000 ブックマーク以上付いたが、それでもやっと 5 位という感じ。バズっても短期的なアクセスしか得ることができず(人の噂も 75 日!!!、!)、長期的に細々とトラフィックを集めるためには特定の属性の人にだけ響く詳細な記事を書くのがよいのかもしれない。

| @料理/食事

DSC_4302

Kaizen Platform, Inc. (現株式会社Kaizen Platform)を退職したときに、記念品として AeroPress をプレゼントしてもらった。この AeroPress がなかなかよくて気に入っているので紹介します。

AeroPress は Rebuild でも話題に上がったことがあるし、名前だけは知ってる人いるかもしれない。

自分はどういうわけか知らないけど存在を知っていた。おそらくスーパーウルトラ富裕なコーヒー好きの元同僚 @t32k さんのツイッターかブログで知ったのだろう。買いたいとは思っていたけど自分で買うには結構高くて躊躇していたところ、退職イベントが発生してプレゼントしてもらうことができた。カプ社の皆様ご恵贈ありがとうございました。

何がよいのか

味にぶれがないことがよい。ドリップでうまい味を再現するためにはお湯の量とか抽出時間とか一度に注ぐお湯の量とか気をつけなければならないことが多すぎると思う。 AeroPress を使う場合、豆の量とお湯の温度さえ気をつければよくて、あとはテキトーにやっても味が変わらない。毎回おいしくできるのがよい。

まずい豆でもそこそこおいしくなるのも不思議だけどよい。やっぱりコーヒーは豆の焙煎具合や鮮度が一番重要なファクターで、どんだけドリップを名人がやっても程度の低いコーヒー豆でおいしいコーヒーを抽出することは無理だと思う。 AeroPress を使ったとしてもその原則は崩れないのだけど、それでもスーパーの半額セールで買ったようなやつ(ドリップで飲むと思わず顔をしかめたくなるような味)でも「うん、まぁいけるね」という程度の味にはリカバリできる。本当に不思議。

さっと抽出できて片付けが楽なのもよい。ドリップは 3 分間くらい、ドリッパーの前につきっきりになってちょぼちょぼとドリップしていかなければならない。 AeroPress の場合、挽いた豆を容器の中に入れてお湯を注ぎ、付属のかき混ぜへらで混ぜてあとはカップの上に載せて抽出していくだけ。 30 秒くらいでできてしまう。片付けも楽で、プラスチックフィルターを外してぽんと押し出すだけで豆かすが捨てられる。洗うのもささっと流せば完了。めっちゃ楽。

オフィシャルな使い方

販売元的には AeroPress は空気で圧力をかけるエスプレッソみたいな存在なのだと思う。公式ページに置いてある動画でも少量を抽出して、お湯で薄める様子が紹介されている。

ただ自分的にはこの使い方はあまり合わなかった。コーヒー豆を沢山使わないとちょうどよい濃さのコーヒーにならない。自分は守銭奴なのでコーヒーを入れるときに使う豆の量はコップ一杯あたり 10g から豪遊しても 12g 程度に抑えたいと思ってる。 AeroPress 付属の計量スプーンは 15g 用で、一杯あたりのコーヒーにこんなに量を使えるのは @t32k さんのような富裕層だけだと思う。自分には無理。

いろいろやって試してみて、以下の入れ方が 10g のコーヒー豆で外れなしの抽出をできるという結論にたどり着いた。倒立法という逆さまにした方法で入れます。

自分の使い方

0. コーヒー豆を挽いてお湯を沸かす

1. 器具を組み立てて、本体の中に豆を入れる

DSC_4305 DSC_4306 DSC_4308

2. 沸かしておいたお湯を注ぐ。温度は 80 度にする。

DSC_4309 DSC_4310

3. ちょびっとだけ入れた状態でかき混ぜる

DSC_4311

4. お湯をぐーっと上まで注ぐ

5. 付属のプラスチックフィルターに円形ペーパーフィルターをセットして本体にくっつける

DSC_4312 DSC_4313 DSC_4314

6. ぐいっと本体をひっくり返してマグカップの上にセットする

DSC_4315

7. 注射をするようにじわじわ最後まで押し込んでいく

DSC_4316 DSC_4319

8. できあがり

DSC_4320

9. 片付けも楽勝です。プラスチックフィルターを外して豆かすをゴミ箱に押しだし捨てるだけ

DSC_4321 DSC_4322

10. 飲みましょう

DSC_4323

この入れ方だと一度に一人分しか抽出できない。 ArroPress は 4 人分まで抽出できるようになっているが、お湯で薄めることが前提となっている。上述の通りそれは豆の量が沢山必要になるので自分的にはこの入れ方に落ち着いている。

というわけで AeroPress: The Good Parts でした。コーヒー好きだけどなかなか自分ではおいしく入れられないという人は是非お試し下さい。


この記事は コーヒー Advent Calendar 2017 - Adventar 二日目の記事でした。明日は euxn23 さんです。

| @技術/プログラミング

Archive ページにカテゴリごとの記事件数を表示する機能を追加した。

API でカテゴリごとの記事件数を返すのではなく、 JavaScript だけで entries.length を数えるようにしている。 React で関連コンポーネントが描画されたあとにいい感じに数える処理をトリガーする方法が分からず、描画完了後 1000msec 以内で 100msec ごとに記事数を数える処理を実行している。あまり賢い実装方法ではないと思うけど、一番記事数が多い 2006 年でも 600msec くらいで Ajax のレスポンスは返ってくるのでまぁ実用上問題はない。

こうして見ると、 2011 年から一切映画についてブログに書いてないことが分かる。さすがに年に一本も映画を見ないということはなくて、年に二、三本は絶対見ていると思う。以前は基本的に映画館で見た映画の感想を書くようにしていたので、映画館に行かなくなった(行けなくなった)せいで映画の記事を書かなくなったんだろうなぁと思う。

自分のブログに機能を追加することでここ数年の生活の傾向が分かるのが面白い。実は昨日、 Sunset Live の記事を書いたのも音楽について全然書いてないことに気づいたからなのであった。

| @技術/プログラミング

CircleCI.png

もうすぐ Ruby 2.5 が出そうだけど、このブログの Ruby のバージョンを 2.4 に上げた。

Ruby 2.4 がリリースされたあとすぐに Lokka を Ruby 2.4 に対応させようと頑張ってみたのだけど、 Ruby 2.4 に上げるためには ActiveSupport のバージョンアップが必要だった。しかし padrino-helper 0.11.4.1 が古いバージョンの ActiveSupport に依存しており、 Ruby 2.4 に対応した ActiveSupport を利用するためには padrino-helper をバージョンアップする必要があった。 padrino-helper をバージョンアップしてみると 0.12 から大幅な変更が入ったようで、ビューの表示がぐちゃぐちゃになった。 layout が適用されず中身の partial ビューだけ表示されたり、 HTML が過剰にエスケープされたり。 padrino-helper は 0.11 時代にあった脆弱性を修正して標準ですべての文字列出力を escape するようになったっぽい。しかし Lokka の方がそれに対応できておらずビューがぐちゃぐちゃになっているようだった。

Lokka はヘルパーメソッドやモデルで HTML を文字列から生成しているところがあって、そういうところをこまめに html_safe していったところ余分なエスケープは解消されていった。利用している gem でも文字列から HTML タグを生成しているところがあったので、そういうところもつぶさに調べていって無心で html_safe したり mark_safe していった。

とりあえずこのブログを Ruby 2.4.2 でしばらく運用してみて問題なかったら Lokka 本体の方に Pull Request を出すことにしよう。

あと今回ついでに CircleCI でテストしてテストが通ったら勝手に Production に deploy されるようにしておいた。 CircleCI ほんと便利。

追記 2017-12-03

Pull Request 出しておきました。

| @ブログ

以前、 The Old Reader について書いたとき、クローリングが遅くて不満だ、というようなことを書いていた。

The Old Reader はフィードの更新間隔がクソで、プレミアムアカウントのユーザーが登録しているフィードは 30 分おきにフィードをフェッチするとあるんだけど、全然そんなことなくて、平気で 4 時間遅れたりする。偏りがあって、頻繁に更新されるブログのフィードは頻繁にチェックされるけど、頻繁に更新されないブログのフィードはあまりチェックされないみたい。クローラーのつくりとしては正しいのかもだけど、利用者としては不満が残るなぁ。

The Old Reader - portal shit!

そしたら以下のようなコメントを Pinboard でたまわってた。

フィード・リーダーのクローラーが遅い問題の話。Feedlyもそうだけど、クローラー側のポーリングは4時間ごとくらいで良くて、ユーザー(サービス)側がPubSubHubbbub使って欲しいな。

https://pinboard.in/u:hail2u/b:e25b4cfe2a4a

確かにその通りなのでこのブログでも PubSubHubbub 使うようにしてみた。 Sidekiq とか入れたら Worker にやらせることできていい感じにできるんだろうけど、とりあえずこんな感じで乱暴に通知してる。

class Entry
  after :save do
    if Lokka.production? && !draft
      system("curl -s https://pubsubhubbub.appspot.com/ -d 'hub.mode=publish&hub.url=https://portalshit.net/index.atom' -X POST")
    end
  end
end

爆速で The Old Reader や Inoreader に更新が反映されることを確認したので Feedly とかでも更新後に即反映されるようになってると思う。

| @技術/プログラミング

autodoc-generation-flow.png

autodoc を導入して Rails プロジェクトで Request Spec を書くと自動的にドキュメントが更新されるようにした。 autodoc 自体は前々職の頃から利用していて大変お世話になっていた。ただ最初の頃は手元で AUTODOC=1 bundle exec rake spec:requests して手動でドキュメント更新していた。ドキュメントが更新されるかどうかは担当者の心がけ次第なのでよくなかった。

前職では CircleCI を使っていて、デプロイや Asset Precompile など CI でいろいろやるのが当たり前だったので、 Pull Request が Merge されたタイミングでドキュメント生成するように .circleci.yml をカスタマイズしてた。

いま仕事しに行ってるところでは .circleci/config.yml を version 2 にしていて、 version 2 からは workflow の概念が導入されたので、頑張ってシェルスクリプトで条件分岐させたりする必要がなくなった。 .circleci/config.yml は以下のような感じになってる。

version: 2

shared: &shared
  working_directory: ~/app
  docker:
    - image: circleci/ruby:2.4.1-node
      environment:
        PGHOST: 127.0.0.1
        PGUSER: username
        RAILS_ENV: test
        REDIS_HOST: localhost
    - image: circleci/postgres:9.6-alpine
      environment:
        POSTGRES_USER: username
        POSTGRES_PASSWORD: pasword
    - image: redis:3.2-alpine

jobs:
  build:
    <<: *shared
    steps:
      - checkout
      # Restore bundle cache
      - &restore_cache
        type: cache-restore
        key: app-{{ checksum "Gemfile.lock" }}
      # Bundle install dependencies
      - &bundle_install
        run: bundle install -j4 --path vendor/bundle
      # Store bundle cache
      - &save_cache
        type: cache-save
        key: app-{{ checksum "Gemfile.lock" }}
        paths:
            - vendor/bundle
      # Database setup
      - &db_setup
        run:
          name: Database Setup
          command: |
            sudo apt install postgresql-client
            bundle exec rake db:create
            bundle exec rake db:structure:load
      - type: shell
        command: bundle exec rubocop
      # Run rspec in parallel
      - type: shell
        command: |
          mkdir coverage
          COVERAGE=1 bundle exec rspec --profile 10 \
            --format RspecJunitFormatter \
            --out /tmp/test-results/rspec.xml \
            --format progress \
            $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
      # Save artifacts
      - type: store_test_results
        path: /tmp/test-results
      - type: store_artifacts
        path: coverage

  generate-doc:
    <<: *shared
    steps:
      - checkout
      - *restore_cache
      - *bundle_install
      - *save_cache
      - *db_setup
      # Generate document
      - type: shell
        command: |
          AUTODOC=1 bundle exec rake spec:requests
      - run:
          name: Setup GitHub
          command: |
            export USERNAME=$(git log --pretty=tformat:%an | head -1)
            export EMAIL=$(git log --pretty=tformat:%ae | head -1)
            git config --global user.email "${EMAIL}"
            git config --global user.name "${USERNAME}"
      - run:
          command: |
            git add doc
            git commit --quiet -m "[ci skip] API document Update

            ${CIRCLE_BUILD_URL}"
            git push origin ${CIRCLE_BRANCH}

workflows:
  version: 2
  build-and-generate-doc:
    jobs:
      - build
      - generate-doc:
          requires:
            - build
          filters:
            branches:
              only:
                - master

master ブランチでのビルドのときだけ generate-doc という job が実行されるようになっている( master ブランチ以外のビルドではドキュメント生成がスキップされる)。buildgenerate-doc で重複してる部分が多いので YAML のアンカー機能を使って重複を整理しているけど結構長い。 generate-doc ジョブでドキュメントが生成されると勝手に GitHub の master ブランチに対して push する。このときコミットメッセージに [ci skip] という文字列を付けておけば、 CircleCI はビルドをスキップするので延々とドキュメントの自動更新ビルドが走り続けることはない。

おかげでいまはテストさえ書けば、実際の API と同じフォーマットのドキュメントが自動生成されるのでとても便利になったと思う。

ちなみに JSON Schema というのもあって、これは JSON に仕様を書くとドキュメントやらモックサーバーを作ってくれるものらしい。めっちゃ便利そうだけど、ちゃんと使うのにはそれなりに仕組みを整える必要がありそうで手を出していない。 autodoc の作者の r7kamura さんのブログにも書いてある通り、 autodoc の便利なところは以下だと思う。

実際にアプリが生成した内容からドキュメントを生成するため、実装とドキュメントの乖離が少なく抑えられる。 また、テストを書くことの見返りが増えるため開発者がテストを書くのを推進しやすい。

全てがJSONになる - ✘╹◡╹✘

autodoc で master ブランチへの Merge をトリガーにしてドキュメントを自動生成するというポリシーでは、 B/E 側の作業中に F/E の人が API のドキュメントを見られなくて不便だという問題は確かに存在する。しかし JSON Schema で事前に仕様を固めて実装前にモックサーバーやドキュメントを提供できたとして、果たして事前に決めたとおりに B/E も F/E も実装できるのだろうか。きっと作っていく途中で「やっぱりアレはコレに変えたい」みたいのが双方から出てくると思う。

↑の r7kamura さんの記事では他に外の API をモックするダミーサーバーを JSON Schema で作ったりしてる。確かにすでに仕様が固まった外部の何かをモックするサーバーのセットアップなどには便利なのかもしれない。ただいまのところは autodoc での後追いドキュメント自動生成で自分は事足りるかなという感じがしている。

| @ブログ

このブログ( Lokka )の DB は MySQL を使っている。 MySQL のバージョンは 5.7 なので、 column encoding を utf8mb4 、 collation を utf8mb4_general_ci とかにしておけば emoji を使えるかと思ってたけど使えなかった。長らく DataMapper が 対応してないかと思ってたけどリポジトリを覗いてみたら随分前に対応されてた。どうも Lokka 側に問題があるっぽい。

Lokka の DB 接続設定は dsn を URL として記載するようになっている。 DataMapper は ActiveRecord のように Hash オブジェクトを DB 接続の引数に渡せるので、 encoding: 'UTF-8-MB4' をオプションとして渡せるようにしてやればよいっぽいが、いまの Lokka のコードでは encoding オプションを指定できないのでこれではダメっぽい。

ちょっと Lokka 側のコードをいじって encoding オプションを渡すようにしてみたところ無事 emoji が使えるようになったので lokka/lokka に Pull Request 出しておこう ⌨️