async-enumerable 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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.standard.yml +5 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +416 -0
  8. data/Rakefile +127 -0
  9. data/benchmark/async_all.yaml +38 -0
  10. data/benchmark/async_any.yaml +39 -0
  11. data/benchmark/async_each.yaml +51 -0
  12. data/benchmark/async_find.yaml +37 -0
  13. data/benchmark/async_map.yaml +50 -0
  14. data/benchmark/async_select.yaml +31 -0
  15. data/benchmark/early_termination/any_early.yaml +17 -0
  16. data/benchmark/early_termination/any_late.yaml +17 -0
  17. data/benchmark/early_termination/find_middle.yaml +17 -0
  18. data/benchmark/size_comparison/map_10.yaml +17 -0
  19. data/benchmark/size_comparison/map_100.yaml +17 -0
  20. data/benchmark/size_comparison/map_1000.yaml +20 -0
  21. data/benchmark/size_comparison/map_10000.yaml +23 -0
  22. data/docs/reference/README.md +43 -0
  23. data/docs/reference/concurrency_bounder.md +234 -0
  24. data/docs/reference/enumerable.md +258 -0
  25. data/docs/reference/enumerator.md +221 -0
  26. data/docs/reference/methods/converters.md +97 -0
  27. data/docs/reference/methods/predicates.md +254 -0
  28. data/docs/reference/methods/transformers.md +104 -0
  29. data/lib/async/enumerable/comparable.rb +26 -0
  30. data/lib/async/enumerable/concurrency_bounder.rb +37 -0
  31. data/lib/async/enumerable/configurable.rb +140 -0
  32. data/lib/async/enumerable/methods/aggregators.rb +40 -0
  33. data/lib/async/enumerable/methods/converters.rb +21 -0
  34. data/lib/async/enumerable/methods/each.rb +39 -0
  35. data/lib/async/enumerable/methods/iterators.rb +27 -0
  36. data/lib/async/enumerable/methods/predicates/all.rb +47 -0
  37. data/lib/async/enumerable/methods/predicates/any.rb +47 -0
  38. data/lib/async/enumerable/methods/predicates/find.rb +55 -0
  39. data/lib/async/enumerable/methods/predicates/find_index.rb +50 -0
  40. data/lib/async/enumerable/methods/predicates/include.rb +23 -0
  41. data/lib/async/enumerable/methods/predicates/none.rb +27 -0
  42. data/lib/async/enumerable/methods/predicates/one.rb +48 -0
  43. data/lib/async/enumerable/methods/predicates.rb +29 -0
  44. data/lib/async/enumerable/methods/slicers.rb +34 -0
  45. data/lib/async/enumerable/methods/transformers/compact.rb +18 -0
  46. data/lib/async/enumerable/methods/transformers/filter_map.rb +19 -0
  47. data/lib/async/enumerable/methods/transformers/flat_map.rb +20 -0
  48. data/lib/async/enumerable/methods/transformers/map.rb +22 -0
  49. data/lib/async/enumerable/methods/transformers/reject.rb +19 -0
  50. data/lib/async/enumerable/methods/transformers/select.rb +21 -0
  51. data/lib/async/enumerable/methods/transformers/sort.rb +18 -0
  52. data/lib/async/enumerable/methods/transformers/sort_by.rb +19 -0
  53. data/lib/async/enumerable/methods/transformers/uniq.rb +18 -0
  54. data/lib/async/enumerable/methods/transformers.rb +35 -0
  55. data/lib/async/enumerable/methods.rb +26 -0
  56. data/lib/async/enumerable/version.rb +10 -0
  57. data/lib/async/enumerable.rb +72 -0
  58. data/lib/async/enumerator.rb +33 -0
  59. data/lib/enumerable/async.rb +38 -0
  60. data/scripts/debug_config.rb +26 -0
  61. data/scripts/debug_config2.rb +34 -0
  62. data/scripts/sketch.rb +30 -0
  63. data/scripts/test_aggregators.rb +66 -0
  64. data/scripts/test_ancestors.rb +12 -0
  65. data/scripts/test_async_chaining.rb +30 -0
  66. data/scripts/test_direct_method_calls.rb +53 -0
  67. data/scripts/test_example.rb +37 -0
  68. data/scripts/test_issue_7.rb +69 -0
  69. data/scripts/test_method_source.rb +15 -0
  70. metadata +145 -0
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async/enumerable/methods/each"
4
+
5
+ require "async/enumerable/methods/aggregators"
6
+ require "async/enumerable/methods/converters"
7
+ require "async/enumerable/methods/iterators"
8
+ require "async/enumerable/methods/predicates"
9
+ require "async/enumerable/methods/slicers"
10
+ require "async/enumerable/methods/transformers"
11
+
12
+ module Async
13
+ module Enumerable
14
+ # Methods contains all async implementations of Enumerable methods
15
+ module Methods
16
+ include Each
17
+
18
+ include Aggregators
19
+ include Converters
20
+ include Iterators
21
+ include Predicates
22
+ include Slicers
23
+ include Transformers
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Async
4
+ module Enumerable
5
+ # This constant follows semantic versioning (https://semver.org/):
6
+ #
7
+ # @return [String] The current version of the gem
8
+ VERSION = "0.1.0"
9
+ end
10
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async"
4
+ require "async/barrier"
5
+ require "async/semaphore"
6
+
7
+ require "concurrent/array"
8
+ require "concurrent/atomic/atomic_boolean"
9
+ require "concurrent/atomic/atomic_fixnum"
10
+ require "concurrent/atomic/atomic_reference"
11
+
12
+ require "forwardable"
13
+
14
+ module Async
15
+ module Enumerable
16
+ end
17
+ end
18
+
19
+ require "async/enumerable/configurable"
20
+ require "async/enumerable/concurrency_bounder"
21
+ require "async/enumerable/comparable"
22
+ require "async/enumerable/methods"
23
+ require "async/enumerable/version"
24
+
25
+ module Async
26
+ # Provides async parallel execution for Enumerable.
27
+ # See docs/reference/enumerable.md for detailed documentation.
28
+ module Enumerable
29
+ @__async_enumerable_config_ref = Concurrent::AtomicReference.new(Configurable::Config.new)
30
+ extend Configurable
31
+ include Configurable
32
+
33
+ class << self
34
+ alias_method :config, :__async_enumerable_config
35
+ alias_method :configure, :__async_enumerable_config
36
+
37
+ # Configures modules when included in a class.
38
+ # @param base [Class] The including class
39
+ def included(base)
40
+ base.extend(Configurable)
41
+ base.extend(Configurable::ClassMethods)
42
+ base.include(Configurable)
43
+ base.include(Comparable)
44
+ base.include(Methods)
45
+ base.include(AsyncMethod)
46
+ end
47
+
48
+ # Gets the module-level config reference.
49
+ # @return [AtomicReference] Config reference
50
+ def config_ref
51
+ @__async_enumerable_config_ref
52
+ end
53
+ end
54
+
55
+ # Async method module - included last to override Enumerable's version.
56
+ module AsyncMethod
57
+ # Returns async enumerator (idempotent - returns self if already async).
58
+ # @param kwargs [Hash] Configuration options (max_fibers, etc.)
59
+ # @return [Async::Enumerator] Async wrapper
60
+ def async(**kwargs)
61
+ return self if kwargs.empty? && self.class.include?(Async::Enumerable)
62
+
63
+ source = __async_enumerable_collection || self
64
+ config = __async_enumerable_config
65
+ Async::Enumerator.new(source, config, **kwargs)
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ require "async/enumerator"
72
+ require "enumerable/async"
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Async
4
+ # Wrapper providing async enumerable methods for parallel execution.
5
+ # See docs/reference/enumerator.md for detailed documentation.
6
+ class Enumerator
7
+ include Async::Enumerable
8
+ def_async_enumerable :@enumerable
9
+
10
+ # Creates async wrapper for enumerable.
11
+ # @param enumerable [Enumerable] Object to wrap
12
+ # @param config [Config, nil] Configuration object
13
+ # @param kwargs [Hash] Configuration options (max_fibers, etc.)
14
+ def initialize(enumerable = [], config = nil, **kwargs)
15
+ @enumerable = enumerable
16
+
17
+ # Only configure if config or kwargs are provided
18
+ if config || !kwargs.empty?
19
+ __async_enumerable_configure do |cfg|
20
+ # Merge config if provided
21
+ config&.to_h&.each do |key, val|
22
+ cfg[key] = val if val
23
+ end
24
+
25
+ # Merge kwargs if provided
26
+ kwargs.each do |key, val|
27
+ cfg[key] = val if val
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extension to Ruby's Enumerable module that adds asynchronous capabilities.
4
+ module Enumerable
5
+ # Returns an AsyncEnumerator wrapper that provides asynchronous versions of
6
+ # Enumerable methods for parallel execution.
7
+ #
8
+ # This method enables concurrent processing of Enumerable operations using
9
+ # the `socketry/async` library, allowing for significant performance
10
+ # improvements when dealing with I/O-bound operations or large collections.
11
+ #
12
+ # @param kwargs [Hash] Configuration options (max_fibers, etc.)
13
+ #
14
+ # @example Basic usage with async map
15
+ # [1, 2, 3].async.map { |n| n * 2 } # => [2, 4, 6] (executed in parallel)
16
+ #
17
+ # @example Using with I/O operations
18
+ # urls.async.map { |url| fetch_data(url) } # Fetches all URLs concurrently
19
+ #
20
+ # @example With custom fiber limit
21
+ # huge_dataset.async(max_fibers: 100).map { |item| process(item) }
22
+ # # Processes with max 100 concurrent fibers
23
+ #
24
+ # @example Chaining async operations
25
+ # data.async
26
+ # .select { |item| item.valid? }
27
+ # .map { |item| process(item) }
28
+ # .to_a
29
+ #
30
+ # @return [Async::Enumerator] An async wrapper around this
31
+ # enumerable that provides parallel execution capabilities for enumerable
32
+ # methods
33
+ #
34
+ # @see Async::Enumerator
35
+ def async(**kwargs)
36
+ Async::Enumerator.new(self, **kwargs)
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/async/enumerable"
3
+
4
+ # Test 1: Module config
5
+ puts "=== Test 1: Module config ==="
6
+ puts "Before configure: #{Async::Enumerable.config.max_fibers}"
7
+ Async::Enumerable.configure { |c| c.max_fibers = 50 }
8
+ puts "After configure: #{Async::Enumerable.config.max_fibers}"
9
+
10
+ # Test 2: Create enumerator with no config
11
+ puts "\n=== Test 2: Enumerator with no config ==="
12
+ enum = Async::Enumerator.new([1, 2, 3])
13
+ puts "Enumerator config: #{enum.__async_enumerable_config.max_fibers}"
14
+ puts "Module config: #{Async::Enumerable.config.max_fibers}"
15
+
16
+ # Test 3: Check config_ref
17
+ puts "\n=== Test 3: Config ref details ==="
18
+ puts "Enumerator config_ref: #{enum.__async_enumerable_config_ref}"
19
+ puts "Enumerator config_ref.get: #{enum.__async_enumerable_config_ref.get}"
20
+ puts "Module config_ref: #{Async::Enumerable.config_ref}"
21
+ puts "Module config_ref.get: #{Async::Enumerable.config_ref.get}"
22
+
23
+ # Test 4: Check merge_all_config
24
+ puts "\n=== Test 4: Merge all config ==="
25
+ merged = enum.__async_enumerable_merge_all_config
26
+ puts "Merged config: #{merged}"
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/async/enumerable"
3
+
4
+ # Check initial state
5
+ puts "=== Initial state ==="
6
+ puts "Module config: #{Async::Enumerable.config.max_fibers}"
7
+ puts "Enumerator class config: #{Async::Enumerator.__async_enumerable_config.max_fibers if Async::Enumerator.respond_to?(:__async_enumerable_config)}"
8
+
9
+ # Now configure module
10
+ puts "\n=== After configuring module ==="
11
+ Async::Enumerable.configure { |c| c.max_fibers = 50 }
12
+ puts "Module config: #{Async::Enumerable.config.max_fibers}"
13
+ puts "Enumerator class config: #{Async::Enumerator.__async_enumerable_config.max_fibers if Async::Enumerator.respond_to?(:__async_enumerable_config)}"
14
+
15
+ # Create a fresh class that includes Async::Enumerable
16
+ puts "\n=== Creating fresh class after module config ==="
17
+ class TestClass
18
+ include Async::Enumerable
19
+ def_async_enumerable :@data
20
+
21
+ def initialize
22
+ @data = [1, 2, 3]
23
+ end
24
+ end
25
+
26
+ puts "TestClass config: #{TestClass.__async_enumerable_config.max_fibers}"
27
+
28
+ # Create instance
29
+ puts "\n=== Creating instances ==="
30
+ enum = Async::Enumerator.new([1, 2, 3])
31
+ puts "Enumerator instance config: #{enum.__async_enumerable_config.max_fibers}"
32
+
33
+ test = TestClass.new
34
+ puts "TestClass instance config: #{test.__async_enumerable_config.max_fibers}"
data/scripts/sketch.rb ADDED
@@ -0,0 +1,30 @@
1
+ # i'm flailing a little bit here. what am i going for?
2
+
3
+ class Foo
4
+ include Async::Enumerable
5
+ def_async_enumerable :@bar, max_fibers: 100, pipeline: true
6
+
7
+ def initialize
8
+ @bar = []
9
+ end
10
+
11
+ def perform
12
+ Sync do |parent|
13
+ # @bar = [1, 2, 3, 4]
14
+ async(parent:) # update the instance config to use this parent
15
+ .map { |x| x + 1 } # async map over @bar, returning a task that adds 1 for each
16
+ # => Task{1 + 1}, Task{2 + 1}, Task{3 + 1}, Task{4 + 1} # unordered, individual
17
+ .accumulate(2, timeout_ms: 200) # gather batches up to 2 waiting up to 200ms before sending
18
+ # => [Task{1 + 1}, Task{2 + 1}], [Task{3 + 1}, Task{4 + 1}] # unordered
19
+ .map { |x| x.wait.sum }
20
+ # x is an accumulated Async::Enumerator<Task>
21
+ # wait blocks until all included tasks are resolved, then returns self
22
+ # sum is an Enumerable method called on the Async::Enumerator
23
+ # => [???]
24
+ .flatten
25
+ # => [Task...]
26
+ .sync # resolve the entire pipeline and all tasks in it
27
+ # => [2, 3, 4, 5]
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "async"
6
+ require "async/enumerable"
7
+
8
+ # Create a class that tracks when #each is called
9
+ class TrackedArray
10
+ include Async::Enumerable
11
+ def_enumerator :items
12
+
13
+ attr_reader :items, :each_called
14
+
15
+ def initialize(items)
16
+ @items = items
17
+ @each_called = false
18
+ end
19
+ end
20
+
21
+ Sync do
22
+ puts "Testing aggregator methods to verify they use async #each:"
23
+ puts
24
+
25
+ # Test reduce
26
+ tracked = TrackedArray.new([1, 2, 3, 4, 5])
27
+ result = tracked.async.reduce(0) do |sum, n|
28
+ puts " reduce: processing #{n} in fiber"
29
+ sum + n
30
+ end
31
+ puts "reduce result: #{result}"
32
+ puts
33
+
34
+ # Test sum with block
35
+ tracked = TrackedArray.new([1, 2, 3, 4, 5])
36
+ result = tracked.async.sum do |n|
37
+ puts " sum: processing #{n} in fiber"
38
+ n * 2
39
+ end
40
+ puts "sum result: #{result}"
41
+ puts
42
+
43
+ # Test count with block
44
+ tracked = TrackedArray.new([1, 2, 3, 4, 5])
45
+ result = tracked.async.count do |n|
46
+ puts " count: checking #{n} in fiber"
47
+ n.even?
48
+ end
49
+ puts "count result: #{result}"
50
+ puts
51
+
52
+ # Test tally
53
+ tracked = TrackedArray.new(%w[a b a c b a])
54
+ result = tracked.async.tally
55
+ puts "tally result: #{result}"
56
+ puts
57
+
58
+ # Test min/max with expensive comparison
59
+ tracked = TrackedArray.new([5, 2, 8, 1, 9])
60
+ result = tracked.async.min_by do |n|
61
+ puts " min_by: evaluating #{n} in fiber"
62
+ sleep(0.1) # Simulate expensive computation
63
+ n * -1 # Find the max by negating
64
+ end
65
+ puts "min_by result: #{result}"
66
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/async/enumerable"
5
+
6
+ e = [1, 2, 3].async
7
+ puts "Async::Enumerator ancestors:"
8
+ puts e.class.ancestors.take(10).map(&:to_s).join("\n ")
9
+
10
+ puts "\nMethod resolution for async:"
11
+ puts "Method owner: #{e.method(:async).owner}"
12
+ puts "Source: #{e.method(:async).source_location.inspect}"
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/async/enumerable"
5
+
6
+ puts "Testing async chaining..."
7
+ e = [1, 2, 3].async
8
+ puts "First async: #{e.class}"
9
+ puts "Is Async::Enumerator? #{e.is_a?(Async::Enumerator)}"
10
+
11
+ # Add debug to the async method temporarily
12
+ class Async::Enumerator
13
+ def async_debug(max_fibers: nil)
14
+ puts " In async method"
15
+ puts " self.class: #{self.class}"
16
+ puts " is_a?(::Async::Enumerator): #{is_a?(::Async::Enumerator)}"
17
+
18
+ if is_a?(::Async::Enumerator)
19
+ puts " Returning self!"
20
+ return self
21
+ end
22
+
23
+ puts " Creating new enumerator..."
24
+ super
25
+ end
26
+ end
27
+
28
+ e2 = e.async_debug
29
+ puts "Second async: #{e2.class}"
30
+ puts "Same object? #{e.equal?(e2)}"
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # Test script to demonstrate issue #7 - methods referencing @enumerable directly
3
+
4
+ require_relative "../lib/async/enumerable"
5
+
6
+ class MyCollection
7
+ include Async::Enumerable
8
+ def_enumerator :items
9
+
10
+ def initialize
11
+ @items = [1, 2, 3, 4, 5]
12
+ end
13
+
14
+ attr_reader :items
15
+ end
16
+
17
+ collection = MyCollection.new
18
+ puts "Collection responds to any?: #{collection.respond_to?(:any?)}"
19
+ puts "Collection.async responds to any?: #{collection.async.respond_to?(:any?)}"
20
+
21
+ # Try calling any? directly on the collection (not through async)
22
+ begin
23
+ result = collection.any? { |x| x > 3 }
24
+ puts "Direct any? result: #{result}"
25
+ rescue => e
26
+ puts "Direct any? error: #{e.message}"
27
+ puts " Backtrace: #{e.backtrace.first(3).join("\n ")}"
28
+ end
29
+
30
+ # Try calling through async (should work)
31
+ begin
32
+ result = collection.async.any? { |x| x > 3 }
33
+ puts "Async any? result: #{result}"
34
+ rescue => e
35
+ puts "Async any? error: #{e.message}"
36
+ end
37
+
38
+ puts "\n--- Testing all? method ---"
39
+ # Try all? directly
40
+ begin
41
+ result = collection.all? { |x| x > 0 }
42
+ puts "Direct all? result: #{result}"
43
+ rescue => e
44
+ puts "Direct all? error: #{e.message}"
45
+ end
46
+
47
+ # Try all? through async
48
+ begin
49
+ result = collection.async.all? { |x| x > 0 }
50
+ puts "Async all? result: #{result}"
51
+ rescue => e
52
+ puts "Async all? error: #{e.message}"
53
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "lib/async/enumerable"
5
+
6
+ # Example class that includes Async::Enumerable
7
+ class TodoList
8
+ include Async::Enumerable
9
+ def_enumerator :todos
10
+
11
+ def initialize
12
+ @todos = []
13
+ end
14
+
15
+ attr_reader :todos
16
+
17
+ def add(todo)
18
+ @todos << todo
19
+ end
20
+ end
21
+
22
+ # Test the new functionality
23
+ list = TodoList.new
24
+ list.add("Buy milk")
25
+ list.add("Write code")
26
+ list.add("Review PR")
27
+
28
+ puts "Testing def_enumerator with TodoList:"
29
+ results = list.async.map { |todo| "✓ #{todo}" }.sync
30
+ puts results.inspect
31
+
32
+ # Test that regular arrays still work
33
+ puts "\nTesting regular array async:"
34
+ array_results = [1, 2, 3].async.map { |n| n * 2 }.sync
35
+ puts array_results.inspect
36
+
37
+ puts "\nAll tests passed!"
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test script to verify issue #7 is resolved
5
+ # Issue: Methods should use enumerable source from def_enumerator, not @enumerable directly
6
+
7
+ require_relative "../lib/async/enumerable"
8
+
9
+ # Test 1: Includable pattern with def_enumerator
10
+ class MyCollection
11
+ include Async::Enumerable
12
+ def_enumerator :items # Specifies that 'items' method returns the enumerable
13
+
14
+ def initialize(data)
15
+ @items = data
16
+ end
17
+
18
+ attr_reader :items
19
+ end
20
+
21
+ puts "Test 1: Includable pattern with def_enumerator"
22
+ collection = MyCollection.new([1, 2, 3, 4, 5])
23
+
24
+ # Test async operations work with the specified source
25
+ result = collection.async.map { |x| x * 2 }.to_a
26
+ puts " map result: #{result.inspect}"
27
+ puts " ✓ map works" if result == [2, 4, 6, 8, 10]
28
+
29
+ # Test predicate methods work
30
+ all_positive = collection.all? { |x| x > 0 }
31
+ puts " all? result: #{all_positive}"
32
+ puts " ✓ all? works" if all_positive == true
33
+
34
+ any_large = collection.any? { |x| x > 3 }
35
+ puts " any? result: #{any_large}"
36
+ puts " ✓ any? works" if any_large == true
37
+
38
+ # Test 2: Async::Enumerator with @enumerable instance variable
39
+ puts "\nTest 2: Async::Enumerator wrapper pattern"
40
+ async_enum = [1, 2, 3, 4, 5].async
41
+
42
+ # Verify it uses @enumerable correctly
43
+ result = async_enum.map { |x| x * 2 }.to_a
44
+ puts " map result: #{result.inspect}"
45
+ puts " ✓ Async::Enumerator map works" if result == [2, 4, 6, 8, 10]
46
+
47
+ # Test 3: Class without def_enumerator (uses self)
48
+ class DirectCollection
49
+ include Async::Enumerable
50
+ include Enumerable
51
+
52
+ def initialize(data)
53
+ @data = data
54
+ end
55
+
56
+ def each(&block)
57
+ @data.each(&block)
58
+ end
59
+ end
60
+
61
+ puts "\nTest 3: Without def_enumerator (uses self)"
62
+ direct = DirectCollection.new([1, 2, 3, 4, 5])
63
+
64
+ # Should use self as the enumerable source
65
+ result = direct.async.map { |x| x * 2 }.to_a
66
+ puts " map result: #{result.inspect}"
67
+ puts " ✓ Direct collection works" if result == [2, 4, 6, 8, 10]
68
+
69
+ puts "\n✅ Issue #7 is RESOLVED! All methods properly use the enumerable source."
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/async/enumerable"
5
+
6
+ e = [1, 2, 3].async
7
+ puts "First async: #{e.class}"
8
+
9
+ # Check which async method is defined
10
+ puts "\nMethod owner: #{e.method(:async).owner}"
11
+ puts "Method source location: #{e.method(:async).source_location.inspect}"
12
+
13
+ # Check class methods
14
+ puts "\nClass responds to enumerable_source? #{e.class.respond_to?(:enumerable_source)}"
15
+ puts "Enumerable source: #{e.class.enumerable_source.inspect}" if e.class.respond_to?(:enumerable_source)