u16suzuの blog

u16suzu のブログです。

gemのソースを pry-byebug を使ってブレークポイントで止めて読む準備

いつも仕事でもプライベートでも使っている bundler だけど、中身のコードを把握していないのでどうも雰囲気で使っている感が否めない。

そこで、ソースを読み腹落ち感を得るためにコードを読む準備を進めてみた。

pry-byebug を使ってブレークポイントで止められるようにする

(1). システムに pry-byebugを入れる

毎回 Gemfile 書くのは面倒なのでシステムに入れちゃう

gem install pry-byebug
gem install pry-stack_explorer # 超便利なのでこれも

(2). require "pry" して、止めたいところで binding.pryすれば止まる

require "pry"

ar = [1,2,3]

ar.each do |i|
  binding.pry # ここで止まる
  p i
end

便利。

bundlerのコードで試してみる

(1). ソースを git cloneする

$ git clone git@github.com:bundler/bundler.git ~/src

/vendor/bundle に入ったコードだと git blame できなくて普通に読みづらいので、git clone したり gem-src を使うなりして git repository を取得した方が良い。

(2). CUI ツールのエントリポイントであるbundler/exe/bundle でまずは require "pry"する

#!/usr/bin/env ruby
# frozen_string_literal: true

require 'pry'
(略

(3). 実行して止まるか確認

./bundler で実行すればシステムに入っている方ではなく git clone した方の bundler が実行できる。

$ cd /Users/suzuki_y/src/bundler/exe/
suzuki_y@MBP:~/s/b/exe$ ./bundle install                             

From: /Users/suzuki_y/src/bundler/exe/bundle @ line 30 :

    25:   require "bundler/cli"
    26:
    27:   binding.pry
    28:
    29:   # Allow any command to use --help flag to show help for that command
 => 30:   help_flags = %w[--help -h]
    31:   help_flag_used = ARGV.any? {|a| help_flags.include? a }
    32:   args = help_flag_used ? Bundler::CLI.reformatted_help_args(ARGV) : ARGV
    33:
    34:   Bundler::CLI.start(args, :debug => true)
    35: end

[1] pry(main)>

無事止まりました。

show-stackでコールスタックもみれます。

[1] pry(main)> show-stack

Showing all accessible frames in stack (9 in total):
--
=> #0  <main>
   #1 [block]   block in run <Byebug::PryProcessor#run(&_block)>
   #2 [method]  run <Byebug::PryProcessor#run(&_block)>
   #3 [method]  resume_pry <Byebug::PryProcessor#resume_pry()>
   #4 [method]  at_line <Byebug::PryProcessor#at_line()>
   #5 [method]  at_line <Byebug::Context#at_line()>
   #6 [block]   block in <main>
   #7 [method]  with_friendly_errors <Bundler.with_friendly_errors()>
   #8 [main]    <main>

あとは、コードを好きにいじりながら自由にコード読み放題です。

メソッド系のgemの場合

また、 bundlerのようなコマンド系の gem ではなく、tapp gem のようにコード内で使用するメソッド系の gem は、

gemのルートディレクトリに main.rb みたいな適当なファイルを作って、そこから対象のgemを require して呼び出しを行ってとっかかりを作ります。

# src/tapp/main.rb
require "./lib/tapp"
require "pry"

binding.pry

"123".tapp
"123".taputs
From: /Users/suzuki_y/src/tapp/main.rb @ line 6 :

    1: require "./lib/tapp"
    2: require "pry"
    3:
    4: binding.pry
    5:
 => 6: "123".tapp
    7: "123".taputs

[1] pry(main)> step

From: /Users/suzuki_y/src/tapp/lib/tapp.rb @ line 13 Tapp.config:

    12: def config
 => 13:   @config ||= Tapp::Configuration.new
    14: end

[1] pry(Tapp):1>

いい感じですね。

RubyMineを使ったやり方もあとで追記したいと思います。

rails console から任意のメソッドのソースコードをRubyMineで開く

設定

.pryrcに以下を追記する。RubyMineのCUIコマンドがない場合は Tools > Create Command-line Launcher... で作っておく。

Pry.config.editor = proc { |file, line| "/usr/local/bin/mine --line #{line} #{file}" }

Pry.commands.alias_command 'mine', 'edit'
Pry.commands.alias_command 'mi', 'edit'

実行例

以下のコマンドを実行すると、RubyMine で User.first.label を定義している部分のコードが開かれる。

pry(main)> mine User.first.label

こちらももちろん呼び出し可能。

pry(main) mine User.first.label=

Rails などの gem のコードも同様に開ける。

pry(main)> mine Rails.cache.read

開かれたコード。dalli_store.rb の 115行目あたり。

      def read(name, options=nil)
        options ||= {}
        name = expanded_key name

        instrument(:read, name, options) do |payload|
          entry = read_entry(name, options)
          payload[:hit] = !!entry if payload
          entry
        end
      end

自分の小さな「箱」から脱出する方法を読んだ

自分の中で一方的に師と思っている元KLab CTO 仙石さんの投稿をきっかけにして、 以前購入し積読してあった本を読んだ。 とても良い本だった。

自分の小さな「箱」から脱出する方法

自分の小さな「箱」から脱出する方法

  • 作者: アービンジャーインスティチュート,金森重樹,冨永星
  • 出版社/メーカー: 大和書房
  • 発売日: 2006/10/19
  • メディア: 単行本(ソフトカバー)
  • 購入: 156人 クリック: 3,495回
  • この商品を含むブログ (418件) を見る

本書の概要

自分が本来相手のためにすべきだと思ったことをしないことを、心理学の世界では「自己欺瞞」という。 これを本書ではシンプルに「箱に入る」とか自分への裏切りと呼ぶ。

一度この自己欺瞞が始まると、そこから...

1. 他人の欠点を大げさにあげつらう
2. 自分の長所を過大に評価する
3. 自己欺瞞を正当化する。ものの価値を過大に評価する
4. 相手に非があると考える

というような流れで悪化していき、物事を見る目が曇ってしまい、しまいには他人との信頼関係が壊れてしまう。 本書では、これを防ぐには自分が箱の中に入っているかどうかを意識して、 なんとか箱の中から脱出することで、解決を目指しましょうということを述べている。 もちろん、つねに箱から出ているのが理想ではあるけれども、 そうもいかないので、たまには箱に入ることもある。 でも、箱に入っている状態を意識するというのは大事だと思う。

正直、過去現在に自分が周りで観測した人間関係のこじれはこの流れに沿っている。 もちろん自分が当事者になった時もあるし、そうじゃない時もある。 本書では、そのような過去の事柄についても、ことあるごとにその時どうすればベストだったか? を振り返って考えることを勧めている。

最後に

最後に本の最終ページに書いてあったリストを引用しておく。

知っておくべきこと

  • 自分への裏切りは、自己欺瞞へ、さらには箱へとつながっていく。
  • 箱の中にいると、業績向上に気持ちを集中することができなくなる。
  • 自分が人にどのような影響を及ぼすか、成功できるかどうかは、全てはこの外に出ているか否かにかかっている。
  • 他の人々に抵抗するのをやめた時、箱の外に出ることができる。

知ったことに即して生きること

  • 完璧であろうと思うな。より良くなろうと思え。
  • すでにそのことを知っている人以外には、箱などの言葉を使うな。自分自身の生活に、この原則を活かせ。
  • 他の人々の箱を見つけようとするのではなく、自分の箱を探せ。
  • 箱の中に入っているといって他人を責めるな。自分自身が箱の外に留まるようにしろ。
  • 自分が箱の中にいることがわかっても、あきらめるな。努力を続けろ。
  • 自分が箱の中にいた場合、箱の中にいたということを否定するな。謝った上で、さらに前へ進め。これから先、もっと他の人の役に立つよう努力しろ。
  • 他の人が間違ったことをしているという点に注目するのではなく、どのような正しいことをすればその人に手を貸せるかを、よく考えろ。
  • 他の人が手を貸してくれるかどうかを気にやむのはやめろ。自分が他の人に手を貸せているかどうかに気をつけろ。

though(&although) について

github で gem の README を読んでいるとよくでてくる英単語の though について調べました.

なんとなく but のように逆説の意味であることは知っていたけれどもいざ実戦で出会うと、though の前の節と後の節が逆のことをいっていて, どちらが文章の真意なのかがよくわからなくなり意味がとれず苦労していました.

ネットで調べたところ though のある節の方を 従属節 と捉えて読めば良いという理解に落ち着きました. 主節がメインの言いたいことであって though 以下の従属節は補足情報であるというイメージです.

以下の例文で説明します.

Jess failed to diet, though he joining Rizap.
(ジェスはライザップに入会したにも関わらずダイエットに失敗した。)

主節

  • メインの情報を示す部分
  • 例文の Jess failed to diet の部分
  • 「ジェスはダイエットに失敗した」という事実(主題)を述べている.

従属節

  • 追加の情報の節
  • 例文の though 以降の部分
  • 「ジェスはダイエットのためにライザップに入会した」という 主題を補足する情報 を述べている.

githubからの例文

  • Unicorn の test/benchmark/README から実際に出会った文章を引っ張り出して翻訳してみます.
== Contributors

This directory is maintained independently in the "benchmark" branch
based against v0.1.0.  Only changes to this directory (test/benchmarks)
are committed to this branch although the master branch may merge this
branch occassionaly.
== コントリビュータ

このディレクトリは v0.1.0 をベースにした benchmark ブランチで独立してメンテナンスされています.
test/benchmarks ディレクトリのみへのコミットはこの benchmark ブランチにコミットされます,
しかしながら, master ブランチは時々このブランチをマージします.

although the master branch may merge this branch occassionaly. の部分が従属節なため, 主題は Only changes to this directory (test/benchmarks) are committed to this branch であると目星をつけて訳してみました.

  • 同じく Capistrano の README にも使われていました.
Capistrano is a framework for building automated deployment scripts. 
Although Capistrano itself is written in Ruby,
it can easily be used to deploy projects of any language or framework, be it Rails, Java, or PHP.
Capistrano は自動デプロイを作るためのフレームワークです。
Capistrano 自身は Rubyで書かれていますが, 
Rails, Java, PHP といったようなどんな言語のフレームワークを使ったプロジェクトにも簡単に使うことができます。

althoughとthoughの比較

  • 最後にalthoughとthoughの簡単な使い分けをまとめておきます.

although

  • 硬い表現
  • 文頭で使用する.

though

  • (although)よりは柔らかい表現
  • 後ろに持ってくることもできる
  • 強意のため even though とすることもある

RubyMineとiTerm2 でRubyのバックトレースからファイルを開く時に RubyMine で開くようにする

iTerm2側で以下の設定をすることで、cmd+click でファイルパスを選択した時に RubyMineで開いてくれます。

iTerm2 の Preferences > Profiles >
任意のプロファイルを選択 > Advanced タブ > Semantic History >
Run Command を選択 > `/usr/local/bin/rubymine --line \2 \1` を入れる。

RubyMineのコマンドラインランチャー /usr/local/bin/rubymine をまだ作っていない場合は以下の方法で作成します。

RubyMine のツールバー > Tools > Create Command-line Launcher...

Rubyでデザインパターン ~Stateパターン~

RubyでStateパターンを実装してみるテスト。

Stateのコンクリートクラスにメソッドの実装を生やすことで、Stateを新しく追加するときに追加しやすくしている。

クラスマクロもちょろっと使ってみたり。

でもクラスマクロはコードが短くなるのはいいけど、初見の人には読みづらいと思うんだよなぁ。

class Class
  def subclasses
    subclasses = []
    ObjectSpace.each_object(singleton_class) do |k|
      subclasses << k if k.superclass == self
    end
    subclasses
  end
end
class State
  @_time
  def self.time(time=nil) # 引数がnilの時はgetter, nilでない時はsetterになる
    @_time ||= time
  end
  def time # インスタンスメソッドも一応定義しておく
    self.class.instance_variable_get("@_time")
  end
  def hello
    raise
  end
end
class MorningState < State
  time 0..9
  
  def hello
    puts "Good morning!"
  end
end
class DayState < State
  time 10..15
  
  def hello
    puts "Hello!"
  end
end
class EveningState < State
  time 16..18

  def hello
    puts "Good evening."
  end
end
class NightState < State
  time 19..24
  
  def hello
    puts "Good night."
  end
end
class StateManager
  attr_accessor :state, :time
  def initialize(state=MorningState.new)
    @state = state
  end
  def say_hello
    @state.hello
  end
  def time=(time)
    @time = time
    State::subclasses.each do |k|
      if k.time.include?(@time)
        @state = k.new
        break
      end
    end
  end
end


mgr = StateManager.new
mgr.say_hello
(0..24).to_a.each do |i|
  mgr.time = i
  mgr.say_hello
end

ORDER BY 狙いのキーについて動作確認メモ

こちらのスライドで説明されていることの理解を深めるために動作確認してみました。

www.slideshare.net

# 実験用に bulk insert でデータを入れる

values_1 = (1..9990).to_a.map do |_|
  "(#{rand(80)}, 1)"
end.join(",  ")


values_2 = (1..10).to_a.map do |_|
  "(#{rand(80)}, 0)"
end.join(",  ")

[values_1, values_2].each do |v|
  system "mysql -u root spike -e 'INSERT INTO `users` ( `age`, `gender`) VALUES #{ v };'"
end

puts "success!"
EXPLAIN SELECT *
FROM users
WHERE gender = 1 ORDER BY age LIMIT 5 # 9990件
# WHERE gender = 0 ORDER BY age LIMIT 5 # 10件

# ORDER BY 狙いのインデックス idx_users__age が使われている
# id    select_type table   type    possible_keys   key key_len ref rows    Extra
# 1    SIMPLE  users   INDEX   idx_users__gender   idx_users__age  4  NULL    5  USING WHERE

# WHERE 狙いのインデックス idx_users__gender が使われている
# * id  select_type TABLE   TYPE    possible_keys   KEY key_len ref ROWS    Extra
# 1    SIMPLE  users   ref idx_users__gender   idx_users__gender   1  const   10 USING WHERE; USING filesort
;