こころがホッコリー

ただイカしたRubyistになりたい人生だった。

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^)/

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^)/

RailsERDをラップして、yamlファイルに従ってER図を出力するgemを作ってみた

松村 Advent Calendar 2016 の15日目です。 @muramurasan です。

RailsERD

RailsERDという、出力したいModelをオプションで指定して、コマンドラインでER図を出力できるgemがあります。

github.com

Perfect Ruby On Rails という本でも紹介されているgemで、ささっとER図を出力するには非常に強力なgemです。

そんな RailsERD ですが、利用する上で欲しくなってきた機能がありました。
それが、「yamlファイルに記述したModelに従ってER図を出力する」という機能です。

RailsERD単体だと何が困るか

というのも、このRailsERD、毎回コマンドラインでModelを指定して出力するのですが、繰り返し出力することを考えると、手間を感じるのです。
書き捨てのER図ならば、コマンドラインでも良いんですが、実運用を考えると任意のモデル群の図を、複数枚出力したい、モデルが更新されるたびに......ということが想定できます。
例えば、「決済」「カート」「検索」など、ドメイン毎に図を出力したい、かつ、変更があればER図を出力し直したい......といった感じです。

そこで、RailsERDをラップして「yamlファイルに記述したModelに従ってER図を出力する」というgemを作成しました。

FreshERD

前段の説明通り、yamlファイルに記述したModelに従ってER図を出力する、というgemです。
コアの機能は本家RailsERDに依存しており、例えば、最新の rails-erd 1.5 を利用すれば、Railsプロジェクト以外にも適用できたりします。

github.com

前置きはこの辺で、使い方と、実際に使ってみた例を紹介します。

インストール

Gemfileに下記の記述を追加して、bundle install してください。

gem 'fresh_erd'

yamlの記述

Rails Tutorialのプロジェクトを例にyamlを記述してみます。

diagrams:
  tweet:
    - User
    - Micropost
  all:
    - User
    - Micropost
    - Relationship

tweetall の単位でER図が出力されることになります。
このファイルを適当な名前、例えば、fresh_erd.yml という名称で保存しておきます。

実際に使ってみた

以下のように、--inputオプションでyamlのパスを指定し、--outputオプションでER図を出力するフォルダを指定し、コマンドを叩きます。

$ bundle exec fresh_erd --input="fresh_erd.yml" --output="./"

以下が実行結果です。

--- Generate ERD : tweet
Loading application in 'sample_app_rails_4'...
Generating entity-relationship diagram for 4 models...
Diagram saved to './tweet.png'.
--- Generate ERD : all
Loading application in 'sample_app_rails_4'...
Generating entity-relationship diagram for 4 models...
Diagram saved to './all.png'.

以下のようなER図が取得できます。

f:id:muramurasan:20161126192547p:plain

f:id:muramurasan:20161126192553p:plain

以上です!

ちなみにgitのフックと連携して、最新のER図が常にpushされるように......とか妄想しましたが、
やりすぎ感あるのでこの辺に留めておきました。(FreshERDという名前は、この妄想が由来です)

緯度経度を入力値に、2地点間の距離を算出するgemを作ってみた(国土地理院提供APIを使用)

松村 Advent Calendar 2016 の14日目です。 @muramurasan です。
タイトルの通り、gem作りの練習として「緯度経度を入力値に、2地点間の距離を算出するgem」を作ってみたので、その使い方を紹介させていただきたいと思います。
gemの名前は conncect_gsi_api という、一体何のgemなのかさっぱりわからないものになっています。(後悔)

github.com

インストール

Gemfile を使う場合は、下記の記述を追加して、bundle install してください。

gem 'connect_gsi_api'

手動でインストールして使う場合は、gem install connect_gsi_api を叩きましょう。

実際に使ってみた

インストールから、利用するところまで実際に紹介していきます。
まずはインストールをします。

$ gem install connect_gsi_api
Fetching: connect_gsi_api-0.1.0.gem (100%)
Successfully installed connect_gsi_api-0.1.0
Parsing documentation for connect_gsi_api-0.1.0
Installing ri documentation for connect_gsi_api-0.1.0
Done installing documentation for connect_gsi_api after 0 seconds
1 gem installed

次に、手っ取り早く使用感を確かめるために、irb を起動して使ってみます。

$ irb
irb(main):001:0> require "connect_gsi_api"
=> true
irb(main):002:0> ConnectGsiApi.distance2p 35.6581, 139.701742, 36.123456, 138.705749
=> 103682.866

こんな感じで、引数 出発地緯度出発地経度到着地緯度到着地経度 を渡して distance2pを呼び出すと、距離がメートルで返ってきます。
不正なパラメータを渡してしまったなど、エラーが起きている場合はnilが返ってくる仕様になっています。

なお、デバッグのため、コマンドにも対応しています。

$ bundle exec calcdist 35.6581 139.701742 36.123456 138.705749
103682.866

注意事項

