RubyとRailsの独特な挙動と、それと戦うためのデバッグ手法

Railsを書き始めた人が知っておくと役立つような、RubyとRailsの知識をまとめてみました。
環境はUbuntu 17.10で、Ruby 2.4.1のRails 5.1.3ですが、Macでもほとんど同じく操作ができるかと思います。

# Rubyとの戦い方
この項目では、Railsとは関係のないRuby言語としての挙動やデバッグについて話していきたいと思います。Railsから勉強し始めた方だと、どこまでがRubyの挙動で、どこからがRailsの拡張なのか分からないという人も多いと思うので、とりあえず純粋なRubyのお話から初めます。

## irbの使い方
まず初めにRubyのデバッガ(正しくはREPL)についてですが、Rubyをインストールしたら同時にirbというコマンドが入っていると思います。このirbを使用すると、Rubyのコードがパッと実行できて、ちょっとした疑問をすぐに解決できるようになるので、ぜひ使いこなせるようにしておきましょう。

```console
$ irb
irb(main):001:0> [1] + [2, 3]
=> [1, 2, 3]
```

ちなみにRailsでは後で説明しますが、Rails Consoleというより便利なコマンドがあるので、Rails開発中はそちらを利用すると良いと思います。

## Hashが絡んだメソッドの呼び出しとキーワード引数

Rubyのメソッド呼び出しの書き方は少し独特で、しかもHashが絡むと少しややこしくなってくるので、どんな感じでパースされるのかを覚えておきましょう。

まずはじめに、Hashのみを引数に渡すときは、その外側の波括弧が省略できます。
この波括弧を明示的に書いてる人はほとんど見たことが無いです。

```ruby
User.where(name: 'hoge')
# => User.where({name: 'hoge'})
```

次に、通常の引数と最後にHashを渡す場合です。
Hashの書き方になっているところから後ろが、全て1つのHashになってメソッドに渡っていきます。
Rubyの文法的に正しい表現をすると、「最後のHashの{}は省略できる」です。

```ruby
has_many(:books, a: 1, b: 2)
# => has_many(:books, {a: 1, b: 2})

hoge_method(1, a: 2, 3)
# [Error] syntax error
```

## class, method, source_location, methods

Rubyのコードを読んでいると、君は何者なのだ？という物に出くわすことが、よくあると思います。そんなときにその正体を調べる方法を紹介したいと思います。

まずはじめに、オブジェクトがどのクラスなのかを調べるのが、`class`です。

```ruby
1.class
# => Integer

'hoge'.class
# => String

-> { puts a }.class
# => Proc
```

メソッドが何者なのかを調べるのが、`method`と`source_location`です。
ちなみに、Rubyのコア機能のようなC言語で実装されている部分は`source_location`を呼び出すと`nil`が返ってきます。

```ruby
require 'abbrev'
Abbrev.method(:abbrev).source_location
# => ["/home/user/.rbenv/versions/2.4.1/lib/ruby/2.4.0/abbrev.rb", 73]
```

それから、そのオブジェクトが持っている全メソッドを列挙するのが、`methods`です。
この`methods`を知っていれば、`:__id__`みたいなメソッドがあるんだなあとか、色々発見できますね。

```ruby
'hoge'.methods
=> [:include?, :%, /* 省略 */ :__id__]
```

## method_missingとdefine_methodとeval_class

Rubyにはdefを用いて静的に定義されたメソッド以外にも、いくつかの方法で動的にメソッドを定義することができます。

まずはじめに`method_missing`ですが、これは、Rubyでのメソッド呼び出し時に、その呼び出した名前のメソッドが存在しなかった場合に、そのメソッド名を受け取って、何かしらの動作をすることができるという独特なものです。実際に例を上げてみます。

```ruby
class Cat
  def method_missing(action)
    action.to_s.include?('fish') ? "#{action} nya" : super
  end
end

Cat.new.fisherman
# => "fisherman nya"
Cat.new.hoge
# => NoMethodError: undefined method `hoge' for #<Cat:0x00555795b183d8>
```

この`method_missing`は自分では書くことは少ないかとは思いますが、Rails本体のコードや、Gemのコードを読んでいると割と頻繁に目にすることになると思います。

次に、`define_method`と、`eval_class`を紹介します。
こちらは普通にテンプレートのように動的にメソッドを追加できます。

```ruby
class Animal
  define_method :baw do
    'baw'
  end

  ['mew', 'meow'].each do |name|
    class_eval <<-EOS, __FILE__, __LINE__ + 1
      def #{name}
        '#{name}'
      end
    EOS
  end
end

