読者です 読者をやめる 読者になる 読者になる

コモディティ化するエンジニア

組み込み系のSI屋から、Railsを扱うWeb系のベンチャーに転職した筆者が、日々ミジンコなりに情報を綴るブログ。

Rubyの Set / Array それぞれの検索速度を比較してみた

RubyではArrayよりSetの方が高速に検索(include?)できるとのことですが、実際どのぐらいの差があるか調べてみました。

実行環境

以下の通りです。

$ ruby -v          
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]

計測に使うコード

require './process_measure.rb'
require 'set'

array = Array.new
set = Set.new
10001.times { |i| array.push("#{i}") }
10001.times { |i| set.add("#{i}") }

6.times do |i|
  str = "#{i * 2000}"
  puts "-------------------- #{str} --------------------"
  measure_do { array.include?(str) }
  measure_do { set.include?(str) }
end

process_measure.rbについては、以下の記事をご参照ください。

muramurasan.hatenablog.jp

計測結果

$ ruby set_array.rb
-------------------- 0 --------------------
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000007)
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000005)
-------------------- 2000 --------------------
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000071)
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000004)
-------------------- 4000 --------------------
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000115)
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000004)
-------------------- 6000 --------------------
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000170)
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000004)
-------------------- 8000 --------------------
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000395)
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000008)
-------------------- 10000 --------------------
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000576)
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000008)

考察

Setの方が明らかに高速ですね。 Arrayは先頭から順番に検索するんでしょうか? 検索対象が後ろになればなるほど遅くなる傾向にあります。

Arrayは便利なメソッドがたくさんありますが、追加/削除/検索しかせず、値が重複しないならばSetを使う方が良いですね。

Rails EngineでCapybaraを導入しようとしてハマったこと

Rails EngineにCapybaraを導入しようとしてハマったことが二つあるので、備忘録として残しておきたいと思います。

ルーティングを認識させる

Railsアプリを普通に書いている場合は気にする必要がないのですが、Rails Engineでfeature specをいきなり動かそうとすると、visit (path)のところで以下のようなエラーが発生します。

  1) method_call_situation pages (permit WebUI) render Succsess
     Failure/Error: visit method_call_situations_path
     
     NameError:
       undefined local variable or method `method_call_situations_path' for #<RSpec::ExampleGroups::MethodCallSituationPages::PermitWebUI:0x007fe1d21b1730>
     # ./spec/features/method_call_situations_page_spec.rb:34:in `block (2 levels) in <top (required)>'

Rails Engineではspec内でルーティングを認識させる必要があります。

...
RSpec.configure do |config|
  config.include OkuribitoRails::Engine.routes.url_helpers
end
...

これで、名前付きルートが認識されるようになります。

asset precompile を行うようにする

feature spec内でRails Engine内で持っている画像等のAssetを参照しようとすると、以下のようなエラーが発生します。

1) method_call_situation pages (permit WebUI) available search function
     Failure/Error: <%= image_tag "okuribito_rails/logo.png", class: "header-logo" %>
     
     ActionView::Template::Error:
       Asset was not declared to be precompiled in production.
       Add `Rails.application.config.assets.precompile += %w( okuribito_rails/logo.png )` to `config/initializers/assets.rb` and restart your server
     # ./app/views/layouts/okuribito_rails/_header.html.erb:2:in `___sers_ym_works_okuribito_rails_dev_app_views_layouts_okuribito_rails__header_html_erb__3728936773133030127_70218317871640'
     # ./app/views/layouts/okuribito_rails/application.html.erb:11:in `___sers_ym_works_okuribito_rails_dev_app_views_layouts_okuribito_rails_application_html_erb___1363027026573178022_70218307163340'
     # ./spec/features/method_call_situations_page_spec.rb:34:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # Sprockets::Rails::Helper::AssetNotPrecompiled:
     #   Asset was not declared to be precompiled in production.
     #   Add `Rails.application.config.assets.precompile += %w( okuribito_rails/logo.png )` to `config/initializers/assets.rb` and restart your server
     #   ./app/views/layouts/okuribito_rails/_header.html.erb:2:in `___sers_ym_works_okuribito_rails_dev_app_views_layouts_okuribito_rails__header_html_erb__3728936773133030127_70218317871640'

Rails Engine単体では、asset precompileを行うように書かれていないハズなので、当たり前っちゃ当たり前です。 そこで、以下のようにengine.rbにて、asset precompileを行う設定にします。

module OkuribitoRails
  class Engine < ::Rails::Engine
    isolate_namespace OkuribitoRails

    initializer "okuribito_rails.assets.precompile", group: :all do |app|
      app.config.assets.precompile += %w(
        okuribito_rails/logo.png
      )
    end
  end
