rakeとかでwarning: already initialized constant Rake::VERSIONみたいなエラーが出た

世界の総水産量は年間約9000万トン(養殖含め)だそうですが、世界の鯨が1年に食べる魚類の量は2.8億トンから5億トンになると試算されており(1)、人間の数倍もの水産物が鯨によって消費されているそうです。ヒトの生物量でもせいぜい3億トンかそこら(50kg*70億人=3.5億トン)ですから、すごい量です。とはいえオキアミや微生物など水産物として競合しないものもあるので単純な比較はできませんが。

さて、rails開発していて、見たことのないこんなエラーに遭遇しました。

% rake -T
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:2: warning: already initialized constant Rake::VERSION
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:2: warning: previous definition of VERSION was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:5: warning: already initialized constant Rake::Version::MAJOR
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:5: warning: previous definition of MAJOR was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:5: warning: already initialized constant Rake::Version::MINOR
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:5: warning: previous definition of MINOR was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:5: warning: already initialized constant Rake::Version::BUILD
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:5: warning: previous definition of BUILD was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:5: warning: already initialized constant Rake::Version::OTHER
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:5: warning: previous definition of OTHER was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:7: warning: already initialized constant Rake::Version::NUMBERS
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/version.rb:7: warning: previous definition of NUMBERS was here
WARNING: Possible conflict with Rake extension: String#ext already exists
WARNING: Possible conflict with Rake extension: String#pathmap already exists

...(つらつら続きます)...

/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/task_arguments.rb:107: warning: already initialized constant Rake::EMPTY_TASK_ARGS
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/task_arguments.rb:107: warning: previous definition of EMPTY_TASK_ARGS was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/invocation_chain.rb:54: warning: already initialized constant Rake::InvocationChain::EMPTY
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/invocation_chain.rb:54: warning: previous definition of EMPTY was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/early_time.rb:20: warning: already initialized constant Rake::EARLY
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/early_time.rb:20: warning: previous definition of EARLY was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/late_time.rb:16: warning: already initialized constant Rake::LATE
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/late_time.rb:16: warning: previous definition of LATE was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/backtrace.rb:3: warning: already initialized constant Rake::Backtrace::SYS_KEYS
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/backtrace.rb:3: warning: previous definition of SYS_KEYS was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/backtrace.rb:4: warning: already initialized constant Rake::Backtrace::SYS_PATHS
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/backtrace.rb:4: warning: previous definition of SYS_PATHS was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/backtrace.rb:7: warning: already initialized constant Rake::Backtrace::SUPPRESSED_PATHS
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/backtrace.rb:7: warning: previous definition of SUPPRESSED_PATHS was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/backtrace.rb:11: warning: already initialized constant Rake::Backtrace::SUPPRESSED_PATHS_RE
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/backtrace.rb:11: warning: previous definition of SUPPRESSED_PATHS_RE was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake/backtrace.rb:15: warning: already initialized constant Rake::Backtrace::SUPPRESS_PATTERN
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/backtrace.rb:15: warning: previous definition of SUPPRESS_PATTERN was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake.rb:69: warning: already initialized constant FileList
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake.rb:69: warning: previous definition of FileList was here
/Users/foo/bar/hoge/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/lib/rake.rb:70: warning: already initialized constant RakeFileUtils
/Users/foo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake.rb:70: warning: previous definition of RakeFileUtils was here

自分の場合はrbenvを使っていたので、rubyの処理系を一旦消した上でもっかい入れ直し、さらにvendor/bundleを消したらなおりました。

% rbenv uninstall 2.4.1
% rbenv install 2.4.1
% rbenv rehash
% rm -rf vendor/bundle
% gem install bundler
% bundle install
% rake -T
rake about                              # List versions of all Rails frameworks and the environment
rake app:template                       # Applies the template supplied by LOCATION=(/path/to/template) or URL
rake app:update                         # Update configs and some other initially generated files (or use just update:configs or update:bin)
rake assets:clean[keep]                 # Remove old compiled assets
rake assets:clobber                     # Remove compiled assets
...

