asynchronize 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Maintainability](https://api.codeclimate.com/v1/badges/30d40e270a3d7a0775a9/maintainability)](https://codeclimate.com/github/kennycoc/asynchronize/maintainability)
|
3
3
|
[![Test Coverage](https://api.codeclimate.com/v1/badges/30d40e270a3d7a0775a9/test_coverage)](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
|