Upgrade to Pro — share decks privately, control downloads, hide ads and more …

はじめてのRailsの ソースコードリーディング

Avatar for nakayama-bird nakayama-bird
April 26, 2025
47

はじめてのRailsの ソースコードリーディング

2025/4/26 RUNTEQ.rb

Avatar for nakayama-bird

nakayama-bird

April 26, 2025
Tweet

Transcript

  1. 自己紹介 なかじ @nakayama__bird なかじ/ バード 2020/04-2024/03 公務員 2024/01-2024/10 RUNTEQ(53 期)

    2024/12- エンジニア 先週RubyKaigi に初参加した テックブログ: https://tech.findy.co.jp/entry/2025/ 04/23/080000 2 / 85
  2. アジェンダ 1. はじめに 2. Active Support core_ext について 3. Ruby

    について 4. empty?/present?/blank? からジャンル分け 5. ソースコードを読んでいく 6. パフォーマンス観点の再定義とその問題点 3 / 85
  3. バージョンと聞かれて見に行った場所 # Gemfile # Bundle edge Rails instead: gem "rails",

    github: "rails/rails", branch: "main" gem "rails", "~> 7.1.3", ">= 7.1.3.4" #... gem 'sorcery' gem rails … ! ファイル全体を見ると、カリキュラムで苦しんだsorcery と同じ並びにあるぞ… ! 11 / 85
  4. 【参考】bundler での管理 パッケージ管理にbundler を使っている(バージョン、依存関係など) Bundler provides a consistent environment for

    Ruby projects by tracking and installing the exact gems and versions that are needed.Bundler is an exit from dependency hell, and ensures that the gems you need are present in development, staging, and production. Starting work on a project is as simple as bundle install https://bundler.io/ “ “ 14 / 85
  5. 【Tips 】bundle open <gem 名> 1. $ which code で

    /usr/local/bin/code が返ってくることを確認 2. ↑が返ってこない場合はcode コマンドが使えるようにVScode で設定 https://code.visualstudio.com/docs/setup/mac#_launch-vs-code-from-the- command-line 17 / 85
  6. 【Tips 】bundle open <gem 名> 3. $ vi ~/.zshrc で

    export BUNDLER_EDITOR="code --wait" を追加 4. パスを通す source ~/.zshrc 参考:https://scrapbox.io/nakayamabird/bundle_open_gem でgem のファイルを VScode 確認できるようにする 18 / 85
  7. Active Support core_ext の概要 activesupport/lib/active_support/core_ext core_ext = コア拡張機能(Core Extensions )

    Rails でRuby を便利に使うための拡張機能が用意されているクラス群 20 / 85
  8. Active Support core_ext のメソッド① blank? メソッド そのオブジェクトが空ならtrue, 空でないならfalse を返すメソッド #

    rails console irb(main):001> ['a','b','c'].blank? => false irb(main):001> [].blank? => true Rails 基礎まで進んでいる方だと確実に目にしているメソッド ちなみにRails 入門でも使っている箇所あった 業務内で触るコードでも見かけることが多い 21 / 85
  9. Active Support core_ext のメソッド② present? メソッド present? メソッド: blank? メソッドの逆(否定)

    # rails console irb(main):001> ['a','b','c'].present? => true irb(main):001> [].present? => false # rails console(blank?) irb(main):001> ['a','b','c'].blank? => false irb(main):001> [].blank? => true 22 / 85
  10. 【参考】? のついたメソッド The names of predicate methods (methods that return

    a boolean value) should end in a question mark (i.e. Array#empty?). Methods that don’t return a boolean, shouldn’t end in a question mark. https://rubystyle.guide/#bool-methods-qmark “ “ 24 / 85
  11. 4 はInteger 、'hoge' はString # irb irb(main):001> 4.class => Integer

    4 はInteger (整数)クラスのオブジェクト =>class Integer にあるメソッドが使える! # irb irb(main):001> 'hoge'.class => String 'hoge' はString (文字列)クラスのオブジェクト =>class String にあるメソッドが使える! 30 / 85
  12. メソッドを使ってみる 偶数か判定する even? メソッド # irb irb(main):001 > 4.even? =>

    true 4 がInteger クラスのオブジェクトだ から使える 4 が偶数である https://docs.ruby- lang.org/ja/latest/class/Integer.html 31 / 85
  13. even? メソッドが使えないパターン 偶数か判定する even? メソッド # irb irb(main):001> 'hoge'.class =>

    String irb(main):001> 'hoge'.even? (irb):12:in '<main>': undefined method 'even?' for an instance of String (NoMethodError) 32 / 85
  14. 偶数か偶数じゃないかメソッド # irb irb(main):001* class Integer irb(main):002* def even_or_not_even? irb(main):003*

    if self % 2 == 0 || self % 2 == 1 irb(main):004* true irb(main):005* else irb(main):006* false irb(main):007* end irb(main):008* end irb(main):009> end => :even_or_not_even? irb(main):010> 5.even_or_not_even? => true 整数クラスのメソッドなので実態は全てtrue が返る 33 / 85
  15. Object クラスにあるメソッド object_id メソッド # irb irb(main):001> 4.class => Integer

    irb(main):001> 4.object_id => 9 irb(main):001> 'hoge'.class => String irb(main):001> 'hoge'.object_id => 50920 各オブジェクトに対して一意な整数を返します。 https://docs.ruby-lang.org/ja/latest/method/Object/i/object_id.html “ “ 35 / 85
  16. empty?/present?/blank? 今回見ていく、core_ext の present? ・ blank? メソッドはオブジェクトが空か どうか判定するためのメソッド 似たようなメソッドに empty?

    がある Rails における nil?, empty?, blank?, present? の使い分けとBetter Practice ポイント empty? :Ruby で定義 blank? , present? : Rails のActive Support core_ext で定義 37 / 85
  17. empty?/present?/blank? の違い レシーバ empty? blank? present? nil NoMethodError true false

    true NoMethodError false true false NoMethodError true false [] true true false {} true true false "" true true false " " false true false 38 / 85
  18. 1.nil/true/false レシーバ empty? blank? present? nil NoMethodError true false true

    NoMethodError false true false NoMethodError true false [] true true false {} true true false "" true true false " " false true false 39 / 85
  19. 2. 空白文字列 レシーバ empty? blank? present? nil NoMethodError true false

    true NoMethodError false true false NoMethodError true false [] true true false {} true true false "" true true false " " false true false 40 / 85
  20. 3. 同じ感じの箇所 レシーバ empty? blank? present? nil NoMethodError true false

    true NoMethodError false true false NoMethodError true false [] true true false {} true true false "" true true false " " false true false 41 / 85
  21. 前提 nil はNilClass の唯一のオブジェクト =>NilClass にあるメソッドが使える false はFalseClass の唯一のオブジェクト =>FalseClass

    にあるメソッドが使える true はTrueClass の唯一のオブジェクト =>TrueClass にあるメソッドが使える 45 / 85
  22. NilClass # activesupport/lib/active_support/core_ext/object/blank.rb class NilClass # +nil+ is blank: #

    # nil.blank? # => true # # @return [true] def blank? true end def present? # :nodoc: false end end 46 / 85
  23. FalseClass # activesupport/lib/active_support/core_ext/object/blank.rb class FalseClass # +false+ is blank: #

    # false.blank? # => true # # @return [true] def blank? true end def present? # :nodoc: false end end 47 / 85
  24. present? def present? false end ひたすらfalse がかえる # rails console

    irb(main):007> false.present? => false irb(main):008> nil.present? => false 49 / 85
  25. TrueClass # activesupport/lib/active_support/core_ext/object/blank.rb class TrueClass # +true+ is not blank:

    # # true.blank? # => false # # @return [false] def blank? false end def present? # :nodoc: true end end nil/false と逆の動きをする 50 / 85
  26. irb でempty? メソッド irb(main):001> nil.empty? (irb):2:in `<main>': undefined method `empty?'

    for nil (NoMethodError) irb(main):001> false.empty? (irb):3:in `<main>': undefined method `empty?' for false (NoMethodError) irb(main):001> true.empty? (irb):4:in `<main>': undefined method `empty?' for true (NoMethodError) 52 / 85
  27. empty? とblank? ・present? メソッド TrueClass,FalseClass,NilClass にはempty? メソッドがないため追加でいい感じに定 義している empty? メソッドだとエラーになるところ、blank?

    ・present? メソッドだとboolean で返りエラーにならない レシーバ empty? blank? present? nil NoMethodError true false true NoMethodError false true false NoMethodError true false 53 / 85
  28. empty メソッド empty メソッドでは空白文字列はfalse になる # rails console irb(main):001> "

    ".empty? => false blank? メソッドでは空白文字列はtrue になる # rails console irb(main):001> " ".blank? => true 57 / 85
  29. String クラスのblank? メソッド # activesupport/lib/active_support/core_ext/object/blank.rb class String BLANK_RE = /\A[[:space:]]*\z/

    ENCODED_BLANKS = Concurrent::Map.new do |h, enc| h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING) end # A string is blank if it's empty or contains whitespaces only: # # ''.blank? # => true # ' '.blank? # => true # "\t\n\r".blank? # => true # ' blah '.blank? # => false # # Unicode whitespace is supported: # # "\u00a0".blank? # => true # # @return [true, false] def blank? # The regexp that matches blank strings is expensive. For the case of empty # strings we can speed up this method (~3.5x) with an empty? call. The # penalty for the rest of strings is marginal. empty? || begin BLANK_RE.match?(self) rescue Encoding::CompatibilityError ENCODED_BLANKS[self.encoding].match?(self) end end def present? # :nodoc: !blank? end end 58 / 85
  30. 一部抜粋 # activesupport/lib/active_support/core_ext/object/blank.rb class String BLANK_RE = /\A[[:space:]]*\z/ # 文字列が空白文字列のみで構成されているかどうかの正規表現

    def blank? empty? || # empty?メソッドを呼び出し空文字列判定を行う # "".empty? => true # " ".empty? => false # =>ここでfalseなら以下の処理 begin BLANK_RE.match?(self) # どうやらここで空白文字列判定をしていそう rescue Encoding::CompatibilityError ENCODED_BLANKS[self.encoding].match?(self) end end end 59 / 85
  31. 手元で動かす # irb/rails console irb(main):001> BLANK_RE = /\A[[:space:]]*\z/ => /\A[[:space:]]*\z/

    irb(main):001> karamojiretsu = ' ' => " " irb(main):001> BLANK_RE.match?(karamojiretsu) => true 正規表現を利用して空白文字列の判定をしている 60 / 85
  32. Array クラス # activesupport/lib/active_support/core_ext/object/blank.rb class Array # An array is

    blank if it's empty: # # [].blank? # => true # [1,2,3].blank? # => false # # @return [true, false] alias_method :blank?, :empty? def present? # :nodoc: !empty? end end 62 / 85
  33. Array のempty メソッド Ruby にあるArray クラスのempty メソッドを見ていく # irb/rails console

    irb(main):001> [].empty? => true irb(main):001> [ ].empty? => true irb(main):001> ['らんてくん', 'ロボらんてくん'].empty? => false 自身の要素の数が 0 の時に真を返します。そうでない場合に false を返します。 https://docs.ruby-lang.org/ja/latest/method/Array/i/empty=3f.html “ “ 63 / 85
  34. alias_method とは? # irb irb(main):001* module Kernel irb(main):002* alias_method :foo,

    :puts irb(main):003> end irb(main):004> foo "bar" bar => nil irb(main):005> method(:foo) => #<Method: Object(Kernel)#foo(puts)(*)> foo という名前のメソッドは puts と同じ メソッドの別名を定義します。 https://docs.ruby-lang.org/ja/latest/method/Module/i/alias_method.html “ “ 64 / 85
  35. alias_method とは? # rails console irb(main):001> array = [] =>

    [] irb(main):002> array.method(:blank?) => #<Method: Array#blank?(empty?)()> Array クラスの blank? メソッドはArray クラスの empty? メソッドと同じ class Hash と class Symbol も同じでエイリアスメソッドとして定義されてい る 65 / 85
  36. Object クラスにblank? メソッドがある # activesupport/lib/active_support/core_ext/object/blank.rb class Object def blank? respond_to?(:empty?)

    ? !!empty? : false end # An object is present if it's not blank. # # @return [true, false] def present? !blank? end end このクラスのメソッドは基本的にどこのクラスからも呼び出せる 67 / 85
  37. Object クラスのblank? メソッド def blank? respond_to?(:empty?) ? !!empty? : false

    end 三項演算子になっている respond_to?(:empty?) がtrue なら !!empty? が評価される respond_to?(:empty?) がfalse なら false が返る 68 / 85
  38. respond_to? メソッド Ruby のObject クラスのメソッド # irb irb(main):001> 4.respond_to?(:even?) =>

    true irb(main):001> 'hoge'.respond_to?(:even?) => false オブジェクトがメソッド name を持つとき真を返します。オブジェクトが メソッ ド name を持つというのは、オブジェクトが メソッド name に応答できることを いいます。 https://docs.ruby-lang.org/ja/latest/method/Object/i/respond_to=3f.html “ “ 69 / 85
  39. Array クラスのメソッド # activesupport/lib/active_support/core_ext/object/blank.rb class Array # An array is

    blank if it's empty: # # [].blank? # => true # [1,2,3].blank? # => false # # @return [true, false] alias_method :blank?, :empty? #... end empty? メソッドを呼び出している 71 / 85
  40. Object クラスのメソッド Array クラスはObject クラスを継承している class Object def blank? respond_to?(:empty?)

    ? !!empty? : false end # ... end Array クラスには empty? メソッドがある そのため結局のところempty? メソッドを呼び出している 72 / 85
  41. Object クラスのメソッド irb(main):001> require 'benchmark' => true irb(main):002* class Object

    irb(main):003* def blank? irb(main):004* respond_to?(:empty?) ? !!empty? : false irb(main):005* end irb(main):006> end => :blank? irb(main):007> array = ['a', 'b', 'c'] => ["a", "b", "c"] irb(main):008> method_object = array.method(:blank?) => #<Method: Array(Object)#blank?() (irb):3> irb(main):009> method_object.owner => Object irb(main):010* result = Benchmark.realtime do irb(main):011* 1000000.times { array.blank? } irb(main):012> end => 0.06730700004845858 78 / 85
  42. Array クラスのメソッド ❯ irb irb(main):001> require 'benchmark' => true irb(main):002*

    class Array irb(main):003* alias_method :blank?, :empty? irb(main):004> end => :blank? irb(main):005> array = ['a', 'b', 'c'] => ["a", "b", "c"] irb(main):006> method_object = array.method(:blank?) => #<Method: Array#blank?(empty?)()> irb(main):007> method_object.owner => Array irb(main):008* result = Benchmark.realtime do irb(main):009* 1000000.times { array.blank? } irb(main):010> end => 0.04001499991863966 79 / 85
  43. ここのパフォーマンス改善に関しては議論がある Code that calls present or blank in an active

    record instance is an anti-pattern, to not say wrong. Making it faster is to legitimate that anti-pattern. https://github.com/rails/rails/issues/35059 “ “ 意訳すると『ActiveRecord では、そもそもpresent? やblank? を使うことがアンチ パターンです。それを高速化するということはアンチパターンを正当化すること になります。 』とのことです。 https://qiita.com/ham0215/items/b156d8cc5547ec56c719 “ “ 81 / 85
  44. 内容のまとめ① レシーバ empty? blank? present? nil NoMethodError true false true

    NoMethodError false true false NoMethodError true false [] true true false {} true true false "" true true false " " false true false 82 / 85