第1回コードリーディング勉強会をしました!!!
今回はrailsのペアコードリーディング勉強会を行いました!!スクールの同期二人でやりましたよ( ´△`)
なぜペアコードリーディングなのか?
・実際の業務ではコードを読むことが多い。
・あのMatzさんもコードリーディングをすすめている!(コードは知識の宝ですよね。)
・一人で理解するより、二人で確認して理解することで、正しい理解を得やすい。
やり方
下記をペアでモニターに向かいながらやりました!
①Railsの機能で、仕組みを知りたい機能を決める
②その機能を再現するコードを書く
③コードの中にbinding.pryを埋め込んで、実行中断状態にする。
④consoleのstepで一行づつ処理を解読する。
下記を参考にさせていただきました。ありがとうございます!
やってみた結果は??
継続して続ければ、見た目複雑なコードに対して拒否感はなくなりそう!!
またコンソールで一つ一つ分解してみるので、読解力がつきます!!
ただ、一人でやるのは厳しいので、ペアでやると楽しくできそうです( ´ ▽ ` )
実際にこんな感じでやってみたよ〜
まず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入門
インスタンスを呼び出している。なぜ??笑
最後まで行かなくても時間を区切って読めるだけ読んで、読解力が付けられそうです!いろんなコードを読みたいなぁ〜