Fun with classes and constants
Ruby is somewhat versatile at times. Suppose you want to define a class dynamically, and I do….
class Thing; end
Ok, that’s not dynamic
# still not dynamic but getting there.
def define_a_class
Thing = Class.new
end
# ok then I see
def define_a_class(name)
Object.const_set(name, Class.new)
end
define_a_class('Thing')
=> Thing
This is great but what if you wanted to define a class within a module
define_a_class('Nigel::Thing')
NameError: wrong constant name Nigel::Thing
from (pry):19:in `const_set'
Oh. That sucks. What you’re actually trying to do here is
Object.const_set('Thing', Class.new)
#=> Thing
Nigel.const_set('Thing', Class.new)
#=> Nigel::Thing
Object is for root namespace, and the module name for the nested namespace.
So today we had an issue. We normally define a Permit class if one doesn’t already exist like so.
def define_permit_class
name = "#{self.name}Permit"
Object.const_get(name)
rescue NameError
Object.const_set(name, Class.new(Permit))
end
This works for
Thing.define_permit_class
But obviously not for
Nigel::Thing.define_permit_class
Well, easily fixed. A class can find it’s parent module, or if it is doesn’t have one it’s Object.
Thing.parent
#=> Object
Nigel::Thing.parent
#=> Nigel
So we just refactor this a little and bingo
def define_permit_class
name = "#{self.name.demodulize}Permit"
parent.const_get(name)
rescue NameError
parent.const_set(name, Class.new(Permit))
end
Ok, it now seems that parent is deprecated in Rails 6.1. module_parent it is.
DEPRECATION WARNING: `Module#parent` has been renamed to `module_parent`. `parent` is deprecated and will be removed in Rails 6.1.