アラサーからのエンジニア転身blog

プログラミングの勉強や転職に関して記事にしていきます。

第1回コードリーディング勉強会をしました!!!

今回はrailsのペアコードリーディング勉強会を行いました!!スクールの同期二人でやりましたよ( ´△`)

 

なぜペアコードリーディングなのか?

・実際の業務ではコードを読むことが多い。

・あのMatzさんもコードリーディングをすすめている!(コードは知識の宝ですよね。)

・一人で理解するより、二人で確認して理解することで、正しい理解を得やすい。



 

やり方


下記をペアでモニターに向かいながらやりました!

Railsの機能で、仕組みを知りたい機能を決める

②その機能を再現するコードを書く

③コードの中にbinding.pryを埋め込んで、実行中断状態にする。

④consoleのstepで一行づつ処理を解読する。 

 

 下記を参考にさせていただきました。ありがとうございます!

www.itmedia.co.jp

qiita.com

やってみた結果は??

継続して続ければ、見た目複雑なコードに対して拒否感はなくなりそう!!
またコンソールで一つ一つ分解してみるので、読解力がつきます!!

ただ、一人でやるのは厳しいので、ペアでやると楽しくできそうです( ´ ▽ ` )

実際にこんな感じでやってみたよ〜

まずCodeReadingという新しいプロジェクトを作成

$ rails new CodeReading -d mysql 

デバック系のgemを追加!!

gem 'pry-rails'
gem 'pry-doc'
gem 'pry-byebug'

gemを追加したらおきまりのこれ

$ bundle install

Userモデルを作成 この時Usersテーブルに関するマイグレーションファイルもできます。

$ rails g model User name:string

おおお、DBを作りましょう!

$ bundle exec rake db:create

migrationファイルをUPにしましょう

$ bundle exec rake db:migrate


user.rbに確認したいコードを記載します。

class User < ApplicationRecord
  binding.pry
  User.new(name: "sakurai")
end

こんな感じでbinding.pryで処理を止めてstepで一行づつ読んできます。
今回はどのようにnewされるのかを見ていきます。
(後々わかりますが....解読するコード量が多すぎてキリのいいところで切りました笑)



rails cでコンソールへ!

$ rails c

[1] pry(main)> User.new

From: /Users/projects/codereading/app/models/user.rb @ line 3 :

    1: class User < ApplicationRecord
    2:   binding.pry
 => 3:   User.new(name: "sakurai")
    4: end

現在3行目の処理に入るところでストップしていますので、stepコマンドで処理の内容を見てましょう!





[1] pry(User)> step

From: /Users/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.2.1/lib/active_record/inheritance.rb @ line 51 ActiveRecord::Inheritance::ClassMethods#new:

    50: def new(attributes = nil, &block)
 => 51:   if abstract_class? || self == Base
    52:     raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
    53:   end
    54: 
    55:   if has_attribute?(inheritance_column)
    56:     subclass = subclass_from_attributes(attributes)
    57: 
    58:     if subclass.nil? && base_class == self
    59:       subclass = subclass_from_attributes(column_defaults)
    60:     end
    61:   end
    62: 
    63:   if subclass && subclass != self
    64:     subclass.new(attributes, &block)
    65:   else
    66:     super
    67:   end
    68: end

OHHHH!!Is this new method?!!
きましたよ〜これがメゾットの中身だ!!
よし読み解くぞ〜〜( ´△`)
・・・・・・・・・・・・・・・・
あれれ意味不明(*_*)

大丈夫!!落ち着いていけば読めるぞ!

=> 51: if abstract_class? || self == Base 
現在はこの位置で処理は停止している。

もし「abstract_class? OR self と Base が同じ」 であればif以下の処理を実行する。ですね。



abstract_class? 、 self == Baseが何をコンソール上で見てみましょう!!

[6] pry(User)> abstract_class?
=> nil

abstract_class?はnilですね。




? abstract_class? こんな風にコマンドを打てば abstract_class?が何か教えてくれるぞ!!

[3] pry(User)> ? abstract_class?

From: /Users/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.2.1/lib/active_record/inheritance.rb @ line 150:
Owner: ActiveRecord::Inheritance::ClassMethods
Visibility: public
Signature: abstract_class?()
Number of lines: 1

Returns whether this class is an abstract class or not.

Returns whether this class is an abstract class or not.
このクラスが抽象的なクラスどうか返すよ。
ほうほう・・・・抽象的なクラスってなんやねん( ´△`)






一旦このまま進みましょう!
self も Baseもみちゃいましょう!

[4] pry(User)> self
=> User (call 'User.connection' to establish a connection)
[5] pry(User)> Base
=> ActiveRecord::Base

self == Base これは成立しないですね。
UserクラスとActiveRecord::Baseクラスは違いますものね。
ちなみにこんな関係ですよ。
User < ApplicationRecord < ActiveRecord::Base



結果的にfalseを返すのでifの中は処理されないようです。
if abstract_class? || self == Base 




はいstepします。
stepするとabstract_class?を定義しているコードに飛びます!!
これはコード処理の流れがわかる!!

    151: def abstract_class?
 => 152:   defined?(@abstract_class) && @abstract_class == true
    153: end

まさに152行目で@abstract_classが定義されているかどうか?かつ
@abstract_class == trueなのかをみています。

ちなみにabstract_classが何かここになんとなく書いてありました。
ざっくりいうとあるモデルがあって、対象となるテーブルがなければそればabstract_classということになるようです。
なので今回はabstract_classはないですね!UserモデルはUsersテーブル持ってますもんね。

