こんにちは!kossyです!
さて、今回はOSSのソースコードを読む際に役に立つtrace_locationを使ってみたので、ブログに残してみたいと思います。
試す
まずは公式ドキュメントを参考にして進めます。
# Gemfile gem 'trace_location'
$ bundle
以下、rails cで試しました。
$ config = Rails.application.config.database_configuration[Rails.env] {"adapter"=>"postgresql", "encoding"=>"unicode", "pool"=>5, "username"=>"root", "password"=>"password", "host"=>"db", "database"=>"sample_api_development"} $ TraceLocation.trace do # You just surround you want to track the process. ActiveRecord::Base.establish_connection(config) end Created at /app/log/trace_location-2021081506081629010190.md => true
特に指定しなければMarkdown拡張子でlog配下に出力されます。
オプション名 | 内容 | 例 |
format | :md, :log, :csv (default: :md) | :md |
match | Regexp, Symbol, String or Array for allow list | [:activerecord, :activesupport] |
ignore | Regexp, Symbol, String or Array for deny list | /bootsnap activesupport/ |
約2500行のマークダウンが出力されたので詳細は割愛します、、、
establish_connetctionメソッドの処理から出力がされていました。
# ActiveRecord::ConnectionHandling.establish_connection def establish_connection(config_or_env = nil) config_hash = resolve_config_for_connection(config_or_env) connection_handler.establish_connection(config_hash) end # called from (pry):4 # /usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/connection_handling.rb:162
deviseのvalid_password?メソッドをtraceしてみる
こちらは300行ほどだったため、全て貼り付けてみます。
Generated by [trace_location](https://github.com/yhirano55/trace_location) at 2021-08-15 07:21:57 +0000 <details open> <summary>/usr/local/bundle/gems/devise-4.8.0/lib/devise/models/database_authenticatable.rb:71</summary> ##### Devise::Models::DatabaseAuthenticatable#valid_password? ```ruby def valid_password?(password) Devise::Encryptor.compare(self.class, encrypted_password, password) end # called from (pry):12 ``` </details> <details open> <summary>/usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/attribute_methods/read.rb:15</summary> ##### User::GeneratedAttributeMethods#encrypted_password ```ruby def #{temp_method_name} name = #{attr_name_expr} _read_attribute(name) { |n| missing_attribute(n, caller) } end # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise/models/database_authenticatable.rb:72 ``` </details> <details open> <summary>/usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/attribute_methods/read.rb:37</summary> ##### ActiveRecord::AttributeMethods::Read#_read_attribute ```ruby def _read_attribute(attr_name, &block) # :nodoc sync_with_transaction_state if @transaction_state&.finalized? @attributes.fetch_value(attr_name.to_s, &block) end # called from /usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/attribute_methods/read.rb:17 ``` </details> <details open> <summary>/usr/local/bundle/gems/activemodel-6.0.3.7/lib/active_model/attribute_set.rb:40</summary> ##### ActiveModel::AttributeSet#fetch_value ```ruby def fetch_value(name, &block) self[name].value(&block) end # called from /usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/attribute_methods/read.rb:39 ``` </details> <details open> <summary>/usr/local/bundle/gems/activemodel-6.0.3.7/lib/active_model/attribute_set.rb:15</summary> ##### ActiveModel::AttributeSet#[] ```ruby def [](name) attributes[name] || Attribute.null(name) end # called from /usr/local/bundle/gems/activemodel-6.0.3.7/lib/active_model/attribute_set.rb:41 ``` </details> <details open> <summary>/usr/local/bundle/gems/activemodel-6.0.3.7/lib/active_model/attribute_set/builder.rb:38</summary> ##### ActiveModel::LazyAttributeHash#[] ```ruby def [](key) delegate_hash[key] || assign_default_value(key) end # called from /usr/local/bundle/gems/activemodel-6.0.3.7/lib/active_model/attribute_set.rb:16 ``` </details> <details open> <summary>/usr/local/bundle/gems/activemodel-6.0.3.7/lib/active_model/attribute.rb:40</summary> ##### ActiveModel::Attribute#value ```ruby def value # `defined?` is cheaper than `||=` when we get back falsy values @value = type_cast(value_before_type_cast) unless defined?(@value) @value end # called from /usr/local/bundle/gems/activemodel-6.0.3.7/lib/active_model/attribute_set.rb:41 ``` </details> <details open> <summary>/usr/local/bundle/gems/devise-4.8.0/lib/devise/encryptor.rb:14</summary> ##### Devise::Encryptor.compare ```ruby def self.compare(klass, hashed_password, password) return false if hashed_password.blank? bcrypt = ::BCrypt::Password.new(hashed_password) if klass.pepper.present? password = "#{password}#{klass.pepper}" end password = ::BCrypt::Engine.hash_secret(password, bcrypt.salt) Devise.secure_compare(password, hashed_password) end # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise/models/database_authenticatable.rb:72 ``` </details> <details open> <summary>/usr/local/bundle/gems/activesupport-6.0.3.7/lib/active_support/core_ext/object/blank.rb:121</summary> ##### String#blank? ```ruby 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 # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise/encryptor.rb:15 ``` </details> <details open> <summary>/usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/password.rb:55</summary> ##### BCrypt::Password#initialize ```ruby def initialize(raw_hash) if valid_hash?(raw_hash) self.replace(raw_hash) @version, @cost, @salt, @checksum = split_hash(self) else raise Errors::InvalidHash.new("invalid hash") end end # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise/encryptor.rb:16 ``` </details> <details open> <summary>/usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/password.rb:73</summary> ##### BCrypt::Password#valid_hash? ```ruby def valid_hash?(h) self.class.valid_hash?(h) end # called from /usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/password.rb:56 ``` </details> <details open> <summary>/usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/password.rb:49</summary> ##### BCrypt::Password.valid_hash? ```ruby def valid_hash?(h) /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/ === h end # called from /usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/password.rb:74 ``` </details> <details open> <summary>/usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/password.rb:81</summary> ##### BCrypt::Password#split_hash ```ruby def split_hash(h) _, v, c, mash = h.split('$') return v.to_str, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str end # called from /usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/password.rb:58 ``` </details> <details open> <summary>/usr/local/bundle/gems/devise-4.8.0/lib/devise/models.rb:37</summary> ##### Devise::Models::DatabaseAuthenticatable::ClassMethods.pepper ```ruby def #{accessor} if defined?(@#{accessor}) @#{accessor} elsif superclass.respond_to?(:#{accessor}) superclass.#{accessor} else Devise.#{accessor} end end # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise/encryptor.rb:17 ``` </details> <details open> <summary>/usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/dynamic_matchers.rb:6</summary> ##### ActiveRecord::DynamicMatchers.respond_to_missing? ```ruby def respond_to_missing?(name, _) if self == Base super else match = Method.match(self, name) match && match.valid? || super end end # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise/models.rb:40 ``` </details> <details open> <summary>/usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/dynamic_matchers.rb:32</summary> ##### ActiveRecord::DynamicMatchers::Method.match ```ruby def match(model, name) klass = matchers.find { |k| k.pattern.match?(name) } klass.new(model, name) if klass end # called from /usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/dynamic_matchers.rb:10 ``` </details> <details open> <summary>/usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/dynamic_matchers.rb:37</summary> ##### ActiveRecord::DynamicMatchers::Method.pattern ```ruby def pattern @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/ end # called from /usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/dynamic_matchers.rb:33 ``` </details> <details open> <summary>/usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/dynamic_matchers.rb:37</summary> ##### ActiveRecord::DynamicMatchers::Method.pattern ```ruby def pattern @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/ end # called from /usr/local/bundle/gems/activerecord-6.0.3.7/lib/active_record/dynamic_matchers.rb:33 ``` </details> <details open> <summary>/usr/local/bundle/gems/activesupport-6.0.3.7/lib/active_support/core_ext/module/attribute_accessors.rb:57</summary> ##### Devise.pepper ```ruby def self.#{sym} @@#{sym} end # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise/models.rb:43 ``` </details> <details open> <summary>/usr/local/bundle/gems/activesupport-6.0.3.7/lib/active_support/core_ext/object/blank.rb:25</summary> ##### Object#present? ```ruby def present? !blank? end # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise/encryptor.rb:17 ``` </details> <details open> <summary>/usr/local/bundle/gems/activesupport-6.0.3.7/lib/active_support/core_ext/object/blank.rb:56</summary> ##### NilClass#blank? ```ruby def blank? true end # called from /usr/local/bundle/gems/activesupport-6.0.3.7/lib/active_support/core_ext/object/blank.rb:26 ``` </details> <details open> <summary>/usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/engine.rb:47</summary> ##### BCrypt::Engine.hash_secret ```ruby def self.hash_secret(secret, salt, _ = nil) if valid_secret?(secret) if valid_salt?(salt) if RUBY_PLATFORM == "java" Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s.to_java_bytes, salt.to_s) else __bc_crypt(secret.to_s, salt) end else raise Errors::InvalidSalt.new("invalid salt") end else raise Errors::InvalidSecret.new("invalid secret") end end # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise/encryptor.rb:20 ``` </details> <details open> <summary>/usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/engine.rb:87</summary> ##### BCrypt::Engine.valid_secret? ```ruby def self.valid_secret?(secret) secret.respond_to?(:to_s) end # called from /usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/engine.rb:48 ``` </details> <details open> <summary>/usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/engine.rb:82</summary> ##### BCrypt::Engine.valid_salt? ```ruby def self.valid_salt?(salt) !!(salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/) end # called from /usr/local/bundle/gems/bcrypt-3.1.16/lib/bcrypt/engine.rb:49 ``` </details> <details open> <summary>/usr/local/bundle/gems/devise-4.8.0/lib/devise.rb:500</summary> ##### Devise.secure_compare ```ruby def self.secure_compare(a, b) return false if a.blank? || b.blank? || a.bytesize != b.bytesize l = a.unpack "C#{a.bytesize}" res = 0 b.each_byte { |byte| res |= byte ^ l.shift } res == 0 end # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise/encryptor.rb:21 ``` </details> <details open> <summary>/usr/local/bundle/gems/activesupport-6.0.3.7/lib/active_support/core_ext/object/blank.rb:121</summary> ##### String#blank? ```ruby 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 # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise.rb:501 ``` </details> <details open> <summary>/usr/local/bundle/gems/activesupport-6.0.3.7/lib/active_support/core_ext/object/blank.rb:121</summary> ##### String#blank? ```ruby 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 # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise.rb:501 ``` </details> <details open> <summary>/usr/local/bundle/gems/activesupport-6.0.3.7/lib/active_support/core_ext/numeric/conversions.rb:105</summary> ##### ActiveSupport::NumericWithFormat#to_s ```ruby def to_s(format = nil, options = nil) case format when nil super() when Integer, String super(format) when :phone ActiveSupport::NumberHelper.number_to_phone(self, options || {}) when :currency ActiveSupport::NumberHelper.number_to_currency(self, options || {}) when :percentage ActiveSupport::NumberHelper.number_to_percentage(self, options || {}) when :delimited ActiveSupport::NumberHelper.number_to_delimited(self, options || {}) when :rounded ActiveSupport::NumberHelper.number_to_rounded(self, options || {}) when :human ActiveSupport::NumberHelper.number_to_human(self, options || {}) when :human_size ActiveSupport::NumberHelper.number_to_human_size(self, options || {}) when Symbol super() else super(format) end end # called from /usr/local/bundle/gems/devise-4.8.0/lib/devise.rb:502 ``` </details>
valid_password?メソッドは、引数に与えられたpasswordが正しいかどうかを検証するメソッドですが、 true か false かを判定するまでに多岐に渡るメソッドが呼び出されていることがわかります。
このGemがあれば、OSSのコードリーディングが捗りそうですね。