原因は、rails newしたあと一回普通にbundle installしたのですが、その後でbundle install –path=vendor/bundleをしてしまったため、rbenvの下のrubyの中にgemがどばどば入ったのと、railsアプリ内のvendor/bundleの下に入ったgemが一緒にロードされてしまったためです。

上の対処法では両方消した形になりますが、本当は片方だけで良いはず。
自分はついでに.bundle/configの中からBUNDLE_PATHを除去し、vendor/bundleではなくruby処理系の中に入れることにしました(自分のruby使用頻度だとそれであんま困らないので)

備忘録でした。

MacにRMagickが入らなくてつらい人へ

大型淡水魚のピラルクーはとても特徴的な大きな鱗をもち、その硬さもあって、靴べらなどに利用されることさえあるのだそうです。

さて、Mac(El Capitan)にGemでRMagickを入れようとしたとき、

% gem install rmagick

こんなエラーで死にました。

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

    /Users/hogehoge/.rbenv/versions/2.2.0/bin/ruby -r ./siteconf20170218-46131-rbo6q5.rb extconf.rb
checking for clang... yes
checking for Magick-config... no
checking for pkg-config... yes
checking for outdated ImageMagick version (<= 6.4.9)... no
checking for Ruby version >= 1.8.5... yes
checking for stdint.h... yes
checking for sys/types.h... yes
checking for wand/MagickWand.h... no

Can't install RMagick 2.15.4. Can't find MagickWand.h.
 *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=/Users/hogehoge/.rbenv/versions/2.2.0/bin/$(RUBY_BASE_NAME)

extconf failed, exit code 1

Gem files will remain installed in /Users/hogehoge/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/rmagick-2.15.4 for inspection.
Results logged to /Users/hogehoge/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/extensions/x86_64-darwin-15/2.2.0-static/rmagick-2.15.4/gem_make.out

An error occurred while installing rmagick (2.15.4), and Bundler cannot continue.
Make sure that `gem install rmagick -v '2.15.4'` succeeds before bundling.

基本的にはこちらで書かれているような、pkg-config周りをちゃんとするようというようなのがよく引っかかります。
Mac OS X環境にrmagickをインストールする決定版 – Qiita

僕の場合はまた違う現象でした。
結論から言うと、新しいImageMagick7.xがシステムに入ってるとダメで、6.x系列を入れる必要がありました

% brew uninstall --ignore-dependencies --force imagemagick
% brew install imagemagick@6 && brew link imagemagick@6 --force
% gem install rmagick

この答えは、こちらのStackOverflowで発見しました。
ruby – RMagick installation: Can't find MagickWand.h – Stack Overflow

こういうので時間を溶かすのがいちばんつらいですね。

YouTube Data API V3とgoogle-api-ruby-client gemでYouTube動画検索

この度2017年1月1日を迎えました。神戸港開港150年であり、神戸港に建つ巨大な魚のオブジェ「フィッシュ・ダンス」が公開されて30年でもあります。神戸港には縁もゆかりもありません。

さて、自作サービス(Rails製)の裏側においてYouTubeでの動画検索をしたいと考えていて、YouTube Data API(V3)およびそれを叩くGoogle製のgoogle-api-ruby-client gemを使うことにしました。
しかしながらこのGemは2016年初頭に出た0.9によって大きな変更が入り、公式のものを含めネット上で閲覧可能なサンプルのほとんどが参考にならない状況になっています。
自分でも少し苦労したので、かんたんにメモ。

情報は2017年1月1日JST現在、Gemは0.9.20です。

Continue reading

Mac+rbenvでconfigure: error: something wrong with LDFLAGS=”…”とか言われた時のいち解法

今日は多摩川花火大会をやっていまして、今まさに自室の窓の外から花火が見え音が聞こえる中こんなブログ記事を書いています。ダイナマイト漁で捕獲でもされている気分です。

さて、El Capitanでrbenvを使っていて、rbenv installをしようとしたときこんなかんじのエラーが出てちょっとハマりました。

% rbenv install 2.2.3
Downloading ruby-2.2.3.tar.bz2...
 -> https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.3.tar.bz2
Installing ruby-2.2.3...

BUILD FAILED (OS X 10.11.6 using ruby-build 20160602-31-gf085feb)
...
checking whether LDFLAGS is valid... no
configure: error: something wrong with LDFLAGS="-L/Users/(myname)/.rbenv/versions/2.2.3/lib "
make: *** No targets specified and no makefile found.  Stop.