Animal.new.baw
# => "baw"
Animal.new.mew
# => "mew"
```

# Railsとの戦い方
さて、ここからがRailsの話です。
Railsでの開発における、実用的なデバッグ方法などを説明していきたいと思っています。

まず初めにですが、`pry-byebug`と`pry-rails`というGemを入れましょう。
このGemはirbに代わるpryというデバッグ環境をRailsで使用するために必要です。
とりあえず`rails new`したら入れておきましょう。

```ruby:Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '~> 2.13'
  gem 'selenium-webdriver'

  gem 'pry-byebug'
  gem 'pry-rails'
end
```

## rails console
まず初めに知っておきたいのが、`rails console`というコマンドです。
これはirbに似たREPL環境で、irbはRubyのデフォルトの環境でコードが実行できますが、`rails console`は、Railsの様々なオブジェクトをロードした上でコードの実行ができます。

```shell
$ bundle exec rails console
[1] pry(main)> Rails.root
=> #<Pathname:/home/user/project>
[2] pry(main)> 2.method(:days).source_location
=> ["/home/user/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/activesupport-5.1.3/lib/active_support/core_ext/numeric/time.rb", 45]
```

さらに便利な技として、`show-method`というのがあって、これは`source_location`の拡張版みたいなもので、ソースコードまで直接表示できちゃいます。便利な世の中ですね。

```shell
$ bundle exec rails console
[1] pry(main)> show-method ApplicationController.protect_from_forgery

From: /home/user/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/actionpack-5.1.3/lib/action_controller/metal/request_forgery_protection.rb @ line 122:
Owner: ActionController::RequestForgeryProtection::ClassMethods
Visibility: public
Number of lines: 8

def protect_from_forgery(options = {})
  options = options.reverse_merge(prepend: false)

  self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
  self.request_forgery_protection_token ||= :authenticity_token
  before_action :verify_authenticity_token, options
  append_after_action :verify_same_origin_request
end
```

この`show-method`でもC言語の実装は見れないのですが、`pry-doc`というGemを導入すると見れるようになります。そこまではこの記事では扱わないので、興味がある方は調べてみてください。

## binding.pry

Railsの好きな場所でブレークポイントを仕掛けたい時に使えるのが、`binding.pry`というメソッドです。Rubyのコードなら、例えばERBの中とかでも、割とどこに仕掛けても止まってくれます。
今回は例として、`routes.rb`に仕掛けてみましょう。

```ruby:routes.rb
Rails.application.routes.draw do
  binding.pry
  root 'pages#index'
end
```

そして、`rails server`を実行してみると

```plaintext
$ bundle exec rails s                                           
=> Booting Puma                                     
=> Rails 5.1.3 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options   
                                                    
From: /home/user/project/config/routes.rb @ line 3 :

    1: Rails.application.routes.draw do
    2:   binding.pry
 => 3:   root 'pages#index'
    4: end

[1] pry(#<ActionDispatch::Routing::Mapper>)> show-method root

From: /home/user/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/actionpack-5.1.3/lib/action_dispatch/routing/mapper.rb @ line 1614:
Owner: ActionDispatch::Routing::Mapper::Resources
Visibility: public
Number of lines: 19

def root(path, options = {})
/* 以下略 */
```

素晴らしく便利ですね。
さらに、この状態で、`step`,`next`,`finish`,`continue`などのコマンドを使用することで、ステップ実行できるので試してみてください。

## DSLっぽいクラスメソッド

もう、ここまで読み進めていただいた方なら、気づいていらっしゃるかもしれませんが、
Controllerに書かれている`before_action`や、Modelに書かれている`belongs_to`などの、よく分からない書き方のアノテーションかなと思われるような記法についてですが、これらも全てクラスメソッドです。
もう、binding.pryを使って、デバッグすることもできますね！

## スキーマから生成されるメソッド
最後にですが、ActiveRecordのカラムの話をしたいと思います。
ModelでDBのカラムに対応するようなメソッド(`User.name`みたいな)は、
実行時に動的に定義されています。
なので、一度読み込んであげてクラスの初期化処理を走らせないと、`show-method`などで定義場所を取得することはできません。

```plaintext
$ bundle exec rails c
# 今はまだ見つからない。
[1] pry(main)> show-method User#name
Error: Couldn't locate a definition for User#name!

# 一度クラスを使用すると、
[2] pry(main)> User.new
=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>

# 定義場所を見つけられるようになる。
[3] pry(main)> show-method User#name

From: /home/user/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/activerecord-5.1.3/lib/active_record/attribute_methods/read.rb @ line 34:
Owner: #<#<Class:0x005568d3ec51a8>:0x005568d3ec51f8>
Visibility: public
Number of lines: 4

def #{temp_method}
  name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
  _read_attribute(name) { |n| missing_attribute(n, caller) }
end
```

以上でした。
これでみなさんも、楽しいRailsライフを！！！

もし間違えを見つけられた方や、これも入れたほうが良いんじゃないか、などがあればコメントをいただけると、とても嬉しいです。よろしくお願いします。