💨

クラスメソッドを利用して動的に指定したインスタンスメソッドを追加する方法

2024/12/01に公開

クラスXのfooメソッドをコール時に指定のattributeを渡すことで、動的にattribute付きのbarメソッドを各インスタンスに定義する。

継承クラスごとにfooメソッドのコール有無および名前の切り替えが可能。

class X
  def self.foo(attribute)
    include InstanceMethods.new(attribute)
  end

  class InstanceMethods < Module
    def initialize(attribute)
      define_method(:"bar_#{attribute}") do
        puts "bar_#{attribute}"
      end
    end
  end
end

class Xfoo < X
  foo :attribute
end

xfoo = Xfoo.new
xfoo.bar_attribute
#=> bar_attribute

railsのhas_secure_passwordで利用されており、has_secure_password :barでattributeを引数にして動的にインスタンスメソッドが定義されている。

https://api.rubyonrails.org/v7.1/classes/ActiveModel/SecurePassword/ClassMethods.html

https://github.com/rails/rails/blob/6f0d1ad14b92b9f5906e44740fce8b4f1c7075dc/activemodel/lib/active_model/secure_password.rb#L112

      def has_secure_password(attribute = :password, validations: true)
        # Load bcrypt gem only when has_secure_password is used.
        # This is to avoid ActiveModel (and by extension the entire framework)
        # being dependent on a binary library.
        begin
          require "bcrypt"
        rescue LoadError
          warn "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install."
          raise
        end

        include InstanceMethodsOnActivation.new(attribute)

補足

単なるインスタンスメソッドの追加はmoduleをincludeすればよい。

class X
  def self.foo
    include InstanceMethods
  end

  module InstanceMethods
    def bar
      puts "bar"
    end
  end
end

class Xfoo < X
  foo
end

xfoo = Xfoo.new
xfoo.bar
# => bar

Discussion