このエラーはいろんな状況で起こるようです。autoconfのログを見てもよくわかりませんでした。
ググってみると、やれrbenvプラグインのruby-buildが古いだの、やれ特定バージョンのrubyで起こるだの、gccを消せだの、brew doctorしろだの、手動ビルドしろだの…などなど、さまざまな原因でこの現象が起こるようですが、
僕の場合はLIBRARY_PATH環境変数に書いてあったディレクトリが実在しなかったことが原因でした。

つまり、

LIBRARY_PATH=/usr/local/lib:/usr/lib:/lib

だったのですが、僕の使用するEl Capitan機に/libというディレクトリは存在しなくて、それが結局rbenv installのエラーを引き起こしていました。
なので、環境変数から/libを除くか、/libをmkdirすると、rbenvのビルドは死ななくなりました。
エラーメッセージ自体からはそのことを直接は読み取れなかったので、ハマりました。

ご参考までに。

Railsログ中の”Rendered xxxx.html.erb”を静かにさせたかった

お魚の可食部の重量に占める割合というのは、もちろん種類によって変わってくるのですが、しらすなどではもちろん100%、一方でスズキなどでは40%程度と低いようです。ただしらすは稚魚ですので、成魚と比べるのは厳しい判定かも。
さて、Rails(Rack)がGETリクエストひとつに対して吐いてくれるアクセスログは、下記のような雰囲気になります。
このうち、xxxx.html.erbをレンダリングしたよ!という情報は特に本番ではあまり必要でないことが(自分の状況では)多いので、これを静かにしてしまいたくなりました。

[2016-08-02T15:58:51](pid: 23067) INFO  -- Started GET "/" for 127.0.0.1 at 2016-08-02 15:58:51 +0900
[2016-08-02T15:58:51](pid: 23067) INFO  -- Processing by HomeController#index as HTML
[2016-08-02T15:58:52](pid: 23067) INFO  --   Rendered home/_user_objects.html.erb (5.9ms)
[2016-08-02T15:58:52](pid: 23067) INFO  --   Rendered home/index.html.erb within layouts/application (11.4ms)
[2016-08-02T15:58:52](pid: 23067) INFO  --   Rendered application/_navbar.html.erb (173.5ms)
[2016-08-02T15:58:52](pid: 23067) INFO  --   Rendered application/_image_box.html.erb (0.3ms)
[2016-08-02T15:58:52](pid: 23067) INFO  --   Rendered application/_image_box.html.erb (1.2ms)
[2016-08-02T15:58:52](pid: 23067) INFO  --   Rendered application/_image_box.html.erb (0.9ms)
[2016-08-02T15:58:52](pid: 23067) INFO  --   Rendered application/_image_box.html.erb (0.8ms)
[2016-08-02T15:58:52](pid: 23067) INFO  --   Rendered application/_image_box.html.erb (1.0ms)
[2016-08-02T15:58:52](pid: 23067) INFO  --   Rendered application/_header.html.erb (175.5ms)
[2016-08-02T15:58:52](pid: 23067) INFO  --   Rendered application/_footer.html.erb (0.5ms)
[2016-08-02T15:58:52](pid: 23067) INFO  -- Completed 200 OK in 650ms (Views: 52.1ms | ActiveRecord: 10.0ms)

コチョナナバ: railsの部分テンプレートのログ出力を出さなくする
Hide rendering of partials from rails logs – Stack Overflow
このあたりを参考にして、

# config/environments/production.rb
MyApplication::Application.configure do
  ...
  # Not to show logs like Rendered xxxx.html.erb
  config.action_view.logger = nil
  ...
end

のように設定すると、このrenderingのログだけがいいかんじに静かになってくれます。

[2016-08-03T16:44:20](pid: 5973) INFO  -- Started GET "/" for 127.0.0.1 at 2016-08-03 16:44:20 +0900
[2016-08-03T16:44:20](pid: 5973) INFO  -- Processing by HomeController#index as HTML
[2016-08-03T16:44:21](pid: 5973) INFO  -- Completed 200 OK in 724ms (Views: 53.9ms | ActiveRecord: 9.4ms)

