asynchronize 0.2.1 → 0.3.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.
- checksums.yaml +4 -4
- data/lib/asynchronize.rb +66 -60
- data/readme.md +66 -48
- data/spec/spec.rb +10 -44
- metadata +65 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 912acc8b2e852aa7ccdd7fc09193f46fa64730f74972e9a955b8ba2bf3e40f1f
|
4
|
+
data.tar.gz: c6b91b991495bb45ce6580596c9919a4a376437f3c441706c63f84a62941c77e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86f7e988a2b97f36bb951045fca9214fd81b53239ae6a2fe5c0330a01f6f81c51bfcdb73052e2ac07bf434ec47dd362005274fa54243f8b3be9e4ec74fe53a96
|
7
|
+
data.tar.gz: 274687d5e6b292d4f6bbf85515c95f9d4eb84bef5b8c9b5a51f6fd363ed99000ee7e36ff30672d6b714009077a637b3ecc43746a726631164b12d44d7ac1b563
|
data/lib/asynchronize.rb
CHANGED
@@ -1,83 +1,89 @@
|
|
1
|
+
##
|
2
|
+
# Include this module to allow a declarative syntax for defining asynch methods
|
3
|
+
#
|
4
|
+
# Defines only one method on the including class: `asynchronize`
|
5
|
+
#
|
1
6
|
module Asynchronize
|
2
|
-
require 'set'
|
3
7
|
def self.included(base)
|
4
8
|
base.class_eval do
|
5
|
-
# The methods we have already asynchronized
|
6
|
-
@asynced_methods = Set.new
|
7
|
-
# The methods that should be asynchronized.
|
8
|
-
@methods_to_async = Set.new
|
9
|
-
# Originally used a single value here, but that's not thread safe.
|
10
|
-
# ...Though you probably have other problems if you have multiple
|
11
|
-
# threads adding methods to your class.
|
12
|
-
@methods_asyncing = Set.new
|
13
|
-
|
14
9
|
##
|
15
10
|
# Call to asynchronize a method.
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
11
|
+
#
|
12
|
+
# This does two things
|
13
|
+
# 1. Creates and prepends a module named Asynchronized.
|
14
|
+
# 2. Scopes that module to the calling class.
|
15
|
+
# 3. Defines each of the passed methods on that module.
|
16
|
+
#
|
17
|
+
# Additional notes:
|
18
|
+
# - The new methods wrap the old method within Thread.new.
|
19
|
+
# - Subsequent calls only add methods to the existing Module.
|
20
|
+
# - Will silently fail if the method has already been asynchronized
|
21
21
|
#
|
22
22
|
# @param methods [Symbol] The methods to be asynchronized.
|
23
23
|
# @example To add any number of methods to be asynchronized.
|
24
24
|
# asynchronize :method1, :method2, :methodn
|
25
|
+
#
|
25
26
|
def self.asynchronize(*methods)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Asynchronize.create_new_method(method, self) if method_defined?(method)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# Save the old method_added so we don't overwrite it.
|
34
|
-
if self.methods.include?(:method_added)
|
35
|
-
singleton_class.send(:alias_method, :old_method_added, :method_added)
|
36
|
-
singleton_class.send(:undef_method, :method_added)
|
37
|
-
end
|
38
|
-
|
39
|
-
##
|
40
|
-
# Will asynchronize a method if it has not been asynchronized already, and
|
41
|
-
# it is in the list of methods to asynchronize. If method missing was
|
42
|
-
# already defined, it will call the previous method_missing before
|
43
|
-
# anything else Ruby calls this automatically when defining a method; it
|
44
|
-
# should not be called directly.
|
45
|
-
def self.method_added(method)
|
46
|
-
# Return if this is an inherited class that hasn't included asynchronize
|
47
|
-
return if @methods_asyncing.nil?
|
48
|
-
# Return if we're already processing this method
|
49
|
-
return if @methods_asyncing.include?(method.hash)
|
50
|
-
@methods_asyncing.add(method.hash)
|
51
|
-
self.old_method_added(method) if self.methods.include?(:old_method_added)
|
52
|
-
return unless @methods_to_async.include?(method.hash)
|
53
|
-
# This will delete from @methods_asyncing
|
54
|
-
Asynchronize.create_new_method(method, self)
|
27
|
+
return if methods.empty?
|
28
|
+
async_container = Asynchronize._get_container_for(self)
|
29
|
+
Asynchronize._define_methods_on_object(methods, async_container)
|
55
30
|
end
|
56
31
|
end
|
57
32
|
end
|
58
33
|
|
34
|
+
private
|
59
35
|
##
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
36
|
+
# Define methods on object
|
37
|
+
#
|
38
|
+
# For each method in the methods array
|
39
|
+
#
|
40
|
+
# - If method already defined, go to the next.
|
41
|
+
# - If method does not exist, create it and go to the next.
|
42
|
+
#
|
43
|
+
# @param methods [Array<Symbol>] The methods to be bound.
|
44
|
+
# @param obj [Object] The object for the methods to be defined on.
|
45
|
+
#
|
46
|
+
def self._define_methods_on_object(methods, obj)
|
47
|
+
methods.each do |method|
|
48
|
+
next if obj.methods.include?(method)
|
49
|
+
obj.send(:define_method, method, _build_method)
|
71
50
|
end
|
72
51
|
end
|
73
52
|
|
74
|
-
|
75
|
-
|
53
|
+
##
|
54
|
+
# Build Method
|
55
|
+
#
|
56
|
+
# This always returns the same Proc object. In it's own method for clarity.
|
57
|
+
#
|
58
|
+
# @return [Proc] The actual asynchronous method defined.
|
59
|
+
#
|
60
|
+
def self._build_method
|
76
61
|
return Proc.new do |*args, &block|
|
77
|
-
return Thread.new(
|
78
|
-
Thread.current[:return_value] =
|
79
|
-
|
62
|
+
return Thread.new(args, block) do |thread_args, thread_block|
|
63
|
+
Thread.current[:return_value] = super(*thread_args)
|
64
|
+
thread_block.call(Thread.current[:return_value]) if thread_block
|
80
65
|
end
|
81
66
|
end
|
82
67
|
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Container setup
|
71
|
+
#
|
72
|
+
# Creates the container module that will hold our asynchronous wrappers.
|
73
|
+
#
|
74
|
+
# - If the container module is defined, return it.
|
75
|
+
# - If the container module is not defined, create, prepend, and return it.
|
76
|
+
#
|
77
|
+
# @param obj [Class] The Class to prepend our module to
|
78
|
+
# @return [Module] The already prepended module to define our methods on.
|
79
|
+
#
|
80
|
+
def self._get_container_for(obj)
|
81
|
+
if obj.const_defined?('Asynchronized')
|
82
|
+
return obj.const_get('Asynchronized')
|
83
|
+
else
|
84
|
+
async_container = obj.const_set('Asynchronized', Module.new)
|
85
|
+
obj.prepend async_container
|
86
|
+
return async_container
|
87
|
+
end
|
88
|
+
end
|
83
89
|
end
|
data/readme.md
CHANGED
@@ -2,12 +2,13 @@
|
|
2
2
|
[](https://codeclimate.com/github/kennycoc/asynchronize/maintainability)
|
3
3
|
[](https://codeclimate.com/github/kennycoc/asynchronize/test_coverage)
|
4
4
|
# Asynchronize
|
5
|
-
###
|
5
|
+
### A declarative syntax for creating multithreaded methods.
|
6
6
|
|
7
7
|
Find yourself writing the same boilerplate for all your asynchronous methods?
|
8
8
|
Get dry with asynchronize.
|
9
9
|
|
10
|
-
Just
|
10
|
+
Just add to your Gemfile and `bundle` or install globally with
|
11
|
+
`gem install asynchronize`
|
11
12
|
|
12
13
|
## Usage
|
13
14
|
Create a class with asynchronized methods
|
@@ -15,7 +16,7 @@ Create a class with asynchronized methods
|
|
15
16
|
require 'asynchronize'
|
16
17
|
class Test
|
17
18
|
include Asynchronize
|
18
|
-
#
|
19
|
+
# Can be called before or after the definitions. I prefer it at the top of classes.
|
19
20
|
asynchronize :my_test, :my_other_test
|
20
21
|
def my_test
|
21
22
|
return 'test'
|
@@ -31,20 +32,25 @@ You can manage the thread yourself; the returned value will be in the thread
|
|
31
32
|
variable `:return_value` once it returns.
|
32
33
|
```Ruby
|
33
34
|
thread = Test.new.my_test
|
34
|
-
thread.join
|
35
|
-
puts thread[:return_value] # > test
|
35
|
+
puts thread.join[:return_value] # > test
|
36
36
|
```
|
37
37
|
|
38
|
-
Or
|
39
|
-
|
38
|
+
Or to stay asynchronous when processing the result, you can pass it a block.
|
39
|
+
It will still return the thread, and the return value will still be in the
|
40
|
+
thread variable `:return_value`
|
40
41
|
```Ruby
|
41
|
-
Test.new.my_test do |return_value|
|
42
|
+
thread = Test.new.my_test do |return_value|
|
42
43
|
puts return_value # > test
|
43
44
|
end
|
45
|
+
thread.join[:return_value] # > also test
|
44
46
|
```
|
45
47
|
|
48
|
+
As you can see, it's just a regular thread. Make sure you call `Thread#join` to
|
49
|
+
ensure it completes before your process exits, and to catch any exceptions that
|
50
|
+
may have been thrown!
|
51
|
+
|
46
52
|
## Inspiration
|
47
|
-
I
|
53
|
+
While working on another project, I found myself writing this way too often:
|
48
54
|
```Ruby
|
49
55
|
def method_name(args)
|
50
56
|
Thread.new(args) do |targs|
|
@@ -52,63 +58,75 @@ def method_name(args)
|
|
52
58
|
end
|
53
59
|
end
|
54
60
|
```
|
55
|
-
It's extra typing, adds an unneeded extra layer of nesting
|
56
|
-
|
57
|
-
|
58
|
-
asynchronous.
|
61
|
+
It's extra typing, and adds an unneeded extra layer of nesting. I couldn't find
|
62
|
+
an existing library that wasn't trying add new layers of abstraction to
|
63
|
+
memorize; sometimes you just want a normal thread. Now, just call asynchronize
|
64
|
+
to make any method asynchronous.
|
59
65
|
|
60
66
|
## Versioning Policy
|
61
|
-
|
62
|
-
will follow [Semantic Versioning]
|
63
|
-
number (0.0.x) will be
|
64
|
-
|
65
|
-
the
|
66
|
-
|
67
|
-
|
67
|
+
|
68
|
+
Beginning with version 1.0.0, this project will follow [Semantic Versioning]
|
69
|
+
(https://semver.org) until then, the patch number (0.0.x) will be
|
70
|
+
updated for any changes that do not affect the public interface. Versions that
|
71
|
+
increment the minor number will have at least one of the following. A new
|
72
|
+
feature will be added, some feature will be deprecated, or some previously
|
73
|
+
deprecated feature will be removed. Deprecated features will be removed on the
|
74
|
+
very next version that increments the minor version number.
|
68
75
|
|
69
76
|
## FAQ
|
70
77
|
### Doesn't metaprogramming hurt performance?
|
71
|
-
Not at all!
|
72
|
-
is exactly as efficient as it would have been had you wrote it that way
|
73
|
-
originally.
|
78
|
+
Not at all! It actually works just like inheritance, so it won't be a problem.
|
74
79
|
|
75
80
|
### So, how does it work?
|
76
|
-
When you `include Asynchronize` it
|
77
|
-
|
78
|
-
|
81
|
+
When you `include Asynchronize` it creates an `asynchronize` method on your
|
82
|
+
class. The first time you call this method with any arguments, it creates a new
|
83
|
+
module with the methods you define. It uses `Module#prepend` to cause method
|
84
|
+
calls on the original object to be sent to it instead, and uses super to call
|
85
|
+
your original method inside it's own thread.
|
79
86
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
way, you can call asynchronize any time, and know that the methods will be
|
84
|
-
asynchronized when you use them.
|
85
|
-
|
86
|
-
### So, does that mean I can't use asynchronize if I already use method_added?
|
87
|
-
We check for and alias your old method_added. It will be called before
|
88
|
-
anything else. Of course, if you define method_added after including
|
89
|
-
Asynchronize, you have to do the same and be careful to not overwrite ours!
|
87
|
+
This implementation allows you to call asynchronize at the top of the class and
|
88
|
+
then define the methods below. Since it changes how you interact with those
|
89
|
+
method's return values, I thought it was important to allow this.
|
90
90
|
|
91
91
|
### Why do I need another gem? My code's bloated enough as it is?
|
92
92
|
It's super tiny. Just a light wrapper around the existing language features.
|
93
|
-
Seriously, it's just around
|
94
|
-
[cloc](https://www.npmjs.com/package/cloc) there's almost four
|
95
|
-
lines in the tests as the source. You should read it, I'd love
|
93
|
+
Seriously, it's just around forty lines of code as of version 0.3.0. Actually,
|
94
|
+
according to [cloc](https://www.npmjs.com/package/cloc) there's almost four
|
95
|
+
times as many lines in the tests as the source. You should read it, I'd love
|
96
|
+
feedback!
|
96
97
|
|
97
98
|
### Do you accept contributions?
|
98
|
-
Absolutely!
|
99
|
-
|
100
|
-
|
99
|
+
Absolutely!
|
100
|
+
1. Fork it (https://github.com/kennycoc/asynchronize/fork)
|
101
|
+
2. Create your feature branch (git checkout -b my-new-feature)
|
102
|
+
3. Commit your changes (git commit -am 'Add some feature')
|
103
|
+
4. Push to the branch (git push origin my-new-feature)
|
104
|
+
5. Create a new pull request.
|
105
|
+
|
106
|
+
It's just `bundle` to install dependencies, and `rake` to run the tests.
|
101
107
|
|
102
108
|
### What's the difference between asynchronize and promises/async..await?
|
103
|
-
Those
|
104
|
-
for
|
105
|
-
|
109
|
+
Those and other similar projects aim to create an entirely new abstraction to
|
110
|
+
use for interacting with threads. This project aims to be a light convenience
|
111
|
+
wrapper around the existing language features. Just define a regular method,
|
106
112
|
then interact with it's result like a regular thread.
|
107
113
|
|
108
114
|
### What versions are supported?
|
109
|
-
|
110
|
-
|
111
|
-
|
115
|
+
Ruby 2.3 and up. Unfortunately, Ruby versions prior to 2.0 do not support
|
116
|
+
`Module#prepend` and are not supported. Ruby versions prior to 2.3 have a bug
|
117
|
+
preventing usage of `super` with `define_method`. I'm unable to find a suitable
|
118
|
+
workaround for this issue. (`method(__method__).super_method.call` causes
|
119
|
+
problems when a method inherits from the asynchronized class.)
|
120
|
+
|
121
|
+
Luckily, all major Ruby implementations support Ruby language version 2.3. So I
|
122
|
+
don't see this as a huge problem. If anyone wants support for older versions,
|
123
|
+
and knows how to workaround this issue, feel free to submit a pull request.
|
124
|
+
|
125
|
+
We explicitly test against the following versions:
|
126
|
+
- Matz Ruby 2.5.1
|
127
|
+
- Matz Ruby 2.3.4
|
128
|
+
- JRuby 9.1.13 (language version 2.3.3)
|
129
|
+
- Rubinius 3.105 (language version 2.3.1)
|
112
130
|
|
113
131
|
## License
|
114
132
|
MIT
|
data/spec/spec.rb
CHANGED
@@ -16,29 +16,15 @@ class BasicSpec < Minitest::Test
|
|
16
16
|
end
|
17
17
|
|
18
18
|
describe "when we asynchronize a method" do
|
19
|
-
it "should not be the same method" do
|
20
|
-
original_method = Test.instance_method :test
|
21
|
-
Test.asynchronize :test
|
22
|
-
new_method = Test.instance_method(:test)
|
23
|
-
original_method.wont_equal(new_method, "The method was not overwritten")
|
24
|
-
end
|
25
19
|
it "should not return a thread unless we asynchronize it" do
|
26
20
|
Test.new.test.class.wont_equal(Thread, "The method was not overwritten")
|
27
21
|
end
|
28
|
-
it "should be the same method if we call a second time" do
|
29
|
-
Test.asynchronize :test
|
30
|
-
original_method = Test.instance_method :test
|
31
|
-
Test.asynchronize :test
|
32
|
-
new_method = Test.instance_method :test
|
33
|
-
original_method.must_equal(new_method,
|
34
|
-
"Asynchronized Inception has occurred")
|
35
|
-
end
|
36
22
|
it "should not throw an error if the specified method does not exist" do
|
37
23
|
Test.asynchronize :notamethod
|
38
24
|
end
|
39
25
|
it "should not create a method if the specified method does not exist" do
|
40
26
|
Test.asynchronize :notamethod
|
41
|
-
Test.
|
27
|
+
Test.methods(false).wont_include(:notamethod)
|
42
28
|
end
|
43
29
|
it "should not affect methods on other classes when called before" do
|
44
30
|
Test.asynchronize :test
|
@@ -105,35 +91,6 @@ class BasicSpec < Minitest::Test
|
|
105
91
|
end
|
106
92
|
end
|
107
93
|
|
108
|
-
describe "when there is an existing method_added" do
|
109
|
-
before do
|
110
|
-
class MethodAddedTest
|
111
|
-
@running = false
|
112
|
-
def self.method_added(method)
|
113
|
-
return if @running
|
114
|
-
@running = true
|
115
|
-
old_method = instance_method(method)
|
116
|
-
undef_method(method)
|
117
|
-
define_method(method) do
|
118
|
-
return old_method.bind(self).call + 1
|
119
|
-
end
|
120
|
-
@running = false
|
121
|
-
end
|
122
|
-
include Asynchronize
|
123
|
-
asynchronize :test
|
124
|
-
def test
|
125
|
-
return 4
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
after do
|
130
|
-
BasicSpec.send(:remove_const, :MethodAddedTest)
|
131
|
-
end
|
132
|
-
it "should call that method_added before, and only once." do
|
133
|
-
MethodAddedTest.new.test.join[:return_value].must_equal 5
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
94
|
describe "when inheriting from another class" do
|
138
95
|
before do
|
139
96
|
class ChildClassTest < Test
|
@@ -165,5 +122,14 @@ class BasicSpec < Minitest::Test
|
|
165
122
|
ChildClassTest.new.test.must_equal 6
|
166
123
|
end
|
167
124
|
end
|
125
|
+
|
126
|
+
describe "when asynchronize is called with no arguments" do
|
127
|
+
it "should not define an Asynchronized container" do
|
128
|
+
Test.asynchronize
|
129
|
+
Test.ancestors.find do |a|
|
130
|
+
a.name.split('::').include? 'Asynchronized'
|
131
|
+
end.must_be_nil
|
132
|
+
end
|
133
|
+
end
|
168
134
|
end
|
169
135
|
end
|
metadata
CHANGED
@@ -1,17 +1,74 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: asynchronize
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenneth Cochran
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-06-
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
|
11
|
+
date: 2018-06-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '12.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '12.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.11'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.16'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.16'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.11'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.11'
|
69
|
+
description: Sometimes you just want a regular thread without the overhead of a whole
|
70
|
+
new layer of abstraction. Asynchronize provides a declarative syntax to wrap any
|
71
|
+
method in a Thread.
|
15
72
|
email: kenneth.cochran101@gmail.com
|
16
73
|
executables: []
|
17
74
|
extensions: []
|
@@ -26,7 +83,7 @@ homepage: https://github.com/kennycoc/asynchronize
|
|
26
83
|
licenses:
|
27
84
|
- MIT
|
28
85
|
metadata: {}
|
29
|
-
post_install_message:
|
86
|
+
post_install_message: Making something cool with asynchronize? Let me know at https://github.com/kennycoc/asynchronize
|
30
87
|
rdoc_options: []
|
31
88
|
require_paths:
|
32
89
|
- lib
|
@@ -34,7 +91,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
34
91
|
requirements:
|
35
92
|
- - ">="
|
36
93
|
- !ruby/object:Gem::Version
|
37
|
-
version: '
|
94
|
+
version: '2.3'
|
38
95
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
96
|
requirements:
|
40
97
|
- - ">="
|
@@ -45,7 +102,7 @@ rubyforge_project:
|
|
45
102
|
rubygems_version: 2.7.6
|
46
103
|
signing_key:
|
47
104
|
specification_version: 4
|
48
|
-
summary:
|
105
|
+
summary: A declarative syntax for creating multithreaded methods.
|
49
106
|
test_files:
|
50
107
|
- spec/spec.rb
|
51
108
|
- spec/minitest_helper.rb
|