Static dependency injection at its best.
Usage example:
class CreateUser
include as_method ValidateUserEntity, name: :validate!
include as_method Generators::GeneratePassword # -> generate_password()
include as_method SaveEntity, name: :save
def call(name, email)
@name = name
@email = email
user = User.new(attributes)
validate!(user)
save(user)
end
def self.call(...) = new.call(...)
private
def attributes
{
name: @name,
email: @email,
password: generate_password(length: 30),
}
end
end
This approach allows you to:
- keep bringing reusable methods in the old-fashioned way via
include
orextend
, just like our ancestors did; - make Service Objects (SOs) look and feel just like regular methods;
- have all used SOs listed as explicit dependencies in-place;
Click on the image below:
Tested on Ruby v2.4 .. v3.x, but it is expected to work on all 2.x versions.
Add to your Gemfile
gem "as_method"
To make as_method
class method available in all classes and modules, add this line to your application loader:
require "as_method/setup"
or enable it in place:
class MyClass
extend AsMethod::Allow
...
end
If you require dependency injection during object construction, you might consider using dry-auto_inject.
However, be aware of the following points:
- Dependencies are somewhat implicit as they are defined in a separate file.
- Object constructors get modified, as explained in detail here."
- Resulting methods don't invoke 'call' on objects, they return callables instead.
1000 iterations of a Test Service Object call. It does nothing but calls two other service objects, they call two other service objects each, and so on. (Depth 11 levels, 2049 unique classes, 4095 calls).
user system total real memory
pure Ruby 3.689327 0.047354 3.736681 ( 3.744128) 27440
as_method 6.219795 0.038531 6.258326 ( 6.298066) 37652
dry-auto_inject 22.121456 0.119032 22.240488 (22.467116) 55408
Ruby version: 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-darwin22]
- extra spec for circular dependencies (SO1 includes SO2, SO2 includes SO1, directly and via modules)
- extra spec for Base Service Object class that includes another SO
- spec for AsMethod::Allow (extending a base class and a module)