大変Informativeでよろしい。
以上、最初から最後まで完全に備忘録でした。

resque_specはGemfileのtest groupにのみ入れましょうねという雑なお話

季節は夏となりましたが気温の上下が激しく、体温調節が狂って風邪をひきやすそうな気候の今日このごろです。
普通、お魚は変温動物なので体温は周辺水温とほぼ全く同じ(代謝に伴うごく僅かな上昇程度)なのですが、サメの場合は5℃から15℃ほども高いのだそうです。マグロなどの高速で泳ぐ回遊魚にも同様の傾向が見られるのだそうです。そしてマグロの場合は、冷凍保存の質に影響するため比較的シビアな温度計測が求められるとのこと。
さて、Ruby on Rails (にかぎらずRuby)でのジョブキューイングにResqueを使っている際にハマったしょうもない出来事について、忘れないようにメモ。
結論から言うと、Gemfileではresque_specは:test groupだけに入れるようにしましょう、でないとジョブがキューに投入されないですよというだけの雑なお話です。

# Gemfile: Good
group :test do
  gem 'resque_spec'
end

ちゃんと、test環境のみでresque_specをアクティベートするように書きましょう。

# Gemfile: Bad!!!!!!
group :development, :test do
  gem 'resque_spec'
end
# or
gem 'resque_spec'
おこったこと

例えば上記の悪例のように、うっかり:developmentと:testにresque_specを書いてしまった場合。

Development環境(RAILS_ENV=development)でローカルPC上でresqueを走らせてジョブキューイングを実際に試そうとしています。
下記のようにちゃんとResqueを起動し、キューを監視してくれているというのに、

% QUEUE=default bundle exec rake resque:work
 ** [12:00:30 2016-06-07] 25304: Starting worker xxxx:25304:default
 ** [12:00:30 2016-06-07] 25304: Registered signals
 ** [12:00:30 2016-06-07] 25304: Running before_first_fork hooks
 ** [12:00:30 2016-06-07] 25304: Checking default
 ** [12:00:30 2016-06-07] 25304: Sleeping for 5.0 seconds
 ...

例えばコンソールからジョブをつっこんでも、

% rails c
> Resque.enqueue(HardWorker)

resqueサイドのほうが全く反応してくれず、queueingされていない様子…。
redisをのぞいても何かが入っている様子はない。
Resque.infoを見ても何も処理された様子がない。

というような事態になります。
なりました。
2時間ぐらい無駄にしました。つらい。

Gemを入れるとき、あまり深く考えずにdevelopmentとtest両方に入れちゃうことは皆さんままあるようなので(自分も含め)、全般的に少し注意したほうがいいポイントになりそうです。

ちなみに

じゃあ同じ論理で、rspecはテストに使うんだしrspecもtestの中だけに書いてあればいいよね!と思ったらそれも落とし穴だったりします。
rspec-rails は Gemfile で development グループにも入れてあげよう – blog.sorah

参考

Resqueのソース読んだり(結果的にはresque_specを見るべきであったので完全な無駄手間…)、めっちゃググったりしてやっと見つけたのがこちらのStackOverflow
redis – Rails development environment Resque.enqueue does not create jobs – Stack Overflow

resque_specのREADMEにも実はちゃんと書いてあるのですが、そもそもresque_specの不適切な設定が原因だと気づけなかったので、あとになって気づいた次第。あらためて、そもそも入れる時にちゃんと気をつけろという話。
leshill/resque_spec: RSpec matcher for Resque

やられたぜ!

公開Google CalendarからRuby+Google APIでイベントを取得

いよいよ5月も終わり季節は夏にという時期で、お魚的には鮎や鯵などが旬を迎えるという頃合いになっています。今の時期に魚や野菜を食べて夏バテに備えたいものです。

さて、Google Calendarからイベントを自動で取ってきたいということがあったので、Rubyでやってみました。

(Calendarにかぎらず)Google各種サービスのAPIは公式のGemが公開されており、これを使うのが最も簡単です。
Ruby Quickstart | Google Calendar API | Google Developers
Class: Google::Apis::CalendarV3::Calendar — Documentation for google/google-api-ruby-client (master)

