Long time no see ElixirForum. I’m working at a Rails shop right now, but I still like to tinker with Elixir every now and then. We’ve recently adopted a pattern for writing our objects, dubbed the Useful Object pattern: GitHub - eventide-project/useful-objects
The goal for this pattern is as follows:
An object’s dependencies are initialized by default to a safe , and inert (in terms of side effects) implementation of the dependency’s interface.
An object’s class interface provides a means of constructing an instance of the object, including the initialization of its dependencies to active , operational implementations of the dependencies’ interfaces, eg: an active database connection, an active payment gateway client, etc.
An object may record telemetry about its execution as it is executing. The activation of telemetry instrumentation is optional.
Here’s an example of what one looks like without the added macros:
class Something
# Exposing some instance variables to the outside world...
attr_reader :some_value
attr_reader :some_other_value
# Setting a dependency that uses its inert form by default, but can be later updated to the operational form...
attr_writer :some_dependency
def some_dependency
@some_dependency ||= SomeDependency::Substitute.build
end
# Setting instance variables when you call new...
def initialize(some_value, some_other_value)
@some_value = some_value
@some_other_value = some_other_value
end
# Call new, then turn set your substitute dependency to it's operational form
def self.build(some_object)
new(some_object.some_value, some_object.some_other_value).tap do |instance|
instance.some_dependency = SomeDependency.()
end
end
# Shorthand for building w/ operational dependency and actuating immediately
def self.call(some_object)
instance = build(some_object)
instance.()
end
def call
# Execute the object's raison d'etre, making use
# of the object's attributes and dependencies
end
end
class SomeDependency
# Actuating SomeDependency causes some nasty side effects! Bad for testing...
def call
do_some_destructive_side_effect
end
# Luckily, here's our Substitute that mimics, but does not perform, any destructive side effects
module Substitute
def self.build
SomeDependency.new
end
class SomeDependency < ::SomeDependency
def call
pretend_to_do_some_destructive_side_effect
end
end
end
end
As you can see, Something
starts off by using SomeDependency::Substitute
until the build method is called, at which point is starts SomeDependency
proper. I’ve found this paradigm pretty useful (heh) for standardize the way I write code as well as controlling side effects during tests.
Does this pattern translate to Elixir code or FP code in general? Are there any alternatives to this pattern to manage side effects during testing?