[Rails] self.abstract_class = true の意味と挙動 « Codaholic





はい次step( ´△`)

    50: def new(attributes = nil, &block)
    51:   if abstract_class? || self == Base
    52:     raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
    53:   end
    54: 
 => 55:   if has_attribute?(inheritance_column)
    56:     subclass = subclass_from_attributes(attributes)
    57: 
    58:     if subclass.nil? && base_class == self
    59:       subclass = subclass_from_attributes(column_defaults)
    60:     end
    61:   end
    62: 
    63:   if subclass && subclass != self
    64:     subclass.new(attributes, &block)
    65:   else
    66:     super
    67:   end
    68: end

newに戻って55行目の処理にいきましたね!!





if has_attribute?(inheritance_column)とはなんでしょうか?みていきましょう。

[12] pry(User)> ? has_attribute?

From: /Users/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.2.1/lib/active_record/attribute_methods.rb @ line 293:
Owner: ActiveRecord::AttributeMethods
Visibility: public
Signature: has_attribute?(attr_name)
Number of lines: 9

Returns true if the given attribute is in the attributes hash, otherwise false.

  class Person < ActiveRecord::Base
  end

  person = Person.new
  person.has_attribute?(:name)    # => true
  person.has_attribute?('age')    # => true
  person.has_attribute?(:nothing) # => false

ほうほうこれは引数に当てているカラムをUsersテーブルが持っているか持っていかを判定するメゾットのようです。






では引数のinheritance_columnをみてみましょう。

[13] pry(User)> inheritance_column
=> "type"

はい出ました!type!!
なんでしょうか......typeカラムなんてあったっけ......



[14] pry(User)> has_attribute?(inheritance_column)
   (0.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
=> false

ちなみにこちらはfalseなのでUsersテーブルはtypeというカラムを持っていないことを確認しているみたいですね。
(あとあとわかりますが、継承カラムと同じ名前のものを持っていいないかチェックしているようです。)



このままstepして次の処理にいきます!

    252: def inheritance_column
 => 253:   (@inheritance_column ||= nil) || superclass.inheritance_column
    254: end

ここではinheritance_columnメゾットの定義内容がわかります!!
(@inheritance_column ||= nil) は左辺がnilなら右辺の値を左辺に代入するです。
左辺がnilでなければ、左辺はそのままです。ちなみに@inheritance_columnは定義されておらず,nilなので結果としてnilを返します。
superclass.inheritance_columnは"type"です。
結果として"type"を返します。



inheritance_columnは単一テーブルを複数モデルを利用する際の継承カラムを呼び出すメゾットのようです。
継承カラムはデフォでtypeという予約後になっています。
答えはここになりました。
Usersテーブルが継承カラムと同じカラム名を持っていないかのチェック機能のようです。
ActiveRecordで"type"という名前のカラムがあるとエラーが出る場合の対応




さあstep!!!!!ウェーイ!!

    229: def has_attribute?(attr_name)
 => 230:   attribute_types.key?(attr_name.to_s)
    231: end

has_attribute?(attr_name)これの引数には"type"が入るので、attr_nameは"type"ですね。




attribute_typesが何かみてみましょしょう!!

[26] pry(User)> attribute_types 
=> {"id"=>
  #<ActiveModel::Type::Integer:0x007fc573c88170
   @limit=8,
   @precision=nil,
   @range=
    -9223372036854775808...9223372036854775808,
   @scale=nil>,
 "name"=>
  #<ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString:0x007fc57498a7b0
   @limit=255,
   @precision=nil,
   @scale=nil>,
 "created_at"=>
  #<ActiveRecord::Type::DateTime:0x007fc574989298
   @limit=nil,
   @precision=0,
   @scale=nil>,
 "updated_at"=>
  #<ActiveRecord::Type::DateTime:0x007fc574989298
   @limit=nil,
   @precision=0,
   @scale=nil>}

これはUsersテーブルのカラム属性情報がハッシュになっているようです。


ってことは下記のコードはカラム名に"type"があるかないかを判定しているコードとわかりますね!
attribute_types.key?(attr_name.to_s)


ほいstep( ´△`)
attribute_typesをどうやって定義しているからコードがたくさんあります。





最終的に2時間程度でsuperまでいきました。

From: /Users/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.2.1/lib/active_record/inheritance.rb @ line 51 ActiveRecord::Inheritance::ClassMethods#new:

    50: def new(attributes = nil, &block)
 51:   if abstract_class? || self == Base
    52:     raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
    53:   end
    54: 
    55:   if has_attribute?(inheritance_column)
    56:     subclass = subclass_from_attributes(attributes)
    57: 
    58:     if subclass.nil? && base_class == self
    59:       subclass = subclass_from_attributes(column_defaults)
    60:     end
    61:   end
    62: 
    63:   if subclass && subclass != self
    64:     subclass.new(attributes, &block)
    65:   else
    => 66:     super
    67:   end
    68: end


superは出力すると生成したインスタンスを呼び出します。

[36] pry(User)> super
=> #<User:0x007fc57041e460
 id: nil,
 name: "sakurai",
 created_at: nil,
 updated_at: nil>

superって親クラスのメゾットを呼び出すメゾットですよね。。。ええええ。
スーパークラスのメソッドを呼び出す - クラスの継承 - Ruby入門

インスタンスを呼び出している。なぜ??笑

最後まで行かなくても時間を区切って読めるだけ読んで、読解力が付けられそうです!いろんなコードを読みたいなぁ〜