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

メタプログラミングと モンキーパッチの話

Avatar for bqnq bqnq
October 31, 2025

メタプログラミングと モンキーパッチの話

Avatar for bqnq

bqnq

October 31, 2025

More Decks by bqnq

Other Decks in Technology

Transcript

  1. 2 ⾃⼰紹介 EC事業部 プロダクトチーム 2025年 新卒⼊社 菊地 信志 bqnq (ばな) 25卒

    GMOペパボ株式会社 EC事業部 普段は Rails, MySQL, Docker を触っています • Matzと同じ⼤学出⾝です! • LT会5分だと思ってました... • Twitter : @_bqnq
  2. Railsがどこでメタプログラミングを使っているのか? 7 • ActiveRecordでは2つのモデル間の関連付けに利⽤ Railsでの利⽤例 class Author < ApplicationRecord has_many

    :books, dependent: :destroy end class Book < ApplicationRecord belongs_to :author end @book = @author.books.create(published_at: Time.now)
  3. Railsがどこでメタプログラミングを使っているのか? 8 • ActiveRecordでは2つのモデル間の関連付けに利⽤ Railsでの利⽤例 class Author < ApplicationRecord has_many

    :books, dependent: :destroy end class Book < ApplicationRecord belongs_to :author end @book = @author.books.create(published_at: Time.now) メタプログラミングはいいことばかり?
  4. モンキーパッチについて 10 • クラス‧メソッドを再定義して挙動を変えること • フレームワークの挙動を意図的に変更 問題点: モンキーパッチ ActiveRecord::Base.singleton_class.prepend(Module.new do

    def belongs_to(name, **options) super(name, **options.merge(optional: true)) # ← 強制的にoptionalに変更 end end) 仕様上、孤児レコードを作れてしまう
  5. モンキーパッチの事例 11 • The Case Against Monkey Patching, From a

    Rails Core Team Member • MySQLが⾃動的に発⽣させる警告を検知するための モンキーパッチを適⽤ • SQL実⾏時にデータ破壊を防ぐため警告をログを記録 • 内部API変更‧アダプタ実装の変更に脆弱 Shopifyの事例 https://shopify.engineering/the-case-against-monkey-patching
  6. モンキーパッチの事例 12 https://github.com/Shopify/activerecord-pedantmysql2-adapter module ActiveRecord module ConnectionAdapters class Mysql2Adapter alias_method

    :orig_execute, :execute def execute(sql, name = nil) result = orig_execute(sql, name) warn_if_needed result end def warn_if_needed warnings = execute("SHOW WARNINGS").to_a Rails.logger.warn(warnings) if warnings.present? end end end end
  7. モンキーパッチの事例 13 https://github.com/Shopify/activerecord-pedantmysql2-adapter module ActiveRecord module ConnectionAdapters class Mysql2Adapter alias_method

    :orig_execute, :execute def execute(sql, name = nil) result = orig_execute(sql, name) warn_if_needed result end def warn_if_needed warnings = execute("SHOW WARNINGS").to_a Rails.logger.warn(warnings) if warnings.present? end end end end シグネチャの変更に弱い def execute(sql, name = nil, binds = [], prepare: false) ログ&計測の仕様変更に弱い