@@ -27,6 +27,9 @@ When you include `Mortymer::DependenciesDsl`, you get access to the `inject` dir
27271 . Create an instance variable ` @user_repository `
28282 . Automatically initialize it with an instance of ` UserRepository `
2929
30+ The name of the injected variable is the snake_case version of the class we are injecting by default. So
31+ for example ` Repositories::UserRepository ` will result in an instance variable called ` @user_repository `
32+
3033### Custom Variable Names
3134
3235You can customize the instance variable name using the ` as ` option:
6669
6770## Advanced Usage
6871
69- ### Dependency Scopes
70-
71- Mortymer supports different scopes for dependencies:
72-
73- #### Singleton Scope (Default)
74-
75- By default, dependencies are singleton-scoped, meaning the same instance is shared across the application:
72+ Just with the basic information, Mortymer already provides a really powerful and
73+ expressive system to declare dependencies, but as it is, is just a Fancy Factory or Initializer
74+ for your classes. Where Dependency Injection really shines, is when you are able to control some
75+ other aspects of the dependency injection cycle. For example, Mortymer does not interfere with your
76+ usual object initialization.
7677
7778``` ruby
78- class UserService
79+ class MyService
7980 include Mortymer ::DependenciesDsl
81+ inject Mailer
8082
81- inject UserRepository # Singleton by default
82- end
83+ def initialize (user )
84+ # At this point we can be sure that the @mailer instance
85+ # is already defined
86+ @mailer .send_email(" Hello user" , user.email)
87+ @user = user
88+ end
8389```
8490
85- #### Request Scope
91+ It might not be clear what is happening here. What is really doing Mortymer is
92+ wrapping the ` #initialize ` method with the dependency resolving algorithm and if
93+ any ` kwarg ` passed to the ` #initialize ` method matches the name of the injected
94+ dependency, then Mortymer will use that as the dependency instead.
8695
87- For dependencies that should be unique per request :
96+ This means that you can call your previous service as :
8897
8998``` ruby
90- class UserService
91- include Mortymer ::DependenciesDsl
92-
93- inject UserRepository , scope: :request
94- end
99+ MyService .new (User .first) # will use the Mailer class to instantiate the @mailer variable
100+ MyService .new (User .first, mailer: instance_double(Mailer )) # will override @mailer with an instance_double
95101```
96102
97- #### Transient Scope
103+ ### Dependency Scopes
98104
99- For dependencies that should be newly instantiated every time :
105+ Mortymer supports different scopes for dependencies :
100106
101- ``` ruby
102- class UserService
103- include Mortymer :: DependenciesDsl
107+ - By default, dependencies are transient, meaning you will get a new fresh instance each time you ask for one
108+ - Dependencies might be singleton, meaning each injection will provide the same instance
109+ - Dependencies might be lazy, basically, a block that might compute a value for your dependency
104110
105- inject UserRepository , scope: :transient
106- end
107- ```
111+ To change a dependency's scope, you need to register it in the Mortymer DI Container
108112
109- ### Factory Registration
113+ #### Singleton Scope
110114
111- You can register custom factory methods for your dependencies:
115+ ``` ruby
116+ Mortymer .container.register_constant(Mailer , SingletonMailer .new )
117+ ```
118+
119+ #### Lazy Scope
112120
113121``` ruby
114- Mortymer .configure do |config |
115- config.register_factory(UserRepository ) do
116- UserRepository .new (connection: DatabaseConnection .current)
122+ Mortymer .container.register_constant(MAILER_ADMIN_ADDRESS ) do
123+ if some_condition?
124+ " admin+#{ Time .current} @example.com"
125+ else
126+ " admin-ex@example.com"
117127 end
118128end
119129```
@@ -129,6 +139,12 @@ module UserRepositoryInterface
129139 end
130140end
131141
142+ class UserService
143+ include Mortymer ::DependenciesDsl
144+
145+ inject UserRepositoryInterface , as: :repo
146+ end
147+
132148class PostgresUserRepository
133149 include UserRepositoryInterface
134150 # implementation
@@ -140,259 +156,9 @@ class MongoUserRepository
140156end
141157
142158# Register implementation
143- Mortymer .configure do |config |
144- config.register_implementation(UserRepositoryInterface , PostgresUserRepository )
145- end
146-
147- class UserService
148- include Mortymer ::DependenciesDsl
149-
150- inject UserRepositoryInterface
151- end
152- ```
153-
154- ## Testing
155-
156- ### Mocking Dependencies
157-
158- Mortymer makes it easy to mock dependencies in tests:
159-
160- ``` ruby
161- RSpec .describe UserService do
162- let(:repository_mock ) { instance_double(UserRepository ) }
163- let(:mailer_mock ) { instance_double(UserMailer ) }
164-
165- before do
166- Mortymer .stub_dependency(UserRepository , repository_mock)
167- Mortymer .stub_dependency(UserMailer , mailer_mock)
168- end
169-
170- it " creates a user" do
171- service = UserService .new
172- expect(repository_mock).to receive(:create )
173- expect(mailer_mock).to receive(:send_welcome_email )
174-
175- service.create_user(name: " John" )
176- end
177- end
178- ```
179-
180- ### Test-Specific Implementations
181-
182- You can also register test-specific implementations:
183-
184- ``` ruby
185- class TestUserRepository
186- include UserRepositoryInterface
187-
188- def initialize
189- @users = {}
190- end
191-
192- def find (id )
193- @users [id]
194- end
195- end
196-
197- RSpec .describe UserService do
198- before do
199- Mortymer .register_implementation(UserRepositoryInterface , TestUserRepository )
200- end
201- end
202- ```
203-
204- ## Best Practices
205-
206- ### 1. Keep Dependencies Explicit
207-
208- Always use explicit constant references rather than strings for dependencies:
209-
210- ``` ruby
211- # Good
212- inject UserRepository
213-
214- # Avoid
215- inject " user_repository"
216- ```
217-
218- ### 2. Use Meaningful Names
219-
220- Choose descriptive names for your dependencies:
221-
222- ``` ruby
223- # Good
224- inject UserRepository , as: :active_users_repository
225-
226- # Less Clear
227- inject UserRepository , as: :repo
228- ```
229-
230- ### 3. Group Related Dependencies
231-
232- Keep related dependencies together and organize them logically:
233-
234- ``` ruby
235- class UserService
236- include Mortymer ::DependenciesDsl
237-
238- # Authentication dependencies
239- inject AuthenticationService
240- inject TokenGenerator
241-
242- # User management dependencies
243- inject UserRepository
244- inject UserMailer
159+ Mortymer .container.register_constant(UserRepositoryInterface , PostgresUserRepository .new )
245160
246- # Logging/Monitoring
247- inject Logger
248- inject MetricsCollector
249- end
250- ```
251-
252- ### 4. Interface Segregation
253-
254- Create focused interfaces for your dependencies:
255-
256- ``` ruby
257- module UserReader
258- def find (id ); end
259- def list ; end
260- end
261-
262- module UserWriter
263- def create (attributes ); end
264- def update (id , attributes ); end
265- end
266-
267- class UserRepository
268- include UserReader
269- include UserWriter
270- end
271-
272- class UserService
273- include Mortymer ::DependenciesDsl
274-
275- inject UserReader # Only inject what you need
276- end
277- ` ` `
278-
279- ## Common Patterns
280-
281- ### Service Objects
282-
283- ` ` ` ruby
284- class CreateUser
285- include Mortymer ::DependenciesDsl
286-
287- inject UserRepository
288- inject UserMailer
289- inject UserValidator
290-
291- def call (params )
292- @user_validator .validate!(params)
293- user = @user_repository .create(params)
294- @user_mailer .send_welcome_email(user)
295- user
296- end
297- end
298- ```
299-
300- ### Decorators
301-
302- ``` ruby
303- class LoggedUserRepository
304- include Mortymer ::DependenciesDsl
305-
306- inject UserRepository
307- inject Logger
308-
309- def find (id )
310- @logger .info(" Finding user: #{ id } " )
311- result = @user_repository .find(id)
312- @logger .info(" Found user: #{ result&.id } " )
313- result
314- end
315- end
316- ```
317-
318- ## Configuration
319-
320- ### Global Configuration
321-
322- ``` ruby
323- Mortymer .configure do |config |
324- # Register default implementations
325- config.register_implementation(UserRepositoryInterface , PostgresUserRepository )
326-
327- # Register factories
328- config.register_factory(Logger ) do
329- Logger .new ($stdout ).tap do |logger |
330- logger.level = Rails .env.production? ? :info : :debug
331- end
332- end
333-
334- # Configure scopes
335- config.set_scope(UserSession , :request )
336- end
337- ```
338-
339- ### Environment-Specific Configuration
340-
341- ``` ruby
342- # config/initializers/mortymer.rb
343- Mortymer .configure do |config |
344- if Rails .env.test?
345- config.register_implementation(UserRepositoryInterface , TestUserRepository )
346- elsif Rails .env.development?
347- config.register_implementation(UserRepositoryInterface , DevUserRepository )
348- else
349- config.register_implementation(UserRepositoryInterface , ProductionUserRepository )
350- end
351- end
161+ # Then you can instantiate
162+ UserService .new # will use the PostgresUserRepository implementation
163+ UserService .new (repo: MongoUserRepository .new ) # override the dependency to use a different one
352164```
353-
354- ## Troubleshooting
355-
356- ### Common Issues
357-
358- 1 . ** Circular Dependencies**
359-
360- ``` ruby
361- # This will raise a CircularDependencyError
362- class UserService
363- include Mortymer ::DependenciesDsl
364- inject AccountService
365- end
366-
367- class AccountService
368- include Mortymer ::DependenciesDsl
369- inject UserService # Circular dependency!
370- end
371- ```
372-
373- Solution: Refactor to remove the circular dependency or use method injection.
374-
375- 2 . ** Missing Dependencies**
376-
377- ``` ruby
378- # This will raise a DependencyNotFoundError
379- class UserService
380- include Mortymer ::DependenciesDsl
381- inject NonExistentService
382- end
383- ```
384-
385- Solution: Ensure all dependencies are properly registered or the constants exist.
386-
387- 3 . ** Scope Conflicts**
388-
389- ``` ruby
390- # This might cause issues
391- class UserService
392- include Mortymer ::DependenciesDsl
393- inject SessionManager , scope: :request # Request-scoped
394- inject UserRepository # Singleton-scoped
395- end
396- ```
397-
398- Solution: Be careful mixing different scopes and ensure it makes sense for your use case.
0 commit comments