end

これで、テストが通るようになります。

なお、この対処方法が正しいかどうかは残念ながら保証しかねますが、rails_adminというgemのやり方を参考にしています。

Railsで未使用のメソッド削除を支援するEngine(gem)を作った

はじめに

先日のエントリーで、任意のメソッド呼出を監視し、任意のコードを実行するgem「Okuribito」の作成を報告しました。

muramurasan.hatenablog.jp

OkuribitoはRuby単体で動くgemで、元々は未使用のメソッドを安全に削除することを目的に作成したgemです。
が、大抵はRuby単体ではなく、Railsプロジェクトに適用されることが多いのではないかと思います。
そこで、Okuribitoを利用したRails Engineを作れば、メソッド呼出状況の一覧表示・検索などの機能もWebUIで提供できるのではないかと考え、実際に作成してみました。

github.com

このエントリでは、このRails Engineの紹介および使い方の説明をしたいと思います。

OkuribitoRailsとは

OkuribitoRailsはRailsアプリケーションにおけるメソッド呼出状況を監視し、管理(閲覧)できるようにする Rails Engine(gem) です。
言い換えれば、まだ一度も呼び出されていないメソッドを炙り出すことができ、安全にコードからメソッドを削除できるようになると言えます。

その仕組みを実現するため、コア部分の実装は Okuribito を利用しています。
なお、使い方を簡単に説明すると、次のようになっています。

  1. 監視したいメソッドを登録する(yamlを記述し、配置する)
  2. Railsアプリケーションを起動し、運用する。監視対象のメソッド呼出が行われると、DBに情報が格納される
  3. 現在のメソッド呼出状況、メソッド呼出履歴を閲覧する

ひとたび監視したいメソッドを登録すれば、2はgem側でやってくれるので、開発者は 「監視メソッドの設定 → 呼出状況のチェック」というループに注力することができます。

使い方

以下に、Railsチュートリアルのサンプルアプリにおけるメソッド呼出を監視する、という例を通じて、OkuribitoRailsの使い方を説明していきたいと思います。

インストール

まず、Gemfileに次の記述を追加してbundle installします。

gem 'okuribito_rails'

次に、各種設定ファイル配置・書き換えのためのインストールコマンドを実行します。

rails generate okuribito_rails:install

実行すると、以下のタスクが展開されます。

  • OkuribitoRailsが使うmigrationファイルの配置(db/migrate以下)
  • OkuribitoRailsのルーティング追加(config/routes.rbに自動的に追加される)
  • 設定ファイルの配置(config/initializers/okuribito_rails.rb)

migrationの実行

インストールコマンドで追加された分のmigrationを実行します。

rake db:migrate

監視したいメソッドの記述

監視したいメソッドをyamlファイルに記述していきます。(デフォルトの設定では、config/okuribito.ymlにて配置する必要があります)
以下のように、クラス名にネストしてメソッド名を記述していきます。
メソッド名を記述する際は、クオテーションで囲み、それがクラスメソッドなのかインスタンスメソッドなのかのシンボルを記述する必要があります。

User:
  - '#feed'
  - '#profile'
Micropost:
  - '.from_users_followed_by'

設定ファイルの変更(任意)

config/initializers/okuribito_rails.rbはデフォルトで以下の設定になっているので、必要に応じて変更します。

OkuribitoRails.configure do |config|
  config.setting_path = "config/okuribito.yml"
  config.once_detect = true
  config.prohibit_webui = ["test"]
  config.prohibit_observe = ["test"]
end
  • setting_path
    • 監視したいメソッドを記述したyamlのパスを書きます。
  • once_detect
    • trueにすると、メソッド呼び出し検出時の、呼出履歴の登録をRailsアプリケーション起動中に一度だけにします。(DBへinsertするオーバーヘッドを軽減させることができます)
  • prohibit_webui
    • 配列で指定した環境下で、WebUI(メソッド呼び出し状況の閲覧 等)を一切見れなくします。production環境ではWebUIを閉じたい、といった場合に対応することができます。
  • prohibit_observe
    • 配列で指定した環境下で、メソッド呼び出しの監視をしなくなります。staging環境やtest環境ではメソッド呼び出しの監視をしたくない、といった場合に対応することができます。

運用

OkuribitoRailsをインストールしているRailsアプリケーションは、以下の動作をするようになります。

  1. アプリケーション起動時、yamlファイルに記述した監視メソッドをDBに登録する。
  2. アプリケーション実行中、監視メソッドが呼び出されると、その情報をDBに登録する。

こうして蓄積された情報は、WebUIを通じて、それぞれ以下の通り閲覧することができます。

