ruby-on-railsrubymetaprogrammingactivesupport-concern

Why can't I use method call inside "module" block for define_method?


I have a model class with attr_reader - type_slugs (returns an array of string) which I'd like to use to generate several methods.

For example:

module Orders
  module Extensions
    module Order
      module TypeCheckable
        extend ::ActiveSupport::Concern

        ::Orders::Config.type_slugs.each do |type|
          define_method("#{type}_order_type?") { order_type == type }
        end
      end
    end
  end
end

But it gives me NoMethodError: undefined method `type_slugs' for Orders::Config:Module

Same error happens if I use any model method like:

some_method_defined_in_model.each do |type|
  define_method("#{type}_order_type?") { order_type == type }
end

But it works just fine if I use a word array

%w(message product another).each do |type|
  define_method("#{type}_order_type?") { order_type == type }
end

Is it even possible to use Orders::Config.type_slugs in this case? If not, why so?


Solution

  • From the information you provided, it follows that the module Orders::Config does not have type_slugs method

    If you want your code works, you need define it, something like this:

    module Orders
      module Config
        def self.type_slugs
          %w[message product another]
        end
      end
    end
    

    Or if that list doesn't change dynamically:

    module Orders
      module Config
        TYPE_SLUGS = %w[message product another].freeze
      end
    end
    

    In that case change ::Orders::Config.type_slugs to ::Orders::Config::TYPE_SLUGS

    When use Rails concerns, you should use included for instance methods

    module Orders
      module Extensions
        module Order
          module TypeCheckable
            extend ::ActiveSupport::Concern
    
            included do
              ::Orders::Config::TYPE_SLUGS.each do |type|
                define_method("#{type}_order_type?") { order_type == type }
              end
            end
          end
        end
      end
    end
    

    After that you can use it in your models

    class SuperOrder < ApplicationRecord
      include Orders::Extensions::Order::TypeCheckable
    end
    
    class BestOrder < ApplicationRecord
      include Orders::Extensions::Order::TypeCheckable
    end
    

    And call methods like this

    SuperOrder.new.product_order_type? # compare super order type with "product"
    BestOrder.new.message_order_type? # compare best order type with "message"