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
メソッドが何者なのかを調べるのが、method
とsource_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-byebug
とpry-rails
というGemを入れましょう。
このGemはirbに代わるpryというデバッグ環境をRailsで使用するために必要です。
とりあえずrails new
したら入れておきましょう。
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
に仕掛けてみましょう。
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ライフを!!!
もし間違えを見つけられた方や、これも入れたほうが良いんじゃないか、などがあればコメントをいただけると、とても嬉しいです。よろしくお願いします。