メソッド呼び出し状況の閲覧

(root)/okuribito_rails/method_call_situations にアクセスすると、以下の通り、現在のメソッド呼び出し状況を閲覧することができます。

f:id:muramurasan:20161211194554p:plain

監視中のメソッド一覧が表示され、監視開始日時と、呼出日時を確認することができます。
この例では、User#profileがまだ呼び出されていないことがわかり、今後も未呼出が続くようであれば、コードからメソッドを削除しても良いだろうと判断するこができます。

メソッド呼び出し履歴の閲覧

(root)/okuribito_rails/method_call_logs にアクセスすると、以下の通り、これまでのメソッド呼出履歴を閲覧することができます。

f:id:muramurasan:20161211194606p:plain

呼び出された日時と、その時に呼び出された箇所が記録されているため、どこのコードがまだメソッドを利用しているのか確認することができます。

課題

最低限の機能は作ることができたと思っていますが、使い勝手を良くするために、今後は以下の機能をリリースしたいと考えています。

  • 検索機能
    • メソッド名や、日時を条件に検索(絞り込み)できるようにする機能の提供。
  • メソッド監視日数の表示
    • メソッドの監視開始から経過した日数を表示・検索できるようにする機能の提供。
  • コード内未定義のメソッドを監視対象から排除
    • 上記の条件で、yamlファイルの内容を書き換えるコマンドの提供。
  • 呼出履歴のあるメソッドを監視対象から排除
    • 上記の条件で、yamlファイルの内容を書き換えるコマンドの提供。

おわりに

今回作ったOkuribitoRailsは、Railsアプリケーションにおけるコード整理を助けるRails Engine(gem)です。
コードが汚いのは、ただただ申し訳なさしかありませんが、機能的には有用なものが作れたのではないかと思っています。
使ってみて、もしもバグと思える動作が発生した際は、(詳細を添えて)是非、issue への登録をお願い致します!(もちろん、要望も歓迎します!)
また、機能やコードを改善するPull Requestもお待ちしています!

少しでも「いいね」と思っていただけた方は、是非、リポジトリにStarをつけていただけると幸いです \(^o^)/

LODGEはいいぞ

LODGEはいいぞ。

LODGEとは

東京ガーデンテラスにオープンしたコワーキングスペース
2016年11月にオープンしたばかり。

lodge.yahoo.co.jp

LODGEの何が良いのか

アクセスがいいぞ

最寄駅が永田町、赤坂見附という、東京のど真ん中に立地している。
コワーキングスペースといえば、渋谷や新宿に集中しているイメージがあるが、LODGEはコワーキングスペースがなかなか無いエリアに出現した。
人によっては、渋谷・新宿エリアよりもアクセスが良いのではなかろうか。

もくもくと集中できていいぞ

個人で来た人のために、パーティションで区切られたスペースがある。
この中に入ってしまえば、まさに「俺の基地」って感じがして、もくもくやるのに最高。

f:id:muramurasan:20161218103913j:plain

もちろん、グループで来た人のために、大小様々なテーブルが配置されており、ホワイトボードも豊富にあって、攻守ともにバッチリ。

f:id:muramurasan:20161218103932j:plain

写真は撮っていないが、大型ディスプレイが配置された(予約制の)スペースもあり、やろうと思えばdodosoftの輪読会もできそう。

眺めが良くていいぞ

こもった空間だと、どうしても疲れがたまりやすいもの。
その点、ロッジは開放的な作りになっており、18階という立地を活かした景色も最高。
集中するスペースと、気分を開放させるスペースが同居しており、攻守ともにバッチリ。

f:id:muramurasan:20161218103948j:plain

見ろ、人がゴミのようだ。

f:id:muramurasan:20161218104000j:plain

ハンモックなんかもある。(周りが集中して作業しているから、使うのに度胸が必要だけど)

ウォーターサーバが置いてあっていいぞ

ウォーターサーバが各所に置いてあったり、コーヒーを無料で提供していたりする。
もちろん、自動販売機も同じフロア内にあり、下まで降りればコンビニが向かいにあるので、攻守ともにバッチリ。

おわりに

LODGEはいいぞ。

Rubyで任意のメソッド呼出を検出し、任意のコードを実行できるgemを作った

はじめに

今年8月のエントリーで、任意のメソッド呼出を監視し、任意のコードを実行するgem「Okuribito」の作成を報告しました。

muramurasan.hatenablog.jp

その後、gemの機能や、使われ方の思想が結構変わったので、改めて「Okuribito」の使い方を紹介したいと思います。

Okuribitoとは