さらに、各ブログなどで既にGoogle Calendar API (v3)を使う方法についてはよくまとまっています。
RubyでGoogleカレンダーの情報を引き抜いてみる – 鶏頭のプログラム
rubyにてgoogleカレンダーの情報を取得する – Qiita

この辺りの情報は、基本的に自分の(非公開な)カレンダーを取ってくるものですが、今回僕がやりたいことは、「他人が公開カレンダーのイベントの情報を持ってくる」ことで、
例えば僕個人の予定でなく人力アジャイルイベントカレンダー powered by 名古屋アジャイル勉強会にあるイベントをAPI経由で取ってくる、というようなことをやろうとしています。

上記の参考資料はいずれも、自分のカレンダーにアクセスするためのもので、そのためにOAuthなどのプロセスが必要となっていますが、公開されているカレンダーのイベントを取得するのはもう少しシンプルです。

ということで、ここからはその方法。

Continue reading

PoltergeistのURL Blacklisting/Whitelistingを活用してAjaxスクレイピングを速くする

よくお魚の「脂ののりが良い」といいますが、実際例えば体脂肪率という形で計測するとどうなるのでしょうか。実はそれを測定するためにフィッシュアナライザ™という製品があり、人間用の体脂肪率計と同じ仕組みで非侵襲的な計測が可能なんだそうです。アジなどでは10%とかいうオーダーの数字とのことで、意外とスリム。

さて、Rubyを使ってWebスクレイピングをするときは、個人的にはNokogiriをよく使っています。
通常はNet::HTTPでとってきたHTMLをそのままNokogiriにぶち込めばOKなのですが、非同期での描画を行うAjaxのサイトの解析はそのままではできません。
そこで、PhantomJSのRubyフロントエンドであるPoltergeistをドライバとしてCapybaraを組み合わせると、深く考えることなく静的ページと同じ感覚で解析ができてしまいます。要は、見えないところでまるっとブラウザを動かしてしまって、DOMから仮想的なHTMLを生成させちゃってNokogiriにぶち込む…というようなアプローチです。

require 'nokogiri'
require 'capybara'
require 'capybara/poltergeist'

def wait_for_ajax(session)
  # https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara
  Timeout.timeout(Capybara.default_wait_time) do
    return if session.evaluate_script('jQuery.active').blank?
    loop until session.evaluate_script('jQuery.active').zero?
  end
end

def access(url)
  Capybara.register_driver(:poltergeist) do |app|
    Capybara::Poltergeist::Driver.new(app, {
      js_errors: false  #JSに問題があったとき例外を吐かせない。スクレイピングの際は常にfalseがいいです。
    })
  end
  s = Capybara::Session.new(:poltergeist)
  s.visit(url)
  wait_for_ajax(s)
  s
end

def get_html(url)
  s = access(url)
  html = s.html
  s.reset!
  s.driver.quit
  html
end

#あとは普通にNokogiriにぶっこんでゴリゴリやってくだけ!
page = Nokogiri::HTML.parse(get_html(url))
page.css('body')

ちなみに、Capybara::Sessionのインスタンス(accessメソッドの戻り値)に対して、ちょうどfeature specで書くようなマッチャを使ってページに対する操作ができるので、例えば「ここをクリックすると出てくる情報を解析したい」という作業も簡単にできます。

s = access(url)
s.click('Submit')
s.find('.text')  #findを呼ぶとマッチする要素が現れるまで再描画を(デフォルトで2秒間)待ってくれます。
s.html #これをNokogiriにぶち込めばOK

後ろでブラウザがまるごと動いていることの弊害と言ってはあれですが、スクレイピングにあたっては必要ではない情報も非同期でゴリゴリ取ってくることになり、負荷や時間などが増大します。
ということで、そういった必要でない情報は取ってこないようにして少しでも時間と相手サーバの負荷を低減する方法について。

Continue reading

Sidekiq::Schedulerで動的にタスクをぶち込む

お魚の中でもメバルやタチウオなどは夜行性のものとして有名で、特にタチウオなどは昼間沖の深めにいて夜には岸辺の浅いところにくるなど、かなり規則的なパターンで活動するのだそうです。

