Is it better to pass a class or a stringified class name around in Ruby?
For example, I have a parser/builder that can determine the type of class of a parsed record. I pass this to an importer class, but I can pass the name of the class as a string or as its constant. Is there a reason not to do one or the other?
x = ParsedRecord.new(value: 1, type: MyClass)
vs
x = ParsedRecord.new(value: 2, type: 'MyClass')
And the importer would either:
x.type.new
vs
x.type.constantize.new
Passing the class seems easier but I don't see it around a lot (ie. Rails makes you stringify the class names for associations). Perhaps it has to do with load order/autoloading?
Unless you need to lazy-load the class name, it's better to pass the constant directly.
The reason is because converting a String into the corresponding class-name constant is an extra-step and will add some overhead to the execution. If you already have the class name, just pass it. There is no advantage of converting it into a string, and then back into a constant.
The reason because you see strings passed around in other libraries, is because in order to pass MyClass as parameter the class definition must be loaded. In some cases, for example in Rails, you are referencing a class name of a class that will be lazy-loaded (that's the typical case of the rescue_from macro). Using a String doesn't force you to load the class definition until you effectively need it.
A class is an object just like any other. So, this is really not about classes.
Just ask yourself: would you rather pass an object as an argument, or a String containing the name of some variable in some scope somewhere, which you then metaprogrammatically turn into an object again?
Oh, and what about classes that have no name? Or whose name does not correspond to the variable through which they are reachable?
a = Class.new
a.name
# => nil
# has no name
A = a
a.name
# => 'A'
# has name A, is reachable via A and a
A = nil
a.name
# => 'A'
# still has name A, but is no longer reachable via A, only a
B = a
a = nil
B.name
# => 'A'
# still has name A, but is now only reachable via B
Passing the class itself also allows you to just create an anonymous class inline, e.g. as a stub in unit testing:
ParsedRecord.new(value: 1, type: Class.new)
ParsedRecord.new(value: 2, type: Class.new do def initialize; @foo = 42 end end)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With