Okuribitoは任意のメソッド呼出を監視し、任意のコード実行をできるようにするRubyのgemです。
このgemを使えば、例えば、メソッド呼び出しが行われた際にログファイルに書き出すことや、Slack通知を行えるようにするということができるようになります。
また、yamlファイルによって外から監視メソッドを指定するため、コードをほぼ(※)汚すことがない、というのも本gemの目玉となっております。

※ 監視開始を指示する部分と、任意のコードを記述するブロック部分は、どうしてもコーディングする必要があります

使い方

以下のようなコードにOkuribitoを適用する例を通じて、使い方を説明していきたいと思います。

class TestTarget
  def self.deprecated_self_method
    puts "deprecated_self_method"
  end

  def deprecated_method
    puts "deprecated_method"
  end
end

TestTarget.deprecated_self_method
TestTarget.deprecated_self_method
test1 = TestTarget.new
test2 = TestTarget.new
test1.deprecated_method
test2.deprecated_method

インストール

まず、Gemfileに次の記述を追加してbundle installします。

gem 'okuribito'

監視したいメソッドの記述

監視したいメソッドをyamlファイルに記述し、配置します。
以下のように、クラス名にネストしてメソッド名を記述していきます。
メソッド名を記述する際は、クオテーションで囲み、それがクラスメソッドなのかインスタンスメソッドなのかのシンボルを記述する必要があります。

TestTarget:
  - ".deprecated_self_method"
  - "#deprecated_method"

requireの追加

以下を追記し、コードからOkuribitoを使えるようにしましょう。

require "okuribito"

Okuribitoの適用を記述

以下のように、Okuribitoのインスタンス生成時に、メソッド呼び出し検出時に実行したいコードを記述します。
applyした以降は、Okuribitoによるメソッド呼び出し監視が有効になります。

okuribito = Okuribito::OkuribitoPatch.new(once_detect: true) do |method_name, obj_name, caller_info|
  puts "#{obj_name} #{method_name} #{caller_info[0]}"
end
okuribito.apply("okuribito.yml")

渡す仮引数は以下をサポートしています。

  • method_name:メソッド名を取得できます
  • obj_name:オブジェクトを文字列に変換したものを取得できます
  • caller_info:バックトレース(メソッドの呼び出し履歴)を取得できます
  • class_name:クラス名を取得できます
  • method_symbol:クラスメソッド(.)かインスタンスメソッド(#)かの文字を取得できます

また、Okuribitoのインスタンスを生成する際、以下のオプションをハッシュで指定することができます。

  • once_detect:true の場合、Okuribitoは同じメソッドの2回目以降の呼び出しを無視してくれるようになります

運用

Okuribitoを適用した最終的なコードは以下のようになっているとします。

require "okuribito"

class TestTarget
  def self.deprecated_self_method
    puts "deprecated_self_method"
  end

  def deprecated_method
    puts "deprecated_method"
  end
end

okuribito = Okuribito::OkuribitoPatch.new(once_detect: true) do |method_name, obj_name, caller_info|
  puts "#{obj_name} #{method_name} #{caller_info[0]}"
end
okuribito.apply("okuribito.yml")

TestTarget.deprecated_self_method
TestTarget.deprecated_self_method
test1 = TestTarget.new
test2 = TestTarget.new
test1.deprecated_method
test2.deprecated_method

プログラムを実行すると、TestTarget.deprecated_self_methodTestTarget#deprecated_method の呼び出しがそれぞれ一度だけ検出され、結果、以下のような標準出力が得られます。

TestTarget deprecated_self_method okuribito.rb:18:in `<main>'
deprecated_self_method
deprecated_self_method
#<TestTarget:0x007f9c61c96080> deprecated_method okuribito.rb:22:in `<main>'
deprecated_method
deprecated_method

課題

一通り、欲しい機能は作れたかなと思っています。
が、現状、名前空間に対応していないのが課題です。
後、(自分が把握している範囲で)誰かに使ってもらった訳ではないので、バグがまだあるかもしれない、というのが悩みです。

何で Okuribito って名前なの?

元々、未使用のメソッドを安全に削除するのを支援する目的で作ったgemだからです。
メソッドの最後を看取る......という想いを込めて、Okuribito。

おわりに

周囲に「どんなgemがあると嬉しいか?」というのをヒアリングしながら作ったので、コードの汚さはともかく、機能的には良いモノができたと思っています。
使ってみて、もしもバグと思える動作が発生した際は、issue の登録をお願い致します!(もちろん、要望も歓迎します!)
また、機能やコードを改善するPull Requestもお待ちしています!

少しでも「いいね」と思っていただけた方は、リポジトリにStarをつけていただけると幸いです \(^o^)/