さて、rubyで野良アプリ書くときバッチ処理エンジンにはSidekiqを好んで使っています。
Sidekiqには日次処理などを簡単に行うため(*1)にSidekiq::Schedulerという拡張gemがあり(*2)、任意の定義済みワーカを例えばcronライクに繰り返し実行することができます。
基本的には各ワーカの実行条件やパラメータをYAMLの形で全て静的に記述し起動時に読み込むというスタイルなのですが、時には動的にワーカをスケジュールに突っ込みたいことがあります。

Sidekiq::Schedulerはもちろんそれもサポートしています。
ただmoove-it/sidekiq-schedulerの説明がいまいちだったので、備忘録も兼ねてまとめました(*3)。

Continue reading

嫁の顔忘れてもTimecop.returnは忘れないでねという話

夜釣りでは初心者向けの狙い目といえばアナゴなんだそうです。岸壁で釣れちゃうんだとか。ウナギはお高いのでアナゴで一杯、とかあるんだろうか。
さて、Rubyでいろいろ開発していて時間(時刻・期間…)が絡むテストをするときTimecopが便利で広く使われます。
travisjeffery/timecop – GitHub

# 現在時刻を変える
> Timecop.travel(Date.parse('1998/12/1'))
=> 1998-12-01 00:00:00 +0900
> Date.today
=> Tue, 01 Dec 1998
 
# 時間の進行を止める
> Timecop.freeze(Time.parse('1998/12/1 0:00:00'))
=> 1998-12-01 00:00:00 +0900
> sleep(10)
> Time.now
=> 1998-12-01 00:00:00 +0900  # it's still 0:00:00
 
# 時間の進行を早める
> Timecop.travel(Time.parse('1998/12/1 0:00:00'))
> Timecop.scale(3600)
> sleep(2)
> Time.now
=> 1998-12-01 02:00:19 +0900  # it took 2 hours although only slept for 2 seconds

便利ですね。
時間をいじくるので、

  • タイムゾーン周りをミスってないか
  • DBMSのタイムスタンプやredisのexpireのようにruby処理系の時間を使わないものでは効かない

こんなかんじで注意が必要なのは当然なのですが、一番しんどいのはTimecopで変えた時間を戻し忘れたときの挙動不審なかんじでしょうか。
テストでTimecopを使う場合、このテのミスをするとたいてい別のテストケースに影響しますね。順序依存で効いてくるし煽りを食ったテストケース自体は何も悪く無いことがほとんどなので、心神耗弱状態になります。

  • なぜかCapybaraのCookieが取れなくなっている
  • redisではうまくいくのにredis_mockでテストすると想定外の挙動になる
  • Timeoutが狂う

こんなことで時間をムダにするのはあほらしいので、Timecop.returnを呼ぶのは絶対に忘れないようにしましょう。

> Timecop.travel(Time.parse('1998/12/1 0:00:00'))
> Timecop.return
> Date.today
=> Sun, 20 Dec 2015

「忘れないでね」「気をつけてね」はものごとの解決策としては最悪な思考停止なので、
Rails/RSpecをお使いなら、spec/spec_helper.rbにて

config.after(:each) do
  Timecop.return
end

なる設定をしておくべきです。
また、別の安全策として、Timecopのsafe_modeといい、Timecopを使うときはblockを与えることを強制し、block末尾でreturnが自動で行われるようにする仕組みがあります。
blockを渡さずに呼ぶと例外が飛ぶので、すぐに気づきます。

> Timecop.safe_mode = true
> Timecop.travel(Time.parse('1998/12/1 0:00:00'))
Timecop::SafeModeException: Safe mode is enabled, only calls passing a block are allowed.
...
> Timecop.travel(Time.parse('1998/12/1 0:00:00')) do
>   Date.today
> end
=> Tue, 01 Dec 1998
> Date.today
=> Sun, 20 Dec 2015

Rails/RSpecのみで使うなら、spec_helperのafterでケアするのがシンプルにして安全十分かと。
テスト以外でも使うなら、safe_modeにするようinitializerなどで設定しておくほうが安全そうです。

全部Githubに書いてあるんですけども、1ヶ月近くこれにハマってしまった自分が本当にあほらしかったので忘れないように…。