lazy_init 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,765 @@
1
+ # LazyInit
2
+
3
+ Thread-safe lazy initialization patterns for Ruby with automatic dependency resolution, memory management, and performance optimization.
4
+
5
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.6-red.svg)](https://www.ruby-lang.org/)
6
+ [![Gem Version](https://badge.fury.io/rb/lazy_init.svg)](https://badge.fury.io/rb/lazy_init)
7
+ [![Build Status](https://github.com/N3BCKN/lazy_init/workflows/CI/badge.svg)](https://github.com/N3BCKN/lazy_init/actions)
8
+
9
+ ## Table of Contents
10
+
11
+ - [Problem Statement](#problem-statement)
12
+ - [Installation](#installation)
13
+ - [Quick Start](#quick-start)
14
+ - [Core Features](#core-features)
15
+ - [API Reference](#api-reference)
16
+ - [Instance Attributes](#instance-attributes)
17
+ - [Class Variables](#class-variables)
18
+ - [Instance Methods](#instance-methods)
19
+ - [Configuration](#configuration)
20
+ - [Advanced Usage](#advanced-usage)
21
+ - [Dependency Resolution](#dependency-resolution)
22
+ - [Timeout Protection](#timeout-protection)
23
+ - [Memory Management](#memory-management)
24
+ - [Common Use Cases](#common-use-cases)
25
+ - [Error Handling](#error-handling)
26
+ - [Performance](#performance)
27
+ - [Thread Safety](#thread-safety)
28
+ - [Thread Safety Deep Dive](#thread-safety-deep-dive)
29
+ - [Compatibility](#compatibility)
30
+ - [Testing](#testing)
31
+ - [Migration Guide](#migration-guide)
32
+ - [When NOT to Use LazyInit](#when-not-to-use-lazyinit)
33
+ - [FAQ](#faq)
34
+ - [Contributing](#contributing)
35
+ - [License](#license)
36
+
37
+ ## Problem Statement
38
+
39
+ Ruby's common lazy initialization pattern using `||=` is **not thread-safe** and can cause race conditions in multi-threaded environments:
40
+
41
+ ```ruby
42
+ # ❌ Thread-unsafe (common pattern)
43
+ def expensive_calculation
44
+ @result ||= perform_heavy_computation # Race condition possible!
45
+ end
46
+
47
+ # ✅ Thread-safe (LazyInit solution)
48
+ lazy_attr_reader :expensive_calculation do
49
+ perform_heavy_computation
50
+ end
51
+ ```
52
+ LazyInit provides thread-safe lazy initialization with zero race conditions, automatic dependency resolution, and intelligent performance optimization.
53
+
54
+ ## Installation
55
+ Add this line to your application's Gemfile:
56
+ ```ruby
57
+ gem 'lazy_init'
58
+ ```
59
+ And then execute:
60
+ ```ruby
61
+ bundle install
62
+ ```
63
+ Or install it yourself as:
64
+ ```ruby
65
+ gem install lazy_init
66
+ ```
67
+ ## Requirements:
68
+
69
+ - Ruby 2.6 or higher
70
+ - No external dependencies
71
+
72
+ ## Quick Start
73
+ ### Basic Usage
74
+
75
+ ```ruby
76
+ class ApiClient
77
+ extend LazyInit
78
+
79
+ lazy_attr_reader :connection do
80
+ puts "Establishing connection..."
81
+ HTTPClient.new(api_url)
82
+ end
83
+ end
84
+
85
+ client = ApiClient.new
86
+ # No connection established yet
87
+
88
+ client.connection # "Establishing connection..." - computed once
89
+ client.connection # Returns cached result (thread-safe)
90
+ ```
91
+
92
+ ### With Dependencies
93
+ ```ruby
94
+ class WebService
95
+ extend LazyInit
96
+
97
+ lazy_attr_reader :config do
98
+ YAML.load_file('config.yml')
99
+ end
100
+
101
+ lazy_attr_reader :database, depends_on: [:config] do
102
+ Database.connect(config['database_url'])
103
+ end
104
+
105
+ lazy_attr_reader :api_client, depends_on: [:config, :database] do
106
+ ApiClient.new(
107
+ url: config['api_url'],
108
+ database: database
109
+ )
110
+ end
111
+ end
112
+
113
+ service = WebService.new
114
+ service.api_client # Automatically resolves: config → database → api_client
115
+ ```
116
+
117
+ ## Core Features
118
+ ### Thread Safety
119
+
120
+ - Eliminates all race conditions with optimized double-checked locking
121
+ - Circular dependency detection prevents infinite loops
122
+ - Thread-safe reset for testing and error recovery
123
+
124
+ ### Automatic Dependency Resolution
125
+
126
+ - Declarative dependencies with depends_on option
127
+ - Automatic resolution order computation
128
+ - Intelligent caching to avoid redundant work
129
+
130
+ ### Performance Optimization
131
+
132
+ - Three-tier implementation strategy based on complexity
133
+ - 5-6x overhead vs manual ||= (significantly faster than alternatives)
134
+ - Memory-efficient with automatic cleanup
135
+
136
+ ```ruby
137
+ # Simple inline (fastest)
138
+ lazy_attr_reader :simple_value do
139
+ "simple"
140
+ end
141
+
142
+ # Optimized dependency (medium)
143
+ lazy_attr_reader :dependent_value, depends_on: [:simple_value] do
144
+ "depends on #{simple_value}"
145
+ end
146
+
147
+ # Full LazyValue (complete features)
148
+ lazy_attr_reader :complex_value, timeout: 5, depends_on: [:multiple, :deps] do
149
+ "complex computation"
150
+ end
151
+ ```
152
+
153
+ ### Memory Management
154
+
155
+ - Automatic cache cleanup prevents memory leaks
156
+ - LRU eviction for method-local caching
157
+ - TTL support for time-based expiration
158
+
159
+ ## API Reference
160
+ ### Instance Attributes
161
+ ```ruby
162
+ lazy_attr_reader(name, **options, &block)
163
+ ```
164
+ Defines a thread-safe lazy-initialized attribute.
165
+ #### Parameters:
166
+
167
+ - name (Symbol/String): Attribute name
168
+ - timeout (Numeric, optional): Timeout in seconds for computation
169
+ - depends_on (Array<Symbol>/Symbol, optional): Dependencies to resolve first
170
+ - block (Proc): Computation block
171
+
172
+ #### Generated Methods:
173
+
174
+ - #{name}: Returns the computed value
175
+ - #{name}_computed?: Returns true if value has been computed
176
+ - reset_#{name}!: Resets to uncomputed state
177
+
178
+ #### Examples:
179
+ ```ruby
180
+ rubyclass ServiceManager
181
+ extend LazyInit
182
+
183
+ # Simple lazy attribute
184
+ lazy_attr_reader :expensive_service do
185
+ ExpensiveService.new
186
+ end
187
+
188
+ # With timeout protection
189
+ lazy_attr_reader :external_api, timeout: 10 do
190
+ ExternalAPI.connect
191
+ end
192
+
193
+ # With dependencies
194
+ lazy_attr_reader :configured_service, depends_on: [:config] do
195
+ ConfiguredService.new(config)
196
+ end
197
+ end
198
+
199
+ manager = ServiceManager.new
200
+ manager.expensive_service_computed? # => false
201
+ manager.expensive_service # Creates service
202
+ manager.expensive_service_computed? # => true
203
+ manager.reset_expensive_service! # Resets for re-computation
204
+ ```
205
+
206
+ ### Class Variables
207
+ ```ruby
208
+ lazy_class_variable(name, **options, &block)
209
+ ```
210
+ Defines a thread-safe lazy-initialized class variable shared across all instances.
211
+ #### Parameters:
212
+
213
+ - Same as lazy_attr_reader
214
+
215
+ #### Generated Methods:
216
+
217
+ - Class-level: ClassName.#{name}, ClassName.#{name}\_computed?, ClassName.reset\_#{name}!
218
+ - Instance-level: #{name}, #{name}\_computed?, reset\_#{name}! (delegates to class)
219
+
220
+ #### Example:
221
+ ```ruby
222
+ class DatabaseManager
223
+ extend LazyInit
224
+
225
+ lazy_class_variable :connection_pool do
226
+ ConnectionPool.new(size: 20, timeout: 30)
227
+ end
228
+ end
229
+
230
+ # Shared across all instances
231
+ db1 = DatabaseManager.new
232
+ db2 = DatabaseManager.new
233
+ db1.connection_pool.equal?(db2.connection_pool) # => true
234
+
235
+ # Class-level access
236
+ DatabaseManager.connection_pool # Direct access
237
+ DatabaseManager.reset_connection_pool! # Reset for all instances
238
+ ```
239
+
240
+ ### Instance Methods
241
+ #### Include LazyInit (instead of extending) to get instance-level utilities:
242
+ ```ruby
243
+ class DataProcessor
244
+ include LazyInit # Note: include, not extend
245
+ end
246
+ ```
247
+ **lazy(&block)**
248
+
249
+ Creates a standalone lazy value container.
250
+ ```ruby
251
+ def expensive_calculation
252
+ result = lazy { perform_heavy_computation }
253
+ result.value
254
+ end
255
+ ```
256
+
257
+
258
+ __lazy_once(**options, &block)__
259
+
260
+ Method-scoped lazy initialization with automatic cache key generation.
261
+ #### Parameters:
262
+
263
+ - max_entries (Integer): Maximum cache entries before LRU eviction
264
+ - ttl (Numeric): Time-to-live in seconds for cache entries
265
+
266
+ #### Example:
267
+ ``` ruby
268
+ class DataAnalyzer
269
+ include LazyInit
270
+
271
+ def analyze_data(dataset_id)
272
+ lazy_once(ttl: 5.minutes, max_entries: 100) do
273
+ expensive_analysis(dataset_id)
274
+ end
275
+ end
276
+ end
277
+ ```
278
+
279
+ **clear_lazy_once_values!**
280
+
281
+ Clears all cached lazy_once values for the instance.
282
+
283
+ **lazy_once_statistics**
284
+
285
+ Returns cache statistics for debugging and monitoring.
286
+
287
+ ```ruby
288
+ stats = processor.lazy_once_statistics
289
+ # => {
290
+ # total_entries: 25,
291
+ # computed_entries: 25,
292
+ # total_accesses: 150,
293
+ # average_accesses: 6.0,
294
+ # oldest_entry: 2025-07-01 10:00:00,
295
+ # newest_entry: 2024-07-01 10:30:00
296
+ # }
297
+ ```
298
+
299
+ ### Configuration
300
+ Configure global behavior:
301
+ ```ruby
302
+ LazyInit.configure do |config|
303
+ config.default_timeout = 30
304
+ config.max_lazy_once_entries = 5000
305
+ config.lazy_once_ttl = 1.hour
306
+ end
307
+ ```
308
+
309
+ #### Configuration Options:
310
+
311
+ - **default_timeout**: Default timeout for all lazy attributes (default: nil)
312
+ - **max_lazy_once_entries**: Maximum entries in lazy_once cache (default: 1000)
313
+ - **lazy_once_ttl**: Default TTL for lazy_once entries (default: nil)
314
+
315
+ ## Advanced Usage
316
+ ### Dependency Resolution
317
+ #### LazyInit automatically resolves dependencies in the correct order:
318
+ ```ruby
319
+ rubyclass ComplexService
320
+ extend LazyInit
321
+
322
+ lazy_attr_reader :config do
323
+ load_configuration
324
+ end
325
+
326
+ lazy_attr_reader :database, depends_on: [:config] do
327
+ Database.connect(config.database_url)
328
+ end
329
+
330
+ lazy_attr_reader :cache, depends_on: [:config] do
331
+ Cache.new(config.cache_settings)
332
+ end
333
+
334
+ lazy_attr_reader :processor, depends_on: [:database, :cache] do
335
+ DataProcessor.new(database: database, cache: cache)
336
+ end
337
+ end
338
+
339
+ service = ComplexService.new
340
+ service.processor # Resolves: config → database & cache → processor
341
+ ```
342
+
343
+ #### Circular Dependency Detection:
344
+ ```ruby
345
+ lazy_attr_reader :service_a, depends_on: [:service_b] do
346
+ "A"
347
+ end
348
+
349
+ lazy_attr_reader :service_b, depends_on: [:service_a] do
350
+ "B"
351
+ end
352
+
353
+ service.service_a # Raises: LazyInit::DependencyError
354
+ ```
355
+
356
+ ### Timeout Protection
357
+ #### Protect against hanging computations:
358
+
359
+ ```ruby
360
+ class ExternalService
361
+ extend LazyInit
362
+
363
+ lazy_attr_reader :slow_api, timeout: 5 do
364
+ HTTPClient.get('http://very-slow-api.com/data')
365
+ end
366
+ end
367
+
368
+ service = ExternalService.new
369
+ begin
370
+ service.slow_api
371
+ rescue LazyInit::TimeoutError => e
372
+ puts "API call timed out: #{e.message}"
373
+ end
374
+ ```
375
+
376
+ ### Memory Management
377
+ #### LazyInit includes sophisticated memory management:
378
+ ```ruby
379
+ class MemoryAwareService
380
+ include LazyInit
381
+
382
+ def process_data(data_id)
383
+ # Automatic cleanup when cache grows too large
384
+ lazy_once(max_entries: 50, ttl: 10.minutes) do
385
+ expensive_data_processing(data_id)
386
+ end
387
+ end
388
+
389
+ def cleanup!
390
+ clear_lazy_once_values! # Manual cleanup
391
+ end
392
+ end
393
+ ```
394
+
395
+ ## Common Use Cases
396
+ #### Rails Application Services
397
+ ```ruby
398
+ class UserService
399
+ extend LazyInit
400
+
401
+ lazy_attr_reader :redis_client do
402
+ Redis.new(url: Rails.application.credentials.redis_url)
403
+ end
404
+
405
+ lazy_class_variable :connection_pool do
406
+ ConnectionPool.new(size: Rails.env.production? ? 20 : 5)
407
+ end
408
+
409
+ lazy_attr_reader :email_service, depends_on: [:redis_client] do
410
+ EmailService.new(cache: redis_client)
411
+ end
412
+ end
413
+ ```
414
+
415
+ #### Background Jobs
416
+ ```ruby
417
+ class ImageProcessorJob
418
+ extend LazyInit
419
+
420
+ lazy_attr_reader :image_processor do
421
+ ImageProcessor.new(memory_limit: '512MB')
422
+ end
423
+
424
+ lazy_attr_reader :cloud_storage, timeout: 10 do
425
+ CloudStorage.new(credentials: ENV['CLOUD_CREDENTIALS'])
426
+ end
427
+
428
+ def perform(image_id)
429
+ processed = image_processor.process(image_id)
430
+ cloud_storage.upload(processed)
431
+ end
432
+ end
433
+ ```
434
+ #### Microservices
435
+ ```ruby
436
+ class PaymentService
437
+ extend LazyInit
438
+
439
+ lazy_attr_reader :config do
440
+ ServiceConfig.load('payment_service')
441
+ end
442
+
443
+ lazy_attr_reader :database, depends_on: [:config] do
444
+ Database.connect(config.database_url)
445
+ end
446
+
447
+ lazy_attr_reader :payment_gateway, depends_on: [:config], timeout: 15 do
448
+ PaymentGateway.new(
449
+ api_key: config.payment_api_key,
450
+ environment: config.environment
451
+ )
452
+ end
453
+ end
454
+ ```
455
+ ### Rails Concerns
456
+ ```ruby
457
+ module Cacheable
458
+ extend ActiveSupport::Concern
459
+
460
+ included do
461
+ extend LazyInit
462
+
463
+ lazy_attr_reader :cache_client do
464
+ Rails.cache
465
+ end
466
+ end
467
+
468
+ def cached_method(key)
469
+ lazy_once(ttl: 1.hour) do
470
+ expensive_computation(key)
471
+ end
472
+ end
473
+ end
474
+ ```
475
+ ## Error Handling
476
+ LazyInit provides predictable error behavior:
477
+ ```ruby
478
+ class ServiceWithErrors
479
+ extend LazyInit
480
+
481
+ lazy_attr_reader :failing_service do
482
+ raise StandardError, "Service unavailable"
483
+ end
484
+
485
+ lazy_attr_reader :timeout_service, timeout: 1 do
486
+ sleep(5) # Will timeout
487
+ "Success"
488
+ end
489
+ end
490
+
491
+ service = ServiceWithErrors.new
492
+
493
+ # Exceptions are cached and re-raised consistently
494
+ begin
495
+ service.failing_service
496
+ rescue StandardError => e
497
+ puts "First call: #{e.message}"
498
+ end
499
+
500
+ begin
501
+ service.failing_service # Same exception re-raised (cached)
502
+ rescue StandardError => e
503
+ puts "Second call: #{e.message}" # Same exception instance
504
+ end
505
+
506
+ # Check error state
507
+ service.failing_service_computed? # => false (failed computation)
508
+
509
+ # Reset allows retry
510
+ service.reset_failing_service!
511
+ service.failing_service # => Attempts computation again
512
+
513
+ # Timeout errors
514
+ begin
515
+ service.timeout_service
516
+ rescue LazyInit::TimeoutError => e
517
+ puts "Timeout: #{e.message}"
518
+ # Subsequent calls raise the same timeout error
519
+ end
520
+ ```
521
+ #### Exception Types
522
+ ```ruby
523
+ LazyInit::Error # Base error class
524
+ LazyInit::InvalidAttributeNameError # Invalid attribute names
525
+ LazyInit::TimeoutError # Timeout exceeded
526
+ LazyInit::DependencyError # Circular dependencies
527
+ Performance
528
+ LazyInit is optimized for production use:
529
+ ```
530
+ ## Performance
531
+
532
+ Realistic benchmark results (x86_64-darwin19, Ruby 3.0.2):
533
+
534
+ - Initial computation: ~identical (LazyInit setup overhead negligible)
535
+ - Cached access: 3.5x slower than manual ||=
536
+ -100,000 calls: Manual 13ms, LazyInit 45ms
537
+ - In practice: For expensive operations (5-50ms), the 0.0004ms per call overhead is negligible.
538
+ - Trade-off: 3.5x cached access cost for 100% thread safety
539
+
540
+ [Full details can be found here](https://github.com/N3BCKN/lazy_init/blob/main/benchmarks/benchmark_performance.rb)
541
+
542
+ ### Optimization Strategies
543
+ LazyInit automatically selects the best implementation:
544
+
545
+ - Simple inline (no dependencies, no timeout): Maximum performance
546
+ - Optimized dependency (single dependency): Balanced performance
547
+ - Full LazyValue (complex scenarios): Full feature set
548
+
549
+
550
+ ## Thread Safety
551
+ LazyInit provides comprehensive thread safety guarantees:
552
+ ### Thread Safety Features
553
+
554
+ - Double-checked locking for optimal performance
555
+ - Per-attribute mutexes to avoid global locks
556
+ - Atomic state transitions to prevent race conditions
557
+ - Exception safety with proper cleanup
558
+
559
+ #### Example: Concurrent Access
560
+ ```ruby
561
+ class ThreadSafeService
562
+ extend LazyInit
563
+
564
+ lazy_attr_reader :shared_resource do
565
+ puts "Creating resource in thread #{Thread.current.object_id}"
566
+ ExpensiveResource.new
567
+ end
568
+ end
569
+
570
+ service = ThreadSafeService.new
571
+
572
+ # Multiple threads accessing the same attribute
573
+ threads = 10.times.map do |i|
574
+ Thread.new do
575
+ puts "Thread #{i}: #{service.shared_resource.object_id}"
576
+ end
577
+ end
578
+
579
+ threads.each(&:join)
580
+ # Output: All threads get the same object_id (single computation)
581
+ ```
582
+ ### Thread Safety Deep Dive
583
+ LazyInit uses several techniques to ensure thread safety:
584
+
585
+ - Double-checked locking: Fast path avoids synchronization after computation
586
+ - Per-attribute mutexes: No global locks that could cause bottlenecks
587
+ - Atomic state transitions: Prevents race conditions during computation
588
+ - Exception safety: Proper cleanup even when computations fail
589
+
590
+ [Full report with benchmark here](https://github.com/N3BCKN/lazy_init/blob/main/benchmarks/benchmark_threads.rb)
591
+
592
+ #### Thread Safety benchmark
593
+ - 200 concurrent requests: 0 race conditions
594
+ - 6,000+ operations/second sustained throughput
595
+ - Complex dependency chains: 100% reliable
596
+ - Zero-downtime resets: 100% success rate
597
+ - Tested on Ruby 3.0.2, macOS (Intel)
598
+
599
+ ## Compatibility
600
+
601
+ - Ruby: 2.6, 2.7, 3.0, 3.1, 3.2, 3.3+
602
+ - Rails: 5.2+ (optional, no Rails dependency required)
603
+ - Thread-safe: Yes, across all Ruby implementations (MRI, JRuby, TruffleRuby)
604
+ - Ractor-safe: Planned for future versions
605
+ - Versioning: LazyInit follows semantic versioning
606
+
607
+ ## Testing
608
+
609
+ #### RSpec Integration
610
+ ```ruby
611
+ RSpec.describe UserService do
612
+ let(:service) { UserService.new }
613
+
614
+ describe '#expensive_calculation' do
615
+ it 'computes value lazily' do
616
+ expect(service.expensive_calculation_computed?).to be false
617
+
618
+ result = service.expensive_calculation
619
+ expect(result).to be_a(String)
620
+ expect(service.expensive_calculation_computed?).to be true
621
+ end
622
+
623
+ it 'returns same value on multiple calls' do
624
+ first_call = service.expensive_calculation
625
+ second_call = service.expensive_calculation
626
+
627
+ expect(first_call).to be(second_call) # Same object
628
+ end
629
+
630
+ it 'can be reset for fresh computation' do
631
+ old_value = service.expensive_calculation
632
+ service.reset_expensive_calculation!
633
+ new_value = service.expensive_calculation
634
+
635
+ expect(new_value).not_to be(old_value)
636
+ end
637
+ end
638
+ end
639
+ ```
640
+ #### Test Helpers
641
+ ```ruby
642
+ # Custom helpers for testing
643
+ module LazyInitTestHelpers
644
+ def reset_all_lazy_attributes(object)
645
+ object.class.lazy_initializers.each_key do |attr_name|
646
+ object.send("reset_#{attr_name}!")
647
+ end
648
+ end
649
+ end
650
+
651
+ RSpec.configure do |config|
652
+ config.include LazyInitTestHelpers
653
+ end
654
+ ```
655
+
656
+ #### Rails Testing Considerations
657
+ ```ruby
658
+ # In Rails, be careful with class variables during code reloading
659
+ RSpec.configure do |config|
660
+ config.before(:each) do
661
+ # Reset class variables in development/test
662
+ MyService.reset_connection_pool! if defined?(MyService)
663
+ end
664
+ end
665
+ ```
666
+ ## Migration Guide
667
+ #### From Manual ||= Pattern
668
+ Before:
669
+ ```ruby
670
+ class LegacyService
671
+ def config
672
+ @config ||= YAML.load_file('config.yml')
673
+ end
674
+
675
+ def database
676
+ @database ||= Database.connect(config['url'])
677
+ end
678
+ end
679
+ ```
680
+ After:
681
+ ```ruby
682
+ class ModernService
683
+ extend LazyInit
684
+
685
+ lazy_attr_reader :config do
686
+ YAML.load_file('config.yml')
687
+ end
688
+
689
+ lazy_attr_reader :database, depends_on: [:config] do
690
+ Database.connect(config['url'])
691
+ end
692
+ end
693
+ ```
694
+
695
+ ### Migration Benefits
696
+
697
+ - Thread safety: Automatic protection against race conditions
698
+ - Dependency management: Explicit dependency declaration
699
+ - Error handling: Built-in timeout and exception management
700
+ - Testing: Easier state management in tests
701
+
702
+ ### Gradual Migration Strategy
703
+
704
+ - Start with new lazy attributes using LazyInit
705
+ - Identify critical thread-unsafe ||= patterns
706
+ - Convert high-risk areas first
707
+ - Add dependency declarations where beneficial
708
+ - Remove manual patterns over time
709
+
710
+ ## When NOT to Use LazyInit
711
+ Consider alternatives in these scenarios:
712
+
713
+ - Simple value caching where manual ||= suffices and thread safety isn't needed
714
+ - Performance-critical hot paths in tight loops where every microsecond counts
715
+ - Single-threaded applications with basic caching needs
716
+ - Primitive value caching (strings, numbers, booleans) where overhead outweighs benefits
717
+ - Very simple Rails applications without complex service layers
718
+
719
+ ## FAQ
720
+ Q: How does performance compare to other approaches?
721
+
722
+ A: Compared to manual mutex-based solutions, LazyInit provides better developer experience with competitive performance. See benchmarks for detailed comparison with manual ||= patterns.
723
+
724
+ Q: Can I use this in Rails initializers?
725
+
726
+ A: Yes, but be careful with class variables in development mode due to code reloading.
727
+
728
+ Q: What happens during Rails code reloading?
729
+
730
+ A: Instance attributes are automatically reset. Class variables may need manual reset in development.
731
+
732
+ Q: Is there any memory overhead?
733
+
734
+ A: Minimal - about 1 mutex + 3 instance variables per lazy attribute.
735
+
736
+ Q: Can I use lazy_attr_reader with private methods?
737
+
738
+ A: Yes, the generated methods respect the same visibility as where they're defined.
739
+
740
+ Q: How do I debug dependency resolution issues?
741
+
742
+ A: Use YourClass.lazy_initializers to inspect dependency configuration and check for circular dependencies.
743
+
744
+ Q: Does this work with inheritance?
745
+
746
+ A: Yes, lazy attributes are inherited and can be overridden in subclasses.
747
+ ## Contributing
748
+
749
+ 1. Fork the repository
750
+ 2. Create your feature branch (git checkout -b my-new-feature)
751
+ 3. Write tests for your changes
752
+ 4. Ensure all tests pass (bundle exec rspec)
753
+ 5. Commit your changes (git commit -am 'Add some feature')
754
+ 6. Push to the branch (git push origin my-new-feature)
755
+ 7. Create a Pull Request
756
+
757
+ ## Development Setup
758
+ ```bash
759
+ git clone https://github.com/N3BCKN/lazy_init.git
760
+ cd lazy_init
761
+ bundle install
762
+ bundle exec rspec # Run tests
763
+ ```
764
+ ## License
765
+ The gem is available as open source under the terms of the MIT License.