pry-railsでRuby(Rails)を探索する話

pry-rails/binding.pry 使ってますかー

私。pry-railsが好きです。 めっちゃ使いやすい。 javascriptdebuggerも好きなんですが、それ以上に個人的にRails開発でのpryが好きなのでそれについて書こうと思っています。

はい というわけでMakeIT AdventCalendar 8日目pry-railsRuby(Rails app)を探索する話で投稿したいとおもいます。 前回ポエム書いちゃったので2回目は技術的な何かに触れないとねw 明日は @ymzk-jp がGitのHEADとは何者なのかを投稿してくれるはずです。

./bin/rails c とか binding.pry の話

個人的な話ですが、pry-rails。最初はすごい使いにくくてエラーに遭遇してから原因調査をして、 問題箇所を修正するみたいな開発をすることが多かったなと感じています。

ただ、pry(以下デバッガ)をある程度使えるといい感じにアプリ開発が進むなと感じました。 そのため、よく使うメソッドをまとめたいなと思います

Rails始めた人とかRails使ってるけどデバッガ使わない人に届いたら嬉しいですね。

今回モデルケースにするのは↓のようなものです。

1] pry(main)>
[2] pry(main)> show-models
   (1.5ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
ApplicationRecord
  Table doesn't exist

Comment
  id: integer
  user_id: integer
  post_id: integer
  body: string
  created_at: datetime
  updated_at: datetime
  belongs_to :post
  belongs_to :user
Post
  id: integer
  title: string
  body: text
  published: boolean # statusって名前にすれば良かったと後悔してます
  user_id: integer
  created_at: datetime
  updated_at: datetime
  belongs_to :user
User
  id: integer
  nickname: string
  email: string
  password_digest: string
  created_at: datetime
  updated_at: datetime
  has_many :comments
  has_many :posts

みたいなものを使ってた時。を想定して話していきたいなと思います。


rubyは全てがオブジェクト

突然ですがいわゆるclass,moduleなども一つのオブジェクトとなります。 オブジェクトであれば - 当然メソッド持っています - ほとんどの場合クラス本体もしくは、なにかのインスタンスであるでしょう。 - またそのクラスorインスタンスはほとんどの場合親クラスが存在しています といったことを念頭に起いた状態で次のコードを見てください

[1] pry(main)> self
=> main
[2] pry(main)> ls
ActiveSupport::ToJsonWithActiveSupportEncoder#methods: to_json
Rails::ConsoleMethods#methods: app  controller  helper  new_session  reload!
self.methods: inspect  to_s
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_

[3] pry(main)> cd app
[4] pry(#<ActionDispatch::Integration::Session>):1> self
=> #<ActionDispatch::Integration::Session:0x00007fd184305860
 @_mock_session=nil,
 @_routes=nil,
 @accept="text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
 @app=
  #<SampleApp::Application:0x00007fd180ab8c28
   @_all_autoload_paths=

デバッガ起動直後のselfは main (オブジェクト?)を参照しています。(わからんw)

rubyの実行

ここ(デバッガ)ではirb同様Rubyを実行することができます

Ex) fizzbuzz

[42] pry(main)> 100.times{|n| puts n % 15 == 0 ? 'fizzbuzz' : n % 5 == 0 ? 'fizz' : n % 3 == 0 ? 'buzz' : n }

Railsでの使い方

Rubyの使い方はあまり触れずに今回はRailsの使い方を。 先ほど cdをしましたが。 bash使ってれば察すると思いますが、 lsもありそうですよね。

[47] pry(main)> self
=> main
[48] pry(main)> ls
ActiveSupport::ToJsonWithActiveSupportEncoder#methods: to_json
Rails::ConsoleMethods#methods: app  controller  helper  new_session  reload!
self.methods: inspect  to_s
instance variables: @app_integration_instance  @controller
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_
[49] pry(main)>

cd, ls

ここでいうlsは’self’のオブジェクトがもっている(利用可能な)メソッドを羅列してくれます。

例えば今回のモデルである'Post'クラスや’Post’のインスタンスに潜ってlsをしてみようと思います

[70] pry(main):1> cd Post
[85] pry(Post):2> self
=> Post(id: integer, title: string, body: text, published: boolean, user_id: integer, created_at: datetime, updated_at: datetime)

こうするとPostクラスに移動した状態です 基本的に、今いる参照先(currentなオブジェクト?)を確認する際は'self'を使う、もしくは'プロンプト'の表示項目 "[82] pry(Post)"を確認する などをすると思います

[82] pry(Post):2> ls
Object.methods: yaml_tag
ActiveModel::Naming#methods: model_name
ActiveSupport::Benchmarkable#methods: benchmark
ActiveSupport::DescendantsTracker#methods: descendants  direct_descendants
ActiveRecord::ConnectionHandling#methods:
  clear_active_connections!  clear_reloadable_connections!  connection_config              connection_specification_name=  mysql2_connection
  clear_all_connections!     connected?                     connection_pool                establish_connection            remove_connection
  clear_cache!               connection                     connection_specification_name  flush_idle_connections!         retrieve_connection
ActiveRecord::QueryCache::ClassMethods#methods: cache  uncached
ActiveRecord::Querying#methods:
  any?          distinct     find_each
  #--------省略-----------------
  Post.methods: __callbacks  _reflections  _validators  attribute_type_decorations  defined_enums  draft  published  publisheds
Post#methods: autosave_associated_records_for_user  belongs_to_counter_cache_after_update
...etc

例えば、モデルクラスであるPost、ここで'ls'をするとRailsのモデルが持ってるメソッドが全て表示されます。 破線の後半にある Post.methodsの部分はPostクラスにて定義したものが表示されます さらに、自身の定義したメソッドを見る場合はいくつか方法があります

  • object#methods.grep() #-> tab押すと予測がでる
  • object#find-method #-> Recursively search for a method within a Class/Module or the current namespace.
  • ls --grep #-> Show the list of vars and methods in the current scope., # Exclude -q, -v and --grep because they,

だそうです。 使ってる感じはこちら

おすすめは メソッド名がある程度わかってるのであれば、ls --grepを使う。 そうでないのであれば methods.grep(:hog) -> hogを含んでるメソッドをタブで予測表示される という使い分けです。

appオブジェクト

デバッガーではappオブジェクトなるものにアクセスすることができます appオブジェクトを経由することでURLヘルパー、pathヘルパー、またリクエストを送ることが可能です URLhelperといえば $./bin/rake routesを使う人やhttp://localhost:3000/rails/info/routesを使う人は多いのではないでしょうか?

これらの確認はこのデバッガでも可能です

[138] pry(main)> show-routes
                   Prefix Verb   URI Pattern                                                                              Controller#Action
                    users GET    /users(.:format)                                                                         users#index
                          POST   /users(.:format)                                                                         users#create
                 new_user GET    /users/new(.:format)                                                                     users#new
                edit_user GET    /users/:id/edit(.:format)                                                                users#edit
                     user GET    /users/:id(.:format)                                                                     users#show
                          PATCH  /users/:id(.:format)                                                                     users#update
                          PUT    /users/:id(.:format)                                                                     users#update
                          DELETE /users/:id(.:format)                                                                     users#destroy
                    posts GET    /posts(.:format)                                                                         posts#index
                          POST   /posts(.:format)                                                                         posts#create
                 new_post GET    /posts/new(.:format)                                                                     posts#new
                edit_post GET    /posts/:id/edit(.:format)

またこのRoutingコマンドに関しても先の'ls'で確認したようにGrepを使えます

[144] pry(main)> show-routes --grep post
                    posts GET    /posts(.:format)                                                                         posts#index
                          POST   /posts(.:format)                                                                         posts#create
                 new_post GET    /posts/new(.:format)                                                                     posts#new
                edit_post GET    /posts/:id/edit(.:format)

そのほかにも。RouteをControllerごとに見るのであれば

[39] pry(main)> find-route Post
Routes for PostsController
--
index GET /posts(.:format)  [posts]
create POST /posts(.:format)
new GET /posts/new(.:format)  [new_post]
edit GET /posts/:id/edit(.:format)  [edit_post]
show GET /posts/:id(.:format)  [post]
update PATCH /posts/:id(.:format)
update PUT /posts/:id(.:format)
destroy DELETE /posts/:id(.:format)

というわけで、実際に調べた posts_path, new_post_pathなどの確認や、リクエストをしてみたいと思います。

[161] pry(main)> app.posts_path
=> "/posts"
[162] pry(main)> app.new_post_path
=> "/posts/new"
[163] pry(main)> app.post_path(1)
=> "/posts/1"
# このように生成されたRouteの確認ができますね。
# 実際にpost_pathにreqしてみると
[177] pry(main)> app.get app.posts_path
Started GET "/posts" for 127.0.0.1 at 2018-12-08 20:02:01 +0900
   (0.9ms)  SELECT `schema_migrations`.`version` FROM `schema_migrations` ORDER BY `schema_migrations`.`version` ASC
Processing by PostsController#index as HTML
  Rendering posts/index.html.erb within layouts/application
  Post Load (1.3ms)  SELECT `posts`.* FROM `posts`
  Rendered posts/index.html.erb within layouts/application (206.8ms)
Completed 200 OK in 533ms (Views: 503.4ms | ActiveRecord: 2.7ms)

=> 200
[200] pry(main)> res = app.response
# res.bodyとするとSSRされたHTMLが見れます

[227] pry(main)> app.request.params
=> {"controller"=>"posts", "action"=>"index"}
[228] pry(main)> app.request.headers
=> header情報
[229] pry(main)> app.controller.class
=> PostsController

といったようなリクエストを送信することもできます。

helperオブジェクト

railsで開発してるとメソッドの切り出しなどにHelperを使うことがあると思います。 そういったときのHelperもデバッガーにて確認することが可能です。

module ApplicationHelper
  def hello_world
    puts 'hello world'
  end
end

例えばこんな感じのHelperがあると

[4] pry(main)> helper.hello_world
hello world
=> nil

このように呼ぶことが可能です。 他にも今まで同様

[21] pry(main)> cd ApplicationHelper
[22] pry(ApplicationHelper):1> self
=> ApplicationHelper
[23] pry(ApplicationHelper):1> ls
ApplicationHelper#methods: hello_world
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_
[24] pry(ApplicationHelper):1> show-method hello_world

From: /Users/fumihumi/project/sample_app/app/helpers/application_helper.rb @ line 2:
Owner: ApplicationHelper
Visibility: public
Number of lines: 3

def hello_world
  puts 'hello world'
end

という形で参照することができます ここでHelperメソッドを呼び出したい場合は

[38] pry(ApplicationHelper):1> show-source self

From: /Users/fumihumi/project/sample_app/app/helpers/application_helper.rb @ line 1:
Module name: ApplicationHelper
Number of lines: 9

module ApplicationHelper
  def hello_world
    puts 'hello world'
  end

  def self.public_world
    puts 'class method hello'
  end
end
[39] pry(ApplicationHelper):1> public_world
class method hello
=> nil
[40] pry(ApplicationHelper):1> hello_world
NameError: undefined local variable or method `hello_world' for ApplicationHelper:Module
from (pry):14:in `__binding__'

一時的にクラスメソッドにする。、もしくは mainを参照している状態でhelper.hello_worldとします。

show-method(show-source)

これはメソッドの定義を見ることができます たとえばshow-routesをみてみると

[64] pry(main)> show-source show-routes

From: /Users/fumihumi/project/sample_app/vendor/bundle/gems/pry-rails-0.3.8/lib/pry-rails/commands/show_routes.rb
Number of lines: 76

class PryRails::ShowRoutes < Pry::ClassCommand
  match 'show-routes'
  group 'Rails'
  description 'Show all routes in match order.'
  banner <<-BANNER
    Usage: show-routes [-G]
    show-routes displays the current Rails app's routes.
  BANNER

  def options(opt)
    opt.on :G, "grep", "Filter output by regular expression",
           :argument => true,
           :as => Array
  end
  ...etc

のようになっており、DescとOptionの存在を知ることができますね。 他にも自分が定義しているメソッドなども参照ができます。

edit

これは引数に渡したメソッドがVimで(?)起動がされます。 多分'.pryrc'で設定できるはず GIFにしましたがまぁこんな感じでみれるということです。

reload

editorなどでアプリのモデル等を編集したらreload!をすることでアプリを再読み込みすることができます。 ただ、時々意図しない感じな挙動になることがある?気がするのでおかしくなったら'exit'した方がいいかと思います。

watch

4] pry(main)> hoge = 'hoge'
=> "hoge"
[15] pry(main)> fuga = 'fuga'
=> "fuga"
[16] pry(main)> watch hoge
Watching hoge
watch: hoge => "hoge"
[17] pry(main)> watch fuga
Watching fuga
watch: fuga => "fuga"
[18] pry(main)> hoge = 'ww'
watch: hoge => "ww"
=> "ww"
[20] pry(main)> foo ='foo'
=> "foo"
[21] pry(main)> foo << 'ee'
=> "fooee"
[23] pry(main)> fuga << 'ee'
watch: fuga => "fugaee"
=> "fugaee"

