LoginSignup
0
0

More than 1 year has passed since last update.

キレイなMonkeypatchを書きましょう!(Gemの内容を別のGemでMonkeypatchした話)

Last updated at Posted at 2022-02-05

RubyのMonkeypatchとは?

身近な言葉のモンキー・パンチ、ではなく、パッチですね。
MonkeypatchはRubyでクラス・モジュールを開いて、その一部・もしくは全部を書き換えるということです。
Rubyの最大の魅力点でもありながら、あまりやらない方がいいとも言われています。

実はRailsに入っているActiveSupportというものがあって、その中にはMonkeypatchがいっぱい集まっています。
Twitter上でこれがいいことかどうかについて時々炎上しています。

Stringクラスで簡単な例

class String
  def neko
    replace "cat"
  end
end
cat = "neko"
cat.neko
cat
# => "cat"

module CatHelper
  def neko
    replace "neko"
  end
end

String.prepend CatHelper
cat = "neko"
cat.neko
cat
# => "neko"

# String.include での結果も試してみて!

なので、安全にモンキーパッチングを楽しみましょう!

Monkeypatchを利用した時の話

さきほど翻訳機能をStringに入れるGem, sampokuokkanen/ActiveTranslateSelfにI18nバックエンドとしてDBを使っている場合、
翻訳がない場合は自動的に翻訳するという機能です。パッチででこのGemの中のメソッドを変えたかったです。

最初は以下のような感じで書こうと思っていました:

module I18n
  module Backend
    class ActiveRecord
      module Missing
        def store_default_translation(locale, key, interpolations)
          translation = ActiveRecord::Translation.new locale: locale.to_s, key: key
          default = I18n.t(key, locale: I18n.default_locale
          translation.value = default.public_send "to_#{locale}" if default.respond_to?("to_#{locale}")
          translation.interpolations = interpolations
          translation.save
        end
      end
    end
  end
end

ただ、これだと結構見にくいですし、フォルダー構成も結構奥まってしまいますし、あまりキレイではないです。
余談ですが、
Railsの中だとActiveRecordはモジュールとして定義されていて、ここだとクラス、ということでもZeitwerkに怒られていましたが。¥

どうやってもっとキレイに書けるかというと、prependを使います。
(今まで正直あまりprepend使ってなかったです)

モジュールを呼び出す前に、モジュールの中を一部差し替えたいので、こちらのパッチは以下のように定義します:

module ActiveTranslateSelf
  module Missing
    def store_default_translation(locale, key, interpolations)
      translation = I18n::Backend::ActiveRecord::Translation.new locale: locale.to_s, key: key
      # デフォルトに設定されている言語の翻訳から訳します
    default = I18n.t(key, locale: I18n.default_locale)
      # ここで実際に値を訳しています
      translation.value = default.public_send "to_#{locale}" if default.respond_to?("to_#{locale}")
      translation.interpolations = interpolations
      translation.save
    end
  end
end

ネームスペースも自分のGem配下になっていて、かなりわかりやすいかと思います。

では、実際にパッチを適用しましょう!

I18n::Backend::ActiveRecord::Missing.prepend ActiveTranslateSelf::Missing
I18n::Backend::Simple.include I18n::Backend::ActiveRecord::Missing

prepend使っているので、どの段階でどこが変わっているのがわかりやすいかと思います。

面白いので仕事のRailsアプリでも活用しよう!・・・というわけにはいかない

結構責任も問いますので、普通にRailsをやている時はほとんど必要になることはないかと思います。
Gemアップデートの時に壊れやすくもなりますので、あまり使いたくいないです。

他にやり方があれば、Monkeypatchは避けましょう!
誘惑に負けないことです!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0