Chris Yeung
Chris Yeung

Reputation: 2723

passing variables into dynamically generated classes in Ruby

I am trying to DRY my code by writing a dynamically generated class methods.

What I want is to have multiple error classes and I have the following codes

ruby

class ChatPolicy::Error < StandardError
ERROR_CLASSES = [
    { class_name: 'UserBlacklisted', message: 'Message 1' },
    { class_name: 'UserSuspended', message: 'Message 2' },
    { class_name: 'UserNotEligibleToRent', message: 'Message 3' },
    { class_name: 'MembershipTierNotAllowed', message: 'Message 4' }
]

ERROR_CLASSES.each do |cls|
    Object.const_set(cls[:class_name], Class.new {
        attr_reader :object

        def initialize(object)
            @object = object
            @message = cls[:message]
        end
    })
end
end

However, since in the Class.new {} block, the variable cannot be passed in. I can't initialize the message variable. I am wondering how I can achieve that?

Upvotes: 0

Views: 273

Answers (2)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121020

However, since in the Class.new {} block, the variable cannot be passed in.

That is not about “variable can not be passed in,” this is about “inside a block, the receiver differs and cls local variable can not be resolved.” There are two possible approaches to achieve the goal:

— lookup the message directly from what is visible:

@message = ChatPolicy::Error::ERROR_CLASSES.detect do |hash|
  hash[:class_name] == self.class.name
end[:message]

— or use class_eval:

ERROR_CLASSES.each do |cls|
    Object.const_set(cls[:class_name], class_eval %Q|
      Class.new {
        attr_reader :object

        def initialize(object)
            @object = object
            @message = '#{cls[:message]}' # ⇐ HERE !!!
        end
    }|)
end

UPD the approach by @matt is better; I leave this answer for historical purposes only.

Upvotes: 1

matt
matt

Reputation: 79813

When you define a method with def, you cannot refer to local variables from the outer scope inside the method definition, i.e. the cls variable in the initialize method.

You can refer to such local variables from inside a block, and you can use a block to create a method with define_method.

So in your example you can get it working by changing the line

def initialize(object)

to

define_method(:initialize) do |object|

Now since the method body is a block you can refer to cls inside the body.

Upvotes: 2

Related Questions