Inoculate
Inoculate is a small, thread-safe dependency injection library configured entirely with Ruby. It provides several life-cycles and provides dependency access through private accessors.
What's in the box?
✅ Simple usage documentation written to get started fast. Check it out!
📚 YARD generated API documentation for the library. Check it out!
🤖 RBS types for your type checking wants. Check it out!
💎 Tests against many Ruby versions. Check it out!
🔒 MFA protection on all gem owners. Check it out!
- Quick Start
- Usage
- Dependency Life Cycles
- Transient
- Instance
- Singleton
- Thread Singleton
- Renaming the Declaration API
- Hide Your Dependency on Inoculate
- Dependency Life Cycles
- Installation
- Supported Ruby Versions
Quick Start
Create an initialization file for your dependencies and start registering them.
require "inoculate"
Inoculate.initialize do |config|
config.transient(:counter) { Counter.new }
end
To take advantage of dependency injection in tests, initialize based on the run-time environment.
# config/environments/test.rb
require "inoculate"
Inoculate.initialize do |config|
config.transient(:counter) { instance_double(Counter) }
end
Finally, declare your dependencies (which are injected as included modules).
require "inoculate"
class HistogramGraph
include Inoculate::Porter
inoculate_with :counter
def to_s
counter.to_s
end
end
Usage
Dependency Life Cycles
Transient
Transient dependencies are constructed for each call of the dependency method.
class Counter
attr_reader :count
def initialize
@count = 0
end
def inc
@count += 1
end
end
Inoculate.initialize do |config|
config.transient(:counter) { Counter.new }
end
class Example
include Inoculate::Porter
inoculate_with :counter
def to_s
counter.inc
"Count is: #{counter.count}"
end
end
a = Example.new
puts a, a, a
This results in:
Count is: 0
Count is: 0
Count is: 0
=> nil
Instance
Instance dependencies are constructed once for each instance of a dependent class.
class Counter
attr_reader :count
def initialize
@count = 0
end
def inc
@count += 1
end
end
Inoculate.initialize do |config|
config.instance(:counter) { Counter.new }
end
class Example
include Inoculate::Porter
inoculate_with :counter
def initialize(name)
@name = name
end
def to_s
counter.inc
"[#{@name}] Count is: #{counter.count}"
end
end
a = Example.new("a")
b = Example.new("b")
puts a, a, b
This results in:
[a] Count is: 1
[a] Count is: 2
[b] Count is: 1
=> nil
Singleton
Singleton dependencies are constructed once.
class Counter
attr_reader :count
def initialize
@count = 0
end
def inc
@count += 1
end
end
Inoculate.initialize do |config|
config.singleton(:counter) { Counter.new }
end
class Example
include Inoculate::Porter
inoculate_with :counter
def initialize(name)
@name = name
end
def to_s
counter.inc
"[#{@name}] Count is: #{counter.count}"
end
end
a = Example.new("a")
b = Example.new("b")
puts a, a, b
This results in:
[a] Count is: 1
[a] Count is: 2
[b] Count is: 3
=> nil
Thread Singleton
Thread Singleton dependencies are constructed once for any thread or fiber.
class Counter
attr_reader :count
def initialize
@count = 0
end
def inc
@count += 1
end
end
Inoculate.initialize do |config|
config.thread_singleton(:counter) { Counter.new }
end
class Example
include Inoculate::Porter
inoculate_with :counter
def initialize(name)
@name = "Example: #{name}"
end
def to_s
counter.inc
"[#{@name}] Count is: #{counter.count}"
end
end
class AnotherExample
include Inoculate::Porter
inoculate_with :counter
def initialize(name)
@name = "AnotherExample: #{name}"
end
def to_s
5.times { counter.inc }
"[#{@name}] Count is: #{counter.count}"
end
end
threads = %w[a b].map do |tag|
Thread.new(tag) do |t|
e = Example.new(t)
a = AnotherExample.new(t)
puts e, e, a, a
end
end
threads.each(&:join)
This results in:
[Example: a] Count is: 1
[Example: b] Count is: 1
[Example: b] Count is: 2
[Example: a] Count is: 2
[AnotherExample: b] Count is: 7
[AnotherExample: a] Count is: 7
[AnotherExample: b] Count is: 12
[AnotherExample: a] Count is: 12
=> [#<Thread:0x000000010d703c68 (irb):177 dead>, #<Thread:0x000000010d703b50 (irb):177 dead>]
Renaming the Declaration API
The inoculate_with
API is named to avoid immediate collisions with other modules
and code you may have. You can use it as-is, or rename it to something you see fit
for your project.
require "inoculate"
class HistogramGraph
include Inoculate::Porter[:inject]
inject :counter
end
Hide Your Dependency on Inoculate
Writing Inocuate::Porter
everywhere in your code is probably going to get old fast,
feel free to hide it behind a base class or common included module.
# Make it available to all Rails controllers.
class ApplicationController < ActionController::Base
include Inoculate::Porter[:dependencies]
end
Installation
Inoculate is a pure ruby library and does not rely on any compiled dependencies.
Install it by adding it to your gemfile and then running bundle install
gem "inoculate"
Or manually install it with gem
$ gem install inoculate
Supported Ruby Versions
Inoculate is tested against the following Ruby versions:
- 3.0
- 3.1
- 3.2
- 3.3.0-preview1
Development
After checking out the repo:
- run
bin/setup
to install dependencies. - run
rake spec
to run the tests. - run
rake spec:all
to run the tests across supported Ruby versions using Docker. - run
rake standard
to see lint errors.
You can also run bin/console
for an interactive prompt that will allow you to experiment.
Local Installation
Run bundle exec rake install
or bundle exec rake install:local
.
Releasing
- Update the version number in
lib/inoculate/version.rb
. - Run
bundle exec rake yard
to generate the latest documentation. - Run
bundle exec rake release
to create a git tag for the version, push git commits and the created tag, and push the.gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/tinychameleon/inoculate.
License
The gem is available as open source under the terms of the MIT License.