あくまで、このgemは国土地理院提供のAPIを使用しているものですので、
APIの使用回数制限や、API側のインタフェース変更で、gemが使えなくなることが想定されます。
残念な仕様ではありますが、うまく値を取得できなかった場合は、nilを返すような仕様になっておりますので、
うまく利用者側でよしなにハンドリングしていただければと思います。

今年読んだ本の振り返り

dodosoft Advent Calendar 2016 9日目です。 @muramurasan です。

www.adventar.org

dodosoftって何? という方は、内輪の勉強会ではあるんですが、こちらのエントリーをどうぞ。

okoysm.hatenablog.jp

タイトルの通り、本エントリーでは今年読んだ本の感想をつらつらと書きたいと思います。
どれも個人的にはオススメの本ばかりです。

リファクタリング:Rubyエディション

名著・リファクタリングRuby版です。
コード・設計の見通しを良くするためのテクニックの数々が紹介されています。
原著のリファクタリングが、オブジェクト指向でいかにして問題を解決するか、
といった、いわば設計面での改善テクニックが多く書かれているのに対し、
Ruby版では、コードベースでいかにして見通しをよくするか、ということが多く書かれている印象です。
Rubyならではのテクニックが数多く紹介されているので、原著読んだ人は更に本書も読むことをオススメします。
(別の本と言っても過言ではないかと)

リファクタリング:Rubyエディション

リファクタリング:Rubyエディション

エッセンシャル思考 最少の時間で成果を最大にする

昨年、嫌われる勇気という本を読むことで、精神衛生面の防護壁を培いました。
嫌われる勇気が、「自分のペースで良いさ」という心構えを説いた本なのに対し、
このエッセンシャル思考は、実際どうすれば仕事は早く終わるのか、面倒事に関わらなくて済むのか、
といった実践面のことを書かれている本です。
作中、「エッセンシャル思考じゃない人の例」がたくさん出てくるので、グサグサ心に刺さります(笑)
文量はそこそこあるんですが、読みやすいのでサクサク進められると思います。

エッセンシャル思考 最少の時間で成果を最大にする

エッセンシャル思考 最少の時間で成果を最大にする

メタプログラミングRuby

いわゆる、黒魔術メタプログラミングの本です。
......と思っていたのですが、単なる黒魔術の説明書じゃないんです。
Rubyという言語がいったいどういう風に動いているのか、
その内部深くまで踏み込んで説明してくれている、いわばRubyの理解を深める本なのです。
「黒魔術なんか扱うことはないから、読まなくていいや」と思っていた方は、
その考えを改めて、ぜひ、本書を手にとってみることをオススメします。一皮剥けます。まじで。
今年読んだ本の中で一番タメになった本でした。オススメ。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

ちなみにメタプログラミングRubyに影響を受けて作った、
「任意のメソッド呼び出しを監視する」というgem、okuribitoがこちら。

github.com

Effective Ruby

効果的なRubyプログラムを書くためのテクニックを紹介している本です。
が、まだこの本を読んで実践するだけの力がついていないなと、己の無力さを知ることとなりました。
正直、紹介されているテクニックの大部分については、使いどころがわからないんですよね。
それは、まだまだ書いているコードが幼稚であること、設計の選択肢が少ないことに他ならないと思います。
Rubyistとしてステップアップしたい方はぜひ、本書を手にとってみてください。

Effective Ruby

Effective Ruby

HTML5/CSS3 モダンコーディング

タイトル通り、HTML5、CSS3を用いた「今風」のコーディングスタイルを、
サンプルのウェブサイトを構築しながら学べる本です。
手を動かしながら学べるので、理解した気になって終わらないのが良いところかと。
また、作中に「昔はこんな辛みがあった、今はこうすれば大丈夫」という説明がまじえてあるので、
本当にこの本一冊で、今はどうすればいいのかを学べる印象です。

達人に学ぶDB設計 徹底指南書 - 初級者で終わりたくないあなたへ

DB周りの知識初級者から、中級者・上級者に引き上げてくれる本です。
確かに、Webアプリケーションを開発 / 保守 する上で、
最低限の知識だけで今まで戦ってきたんだな、というのを突きつけてくれました。
DB周りの知識を幅広く説明しており、論理設計のノウハウから、アンチパターン
物理設計の勘所、パフォーマンスチューニングのやり方まで......
嗚呼、これを知りたかった! ということがぎゅっ、と詰まって書かれている本です。
今年読んだ本のオススメ2番目ですかね......

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

大規模サービス技術入門

「1000台のシステムは、何が変わるのか?」という表題の通り、
サービスが大きくなっていった時に何を気にしなければならないのか?
データ構造、メモリ、OS、DB、サーバ/インフラ あらゆる要素について、
その勘所、テクニックを全編にわたって説明している本です。
が、残念ながら、書評を書いている現段階で、内容をあまり思い出せないことに気がつきました。
これは時間を置かずに、もう一度読まないといけないな、と思いました......

おわりに

この記事に取り組む前は「今年読んだ本全然無い......つらぽよ......」となっていたのですが、
いざ記事を書いてみると、意外と今年読んでいることに気がつきました。
こうして、これまでにやったことの棚卸をするのって大切ですね。