Built-in Ruby types include a method with the same name as the type to idempotently convert some value into that type.
irb(main):001:0> Array('some value') => ["some value"] irb(main):002:0> Array(['some value']) => ["some value"]
This can be done with custom types/objects, but there are a couple of gotchas.
- The converter method is named after a class, it’s not a class method, so it must be defined outside the class. Unless something like
Email.Email(object)is acceptable 😅.
- Since the converter method is not directly linked to the class it belongs to, there can be autoloading issues in Rails applications.
These two gotchas allow for a few different ways to manage these converter methods.
One strategy is avoiding copying the built-in converter methods.
Although defining uppercased methods for your custom classes to create instances of it looks like an interesting idea at first glance, it is rather confusing.
class.instead, which enables a very similar syntax, but uses the real constant it belongs to. An example of such usage is Set
class Email def self.(obj) return obj if obj.is_a?(self) new(obj) end end
In a situation where constant-based autoloading is not a concern, the converter method could be defined in the same file as the class.
# lib/email.rb class Email def initialize(email) @email = email end end def Email(obj) return obj if obj.is_a?(Email) Email.new(obj) end
In a Rails application, another option to allow for
Email() that might be controversial but still works: define the converters in an initializer.
# config/initializers/vale_objects.rb def Email(obj) return obj if obj.is_a?(Email) Email.new(obj) end
# app/value_objects/email.rb class Email def initialize(email) @email = email end end