404などのページをassets pipelineを使って作れるgakubuchi gemを使っています。

capistranoでデプロイする時、これのassets:precompileで落ちてデプロイできてないことがよくある。(落ちないこともある)

もう一度デプロイすると動くので騙し騙し使っている。デバッグしないとなあ・・・。

Tasks: TOP => deploy:assets:precompile
(See full trace by running task with --trace)
The deploy has failed with an error: Exception while executing as root@app1.kowabana.jp: rake exit status: 1
rake stdout: I, [2016-09-16T10:01:36.563918 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/blocks-25f0ada07701275b817165481ea227bf448e4b33b160e18e4e4e5ff988e4ec06.css
I, [2016-09-16T10:01:36.565468 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/blocks-25f0ada07701275b817165481ea227bf448e4b33b160e18e4e4e5ff988e4ec06.css.gz
I, [2016-09-16T10:01:48.018613 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/application-ed4db3deeeed188beda19cdd3571fffe28eac2adfd49a4c948d14dee05076639.js
I, [2016-09-16T10:01:48.019534 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/application-ed4db3deeeed188beda19cdd3571fffe28eac2adfd49a4c948d14dee05076639.js.gz
I, [2016-09-16T10:01:56.640992 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/application-36e5ebe3ef6a536d04cf635d1343bbfb50cd9c2de9b70bec0d72b0469565bf1c.css
I, [2016-09-16T10:01:56.641414 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/application-36e5ebe3ef6a536d04cf635d1343bbfb50cd9c2de9b70bec0d72b0469565bf1c.css.gz
I, [2016-09-16T10:01:56.804354 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/404-f5b8eac98ade00d0f719281af5b7b9103e7403700bda2ea175fefb8fff1ccfc5.html
I, [2016-09-16T10:01:56.805071 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/404-f5b8eac98ade00d0f719281af5b7b9103e7403700bda2ea175fefb8fff1ccfc5.html.gz
I, [2016-09-16T10:01:56.848952 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/406-67bca5a0e35c21ad811db2b88cca0ea5537cf59d85269977c274cb94dee913aa.html
I, [2016-09-16T10:01:56.849368 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/406-67bca5a0e35c21ad811db2b88cca0ea5537cf59d85269977c274cb94dee913aa.html.gz
I, [2016-09-16T10:01:56.905607 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/422-ef00dd8611c3e3d04be35eb07806ac98bad01d2e91f1777e01f7bbc340849e49.html
I, [2016-09-16T10:01:56.906426 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/422-ef00dd8611c3e3d04be35eb07806ac98bad01d2e91f1777e01f7bbc340849e49.html.gz
I, [2016-09-16T10:01:56.962076 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/500-cf3665c336cdd9870de6ce60459b74d0d4280ac369f48ef590922fbac639673e.html
I, [2016-09-16T10:01:56.962528 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/500-cf3665c336cdd9870de6ce60459b74d0d4280ac369f48ef590922fbac639673e.html.gz
rake aborted!
Errno::ENOENT: No such file or directory @ rb_sysopen - /srv/kowabana.jp/releases/20160916010102/public/assets/406-1a8f57ba17cdbf23ab495f461eea0f1e844b30e0789aab4c933385017f437c27.html
/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/gakubuchi-1.2.2/lib/gakubuchi/fileutils.rb:8:in `copy_p'
/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/gakubuchi-1.2.2/lib/gakubuchi/task.rb:15:in `block in execute!'
/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/gakubuchi-1.2.2/lib/gakubuchi/task.rb:10:in `each'
/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/gakubuchi-1.2.2/lib/gakubuchi/task.rb:10:in `execute!'
/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/gakubuchi-1.2.2/lib/tasks/after_precompile.rake:3:in `block in <top (required)>'
/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/rake-11.2.2/exe/rake:27:in `<top (required)>'
/usr/local/rbenv/versions/2.3.1/bin/bundle:23:in `load'
/usr/local/rbenv/versions/2.3.1/bin/bundle:23:in `<main>'
Tasks: TOP => assets:precompile
(See full trace by running task with --trace)
rake stderr: Nothing written


** DEPLOY FAILED
** Refer to log/capistrano.log for details. Here are the last 20 lines:


 DEBUG [3390349e]   I, [2016-09-16T10:01:56.905607 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/422-ef00dd8611c3e3d04be35eb07806ac98bad01d2e91f1777e01f7bbc340849e49.html

 DEBUG [3390349e]   

 DEBUG [3390349e]   I, [2016-09-16T10:01:56.906426 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/422-ef00dd8611c3e3d04be35eb07806ac98bad01d2e91f1777e01f7bbc340849e49.html.gz

 DEBUG [3390349e]   

 DEBUG [3390349e]   I, [2016-09-16T10:01:56.962076 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/500-cf3665c336cdd9870de6ce60459b74d0d4280ac369f48ef590922fbac639673e.html

 DEBUG [3390349e]   

 DEBUG [3390349e]   I, [2016-09-16T10:01:56.962528 #30643]  INFO -- : Writing /srv/kowabana.jp/releases/20160916010102/public/assets/500-cf3665c336cdd9870de6ce60459b74d0d4280ac369f48ef590922fbac639673e.html.gz

 DEBUG [3390349e]   

 DEBUG [3390349e]   rake aborted!

Errno::ENOENT: No such file or directory @ rb_sysopen - /srv/kowabana.jp/releases/20160916010102/public/assets/406-1a8f57ba17cdbf23ab495f461eea0f1e844b30e0789aab4c933385017f437c27.html

/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/gakubuchi-1.2.2/lib/gakubuchi/fileutils.rb:8:in `copy_p'

/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/gakubuchi-1.2.2/lib/gakubuchi/task.rb:15:in `block in execute!'

/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/gakubuchi-1.2.2/lib/gakubuchi/task.rb:10:in `each'

/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/gakubuchi-1.2.2/lib/gakubuchi/task.rb:10:in `execute!'

/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/gakubuchi-1.2.2/lib/tasks/after_precompile.rake:3:in `block in <top (required)>'

/srv/kowabana.jp/shared/bundle/ruby/2.3.0/gems/rake-11.2.2/exe/rake:27:in `<top (required)>'

/usr/local/rbenv/versions/2.3.1/bin/bundle:23:in `load'

/usr/local/rbenv/versions/2.3.1/bin/bundle:23:in `<main>'

Tasks: TOP => assets:precompile

(See full trace by running task with --trace)

とりあえず日本の祝日のデータのyamlのみを含んだ holiday-jp/holiday_jp リポジトリを作りました。

https://github.com/holiday-jp/holiday_jp

従来のgemのreposは holiday-jp/holiday_jp-ruby になります。

holiday-jp/holiday_jp-ruby: Japanese holiday.

holiday_jp-rubyとかholiday_jp-phpとか作ってgit submoduleでyamlのrepoを取り込む感じで。

RubyKaigi中は過去に手をかけられてなかった自分のgemをメンテする絶好のタイミング。

pull requestいただいたmergeしてるけどrubygemsにpushしてないgemがよくあるのでこの機会にリリース。

holiday_jp | RubyGems.org | your community gem host

organizationに移る前に、色々PRを貰ってたのでリリースしました。祝日に山の日が増えてます。

RubyKaigi 1日目、各発表は他所のレポートをみていただくとして、Official Partyはやはり大事ですね。

@k1LoWさんとお話させていただく。

holiday_jpをwootheeみたいにorganization作って、言語非依存のyml(各言語用ライブラリではgit submoduleで読み込む)だけにしてはどうかという話。

それがいいですね。今夜やります。

やりました。

holiday-jp

@morygonzalezさんとお話。

  • Lokkaを今までのしがらみすてて作りなおそう。
  • 2人しかいないのにLokkaKaigiやって沢山人いる風にみせよう。
  • 福岡ダンボール問題。
  • リモートワーク手放しで賞賛するな問題。

ステッカー貰いました。

ss

@toyoshiさんとお話。

  • misocaのチーム構成やマネジメントの話。
  • 企業と連携の仕方。
  • 名古屋は紹介が重い話。(ちょっと親しいぐらいでは紹介しない問題)

僕らも今企業向けのサービスを作っているところなので非常に参考になる話をお聞きしました。

インターン、元インターンの方々とご挨拶。毎年のことですがリモートでやっててはじめましての人も多い。

しょうもない俺達と長く話さず沢山の知らない人にどんどん声をかけようという話。

今回は京都なので5人でAirbnbで一軒家を借りて前乗りしました。

おばあちゃんの家のようなカントリーサイドのティピカルジャパニーズハウスです。

https://gyazo.com/91aad256abf30d78b504f0582e131567

https://gyazo.com/2d1070b02ab578ab03891a0681c0816c

https://gyazo.com/d9ee44f8714982655d2edabfefb7764e

明日に遅れないようにAppleの発表は見ずに寝ることにします。

長いことrailsばっかりやってると他人のコードを「Railsのレール(流儀)にどれだけ乗れてるか、どれだけ流行りの書き方してるか」だけで判断しがち。

見慣れない書き方だったり、他の言語っぽい書き方だったりするとボロカスに評価しているのを見かける。

Railsに浸かり過ぎて、Railsっぽくないもの=悪。レールから外れている=ゴミ。そういった判断は楽だしRailsプロジェクトにおいては大抵あってる(Railsを理解するのを面倒臭がっているゴミコード)んだけど、対象のコードが持つ価値(どんなことをどういう方法で解決してるのか)を判断する力が衰える気がする。

Railsのレールや最近の流行りの書き方とは違うけど、一貫性のあるコードだったり、不具合が出づらく変更に強いコードってのはある。

特にRails経験は浅いけど他の言語・フレームワークに習熟してる人のコードにそういうものが多い。

手癖でやってるとそういう筋肉が落ちる。自戒を込めて。

railsでコメント数をどう実装するかで悩んでいます。

https://gyazo.com/c7ac20038c53ad302091f718fd948d15

ニコ動だとこんな感じ。

https://gyazo.com/a124a4bbf487715768730ec4aea9d810

怖話でもこんな感じでコメント数/再生数などを保存しています。

一定以上の数になると素朴な処理では速度的に無理が来ます。

怖話での実装方法

怖話でのカウント数の実装の歴史。

  1. 何もせずDBに1レコードずつ保存時代。
  2. 一覧ページなどが重いのでrailsのcounter cahcheを使う。
  3. コメントや閲覧はpolymorphic関連なのでcounter cacheが対応してない。conditional_counter_cacheを使う。
  4. 削除に時間がかかり過ぎる。 ← イマココ

一覧の表示はconditional_counter_cacheで大丈夫なのですが、削除時の処理がかかりすぎてタイムアップする問題がでてきました。

削除時の問題

ここではわかりやすく閲覧数ではなくコメント数で説明します。

コメント数が1万件ある話を削除すると下記のような処理が走ります。

  1. storyを削除する。
  2. dependent: :destroyで依存するコメント1が自動的に削除される。
  3. コメントが削除されたのでstoryのcomments_countが-1でUPDATEされる。
  4. dependent: :destroyで依存するコメント2が自動的に削除される。
  5. コメントが削除されたのでstoryのcomments_countが-1でUPDATEされる。
  6. 以下1万回繰り返し

1件につき100msだとしても1000sかかるので無理がある。

これまでの考え

  • memcacheやredisを使うのは手間なので無理が来るまで避けたい。(開発環境の構築の手間が増える)
  • ランキングで集計するので時間の情報は欲しい。
  • 一番楽な方法で実装しよう。

今の考え

  • RDBは無理がある。memcacheやredis、他のストレージもやむなし。
  • rails的に一般的な実装はなんだろう?
  • ありがちな問題なので一般的な対処方法を構築したい。

皆さんこういうのどう実装されてますか?こんな風にやってるよという方がいらっしゃったら @komagata などにメッセージいただけるとありがたいです。 :bow:

更新:railsでコメント数の実装の悩み〜解決編〜 - komagataのブログ

ぼっち演算子を使える2.3系にアップデート。

$ CONFIGURE_OPTS="--with-openssl-dir=`brew --prefix openssl` --with-readline-dir=`brew --prefix readline`" rbenv install 2.3.1

railsのプロジェクトでbundleしたらeventmachineのインストールでコケた。

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/eventmachine-1.0.7/ext
/usr/local/var/rbenv/versions/2.3.1/bin/ruby -r ./siteconf20160518-21178-1i9l3os.rb extconf.rb
checking for rb_trap_immediate in ruby.h,rubysig.h... no
checking for rb_thread_blocking_region()... no
checking for ruby/thread.h... yes
checking for rb_thread_call_without_gvl() in ruby/thread.h... yes
checking for inotify_init() in sys/inotify.h... no
checking for __NR_inotify_init in sys/syscall.h... no
checking for writev() in sys/uio.h... yes
checking for rb_thread_fd_select()... yes
checking for rb_fdset_t in ruby/intern.h... yes
checking for rb_wait_for_single_fd()... yes
checking for rb_enable_interrupt()... no
checking for rb_time_new()... yes
checking for sys/event.h... yes
checking for sys/queue.h... yes
checking for clock_gettime()... no
checking for gethrtime()... no
creating Makefile

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/extensions/x86_64-darwin-15/2.3.0-static/eventmachine-1.0.7/mkmf.log

current directory: /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/eventmachine-1.0.7/ext
make "DESTDIR=" clean

current directory: /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/eventmachine-1.0.7/ext
make "DESTDIR="
compiling binder.cpp
In file included from binder.cpp:20:
./project.h:116:10: fatal error: 'openssl/ssl.h' file not found
#include 
         ^
1 error generated.
make: *** [binder.o] Error 1

make failed, exit code 2

Gem files will remain installed in /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/eventmachine-1.0.7 for inspection.
Results logged to /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/extensions/x86_64-darwin-15/2.3.0-static/eventmachine-1.0.7/gem_make.out

下記を参考にして設定したらいけました。

$ bundle config build.eventmachine --with-cppflags=-I$(brew --prefix openssl)/include
参照:Ruby2.3.0 igaiga diary(2015-12-25)

railsらしいやり方、turbolinksともマッチするjavascriptの書き方ができるturboctrlというgemをリリースしました。

komagata/turboctrl

これは何?

/posts/1にアクセスしたらapp/assets/javascripts/controllers/posts_controller.js.coffeeにあるPostsControllerクラスのshowメソッドが呼ばれるというものです。
# app/assets/javascripts/controllers/posts_controller.js.coffee:
class @PostsController
  show: ->
    console.log "Hey!"

なぜ作ったのか

「さっさとwebアプリを書きたい。」

「RailsのRailから外れる面倒なことはしたくない。」

「Railsはjsの書き方についてRailがなさすぎる。」

「Railsっぽいままjsを書きたい」

「Sprockets捨てるとかではなく、既存の仕組みのまま行きたい」

というのがモチベーションです。

「coffeeの1ファイルに1クラスだけ書く」というルールすら共有されてないと行くプロジェクト行くプロジェクトで辛いんですよね。

class @Foo

sprocketsを使う以上、上記のようなクラスの書き方しますよってとこも共通認識にしたい。

ご意見、ご要望、Issue、PR歓迎です。

検索順位のチェック

Google検索で自分のサイトがあるキーワードで何番目に来るかというのはサイトを運営する上で非常に重要なデータであり指標だと思うんですが、なかなか簡単で正しい取得方法というのが無いような気がします。

要はブラウザでシークレットアクセスで検索した時の順位がプログラムから取得できればいいんですが、Googleの検索結果ページというのは例外的に「検索エンジンからどう見えるか?」というのを無視した作りになっており(自分自身が世界一の検索エンジンだから)、超速度重視のページ構造になっています。

seleniumやcapybara-webkitやphantomjsでスクレイプするスクリプトを書いたとしてもクラス名がgとか_Rmなんでxpathやcss selectorがすぐ変わりそうで信用できません。

そこでプラグラムから取得する真っ当な方法はGoogle Custom Search APIを使って取ることだと思います。(これが本当にシークレットアクセスでブラウザから取った時と全く同じかどうかはわかりませんが、自分の実用上は誤差の範囲のように思います)

Custom Search APIは無料枠が1日100リクエストかつ1リクエスト辺り10件まで取得可能で100位までなので、1日1回だけ調べるとすると最悪10キーワードまでしか無料でできないことになります。

しかし有料といっても大した金額にはならないですし、Google検索結果は重要なデータなので怖話のダッシュボードを作るときにこのCustom Search APIを使いました。

gem作った

ただ、GoogleのAPIは扱いが面倒なので検索順位が簡単に取れるgemを作りました。

komagata/google-search_rank

$ gem install google-search_rank
client = Google::SearchRank.new(api_key: "xxxx", cse_id: "xxxx")
client.find("怖い話", %r{http://kowabana.jp/.*}) # => 3

怖話は「怖い話」で検索すると今は3番目に出てくるので3が取得できます。

Google Cloud Platform

Google Custom Search APIで検索するにはAPI KeyとCSE ID(CustomSearchEngine ID)が必要です。Google Developers Consoleから下記の要領でProjectとCustom Search Engineを作成してKeyとIDをゲットしてください。

Custom Search APIを有効にする。

公開APIのサーバーキーを作成する。

カスタム検索エンジンを作成し、カスタム検索エンジンID(CSE ID)を取得する。

Google Cloud Platformはちょっと取っ付きづらいですが、Googleの全てのAPIを統一的に扱うことができるので一度慣れれば凄く便利です。

APIを検索するAPIがあって、そこでAPIのエンドポイントを取ってきてから使うみたいな感じになっています。

認証も課金もGoogle関係の全てのサービスが共通のAPIで使えるのは超汎用的ですが、昔の個別のAPIからマイグレーションをする必要が発生した人にとってはとても煩雑に見えることでしょう。