17
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-02-20

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のコードがパッと実行できて、ちょっとした疑問をすぐに解決できるようになるので、ぜひ使いこなせるようにしておきましょう。

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

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

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

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

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

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

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

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です。

1.class
# => Integer

'hoge'.class
# => String

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

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

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__みたいなメソッドがあるんだなあとか、色々発見できますね。

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

method_missingとdefine_methodとeval_class

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

まずはじめにmethod_missingですが、これは、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を紹介します。
こちらは普通にテンプレートのように動的にメソッドを追加できます。

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-byebugpry-railsというGemを入れましょう。
このGemはirbに代わるpryというデバッグ環境をRailsで使用するために必要です。
とりあえずrails newしたら入れておきましょう。

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の様々なオブジェクトをロードした上でコードの実行ができます。

$ 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の拡張版みたいなもので、ソースコードまで直接表示できちゃいます。便利な世の中ですね。

$ 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に仕掛けてみましょう。

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

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

$ 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などで定義場所を取得することはできません。

$ 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ライフを!!!

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

17
20
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?