watchを使うとWatchで指定した変数が変更があったときにwatch: <before> => <after>で教えてくれます

hist

名前の通りHistoryをみせてくれます。 これもGrepができます

[48] pry(main)> hist --grep find
20: show-method find-route
21: find-route Post
23: find-route Post

便利。w

ActiveRecord

user = User.last
user.posts.published.to_sql
=> "SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` = 10 AND `posts`.`published` = 1"
# to_sqlメソッドでこれから吐かれるSQLをみれる

[100] pry(main)> (Date.today..Date.tomorrow).to_s(:db)
=> "BETWEEN '2018-12-08' AND '2018-12-09'"
# 引用:http://thr3a.hatenablog.com/entry/20181206/1544099172

[106] pry(main)> user.posts.published.new
=> #<Post:0x00007fd184778208 id: nil, title: nil, body: nil, published: "published", user_id: 10, created_at: nil, updated_at: nil>
# user.posts.new -> user_id == user.id
# user.posts.publisdhed.new -> published: publishedな状態でnewできる

ActiveRecord周りの便利な使い方はSQLを考えながら書くといい感じに描けそうですね わたしはまだまだダメなのでもっと頑張りたいところの一つです。

model, DB構造

[92] pry(main)> show-model User
User
  id: integer
  nickname: string
  email: string
  password_digest: string
  created_at: datetime
  updated_at: datetime
  has_many :comments
  has_many :posts

のようにするとモデルのColumnなどを把握することができます

shell-mode

[130] pry(main)> shell-mode
pry main:/Users/fumihumi/project/sample_app $

と、なんかプロンプトが変わりますが操作が変わる感じはあまりしてません。 どうやって使うのか知ってたら教えていただきたいです...

つらつらと書いてきましたがこんな感じでデバッガー一つとっても複雑なことがたくさんできます。 こういった使い方もあくまでも一つの使い方だと思いますし、もっと便利な方法があるのかもしれません。 自分のデバッガーの使い方を見つけておくとよりよいプログラミングになるのかなとか思ったりしています。 もっと自由自在に使いこなせるようになりたいですね。

以上でAdventCalendar8日目を終わりにしたいと思います。最後まで読んでいただきありがとうございます。 メモ書きのような文章になってしまい申し訳ありませんw