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.
Consider defining 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