asynchronize 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.
- checksums.yaml +7 -0
- data/Rakefile +8 -0
- data/asynchronize.gemspec +24 -0
- data/lib/asynchronize.rb +59 -0
- data/readme.md +90 -0
- data/spec/spec.rb +108 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3469c3b1731ec0b85a8014439e676153947b17adf250567a15779fc039cb39a4
|
4
|
+
data.tar.gz: 7145e39f5206053ab44c1f896b3b26c242891919b48e1c4afae26ce61e2b8199
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f970416bd025a1e3ad544fcda9d4babb6fbf7eb75c9e764c88ed987c720f2c3122893d83aacd142ecfbbc80f1a169e6554144efef33a37d5fd96140659366dd8
|
7
|
+
data.tar.gz: 3b4829c2ecccdad396c960966febf28d6176621cede70d1243aae3abd6a968c3df4a0e5fa25ff6dcd7ee0831d479c6e95bc6fec2e21a3416d48a5afa84a78ca1
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'asynchronize'
|
5
|
+
s.version = '0.1.0'
|
6
|
+
s.date = Date.today.to_s
|
7
|
+
s.summary = 'Easily make multiple methods asynchronous at once.'
|
8
|
+
s.description = 'Take any synchronous method, and run it asynchronously, ' +
|
9
|
+
'without cluttering your code with repetetive boilerplate.'
|
10
|
+
s.author = 'Kenneth Cochran'
|
11
|
+
s.email = 'kenneth.cochran101@gmail.com'
|
12
|
+
s.files = [
|
13
|
+
'lib/asynchronize.rb',
|
14
|
+
'spec/spec.rb',
|
15
|
+
'asynchronize.gemspec',
|
16
|
+
'Rakefile',
|
17
|
+
'readme.md'
|
18
|
+
]
|
19
|
+
s.test_files = [
|
20
|
+
'spec/spec.rb'
|
21
|
+
]
|
22
|
+
s.homepage = 'https://github.com/kennycoc/asynchronize'
|
23
|
+
s.license = 'MIT'
|
24
|
+
end
|
data/lib/asynchronize.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Asynchronize
|
2
|
+
require 'set'
|
3
|
+
def self.included base
|
4
|
+
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
|
+
def self.asynchronize(*methods)
|
15
|
+
@@methods_to_async.merge(methods)
|
16
|
+
methods.each do |method|
|
17
|
+
# If it's not defined yet, we'll get it with method_added
|
18
|
+
next unless method_defined? method
|
19
|
+
Asynchronize.create_new_method(method, self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Save the old method_added so we don't overwrite it.
|
24
|
+
if method_defined? :method_added
|
25
|
+
alias_method :old_method_added, :method_added
|
26
|
+
undef_method(:method_added)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.method_added(method)
|
30
|
+
old_method_added(method) if method_defined? :old_method_added
|
31
|
+
return unless @@methods_to_async.include? method
|
32
|
+
return if @@methods_asyncing.include? method
|
33
|
+
Asynchronize.create_new_method(method, self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.create_new_method(method, klass)
|
39
|
+
klass.instance_eval do
|
40
|
+
old_method = instance_method(method)
|
41
|
+
return if @@asynced_methods.include?(old_method)
|
42
|
+
undef_method method
|
43
|
+
@@methods_asyncing.add(method)
|
44
|
+
|
45
|
+
define_method(method) do |*args, &block|
|
46
|
+
return Thread.new(args, block) do |targs, tblock|
|
47
|
+
result = old_method.bind(self).(*targs)
|
48
|
+
unless tblock.nil?
|
49
|
+
tblock.call(result);
|
50
|
+
else
|
51
|
+
Thread.current[:return_value] = result
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end # define_method
|
55
|
+
@@methods_asyncing.delete(method)
|
56
|
+
@@asynced_methods.add(instance_method method)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# Asynchronize
|
2
|
+
### The easiest way to make a method asynchronous.
|
3
|
+
|
4
|
+
Find yourself writing the same boilerplate for all your asynchronous methods?
|
5
|
+
Get dryyy with asynchronize.
|
6
|
+
|
7
|
+
There are no dependencies other than Ruby.
|
8
|
+
Just install with `gem install asynchronize` or add to your Gemfile and `bundle`
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
Create a class with asynchronized methods
|
12
|
+
```Ruby
|
13
|
+
require 'asynchronize'
|
14
|
+
class Test
|
15
|
+
include Asynchronize
|
16
|
+
# This can be called anywhere.
|
17
|
+
asynchronize :my_test, :my_other_test
|
18
|
+
def my_test
|
19
|
+
return 'test'
|
20
|
+
end
|
21
|
+
def my_other_test
|
22
|
+
#do stuff here too
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
Now, to call those methods.
|
28
|
+
You can just pass it a block.
|
29
|
+
```Ruby
|
30
|
+
Test.new.my_test do |return_value|
|
31
|
+
puts return_value
|
32
|
+
end
|
33
|
+
# > test
|
34
|
+
```
|
35
|
+
|
36
|
+
Or, you can manage the thread yourself; the returned value will be in the thread
|
37
|
+
variable `:return_value` once it returns.
|
38
|
+
```Ruby
|
39
|
+
thread = Test.new.my_test
|
40
|
+
thread.join
|
41
|
+
puts thread[:return_value]
|
42
|
+
# > test
|
43
|
+
```
|
44
|
+
|
45
|
+
## Inspiration
|
46
|
+
I got tired of writing this over and over.
|
47
|
+
```Ruby
|
48
|
+
def method_name(args)
|
49
|
+
Thread.new(args) do |targs|
|
50
|
+
# Actual code.
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
It's extra typing, adds an unneeded extra layer of nesting, and just feels
|
55
|
+
dirty. Now, I can just call asynchronize to make any method asynchronous.
|
56
|
+
|
57
|
+
## FAQ
|
58
|
+
### Metaprogramming?? won't this hurt performance?
|
59
|
+
Not at all! We're actually totally redefining the methods, so the method itself
|
60
|
+
is exactly as efficient as it would have been had you wrote it that way
|
61
|
+
originally.
|
62
|
+
|
63
|
+
### So, how does it work?
|
64
|
+
When you `include Asynchronize` it does two things.
|
65
|
+
1. It defines the asynchronize method for your class
|
66
|
+
2. It defines method_added on your class.
|
67
|
+
|
68
|
+
When you call asynchronize, it creates a set containing all of the methods you
|
69
|
+
want asynchronized. If they already exist, they are modified; otherwise,
|
70
|
+
method_added checks for them with every new method you add to the class. This
|
71
|
+
way, you can call asynchronize any time, and know that the methods will be
|
72
|
+
asynchronized when you use them.
|
73
|
+
|
74
|
+
### So, does that mean I can't use asynchronize if I already use method_added?
|
75
|
+
We check for and alias your old method_added. It will be called before
|
76
|
+
anything else. Of course, if you define method_added after including
|
77
|
+
Asynchronize, you have to do the same and be careful to not overwrite ours!
|
78
|
+
|
79
|
+
### Why do I need another framework? My code's bloated enough as it is?
|
80
|
+
It's super tiny. Just a light wrapper around the existing language features.
|
81
|
+
Seriously, it's just around fifty lines of code. Actually, according to
|
82
|
+
[cloc](https://www.npmjs.com/package/cloc) there's twice as many lines in the
|
83
|
+
tests as the source. You should read it, I'd love feedback!
|
84
|
+
|
85
|
+
### Do you accept contributions?
|
86
|
+
Absolutely! If your use case isn't compatible with the project, you find a
|
87
|
+
bug, or just want to donate some tests; make an issue or send a PR please.
|
88
|
+
|
89
|
+
## License
|
90
|
+
MIT
|
data/spec/spec.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require './lib/asynchronize.rb'
|
3
|
+
|
4
|
+
class BasicSpec < Minitest::Test
|
5
|
+
describe Asynchronize do
|
6
|
+
before do
|
7
|
+
if defined? Test
|
8
|
+
BasicSpec.send(:remove_const, :Test)
|
9
|
+
end
|
10
|
+
class Test
|
11
|
+
include Asynchronize
|
12
|
+
def test(val=5)
|
13
|
+
return val
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
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
|
+
it "should not return a thread unless we asynchronize it" do
|
26
|
+
Test.new.test.class.wont_equal(Thread, "The method was not overwritten")
|
27
|
+
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
|
+
it "should not throw an error if the specified method does not exist" do
|
37
|
+
Test.asynchronize :notamethod
|
38
|
+
end
|
39
|
+
it "should not create a method if the specified method does not exist" do
|
40
|
+
Test.asynchronize :notamethod
|
41
|
+
Test.method_defined?(:notamethod).must_equal false
|
42
|
+
end
|
43
|
+
it "should not affect methods on other classes when called before" do
|
44
|
+
Test.asynchronize :test
|
45
|
+
class MyTest
|
46
|
+
def test
|
47
|
+
end
|
48
|
+
end
|
49
|
+
Test.new.test.class.must_equal(Thread)
|
50
|
+
MyTest.new.test.class.wont_equal(Thread)
|
51
|
+
end
|
52
|
+
it "should not affect methods on other classes when called after" do
|
53
|
+
class AnotherTest
|
54
|
+
def test
|
55
|
+
end
|
56
|
+
end
|
57
|
+
Test.asynchronize :test
|
58
|
+
Test.new.test.class.must_equal(Thread)
|
59
|
+
AnotherTest.new.test.class.wont_equal(Thread)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "when we call asynchronized after defining the method" do
|
64
|
+
before do
|
65
|
+
Test.asynchronize :test
|
66
|
+
end
|
67
|
+
it "should return a thread" do
|
68
|
+
Test.new.test.class.must_equal(Thread,
|
69
|
+
"The asynchronized method without a block did not return a thread.")
|
70
|
+
end
|
71
|
+
it "should provide the result in return_value" do
|
72
|
+
Test.new.test.join[:return_value].must_equal 5
|
73
|
+
end
|
74
|
+
it "should provide the result as a block parameter" do
|
75
|
+
temp = 0
|
76
|
+
Test.new.test do |res|
|
77
|
+
temp = res
|
78
|
+
end.join
|
79
|
+
temp.must_equal 5, "temp is equal to #{temp}."
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "when we call asynchronized before defining the method" do
|
84
|
+
before do
|
85
|
+
Test.asynchronize :othertest
|
86
|
+
class Test
|
87
|
+
def othertest
|
88
|
+
return 5
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
it "should return a thread" do
|
93
|
+
Test.new.othertest.class.must_equal(Thread,
|
94
|
+
"The asynchronized method without a block did not return a thread.")
|
95
|
+
end
|
96
|
+
it "should provide the result in return_value" do
|
97
|
+
Test.new.othertest.join[:return_value].must_equal 5
|
98
|
+
end
|
99
|
+
it "should provide the result as a block parameter" do
|
100
|
+
temp = 0
|
101
|
+
Test.new.othertest do |res|
|
102
|
+
temp = res
|
103
|
+
end.join
|
104
|
+
temp.must_equal 5, "temp is equal to #{temp}."
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: asynchronize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kenneth Cochran
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-31 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Take any synchronous method, and run it asynchronously, without cluttering
|
14
|
+
your code with repetetive boilerplate.
|
15
|
+
email: kenneth.cochran101@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- Rakefile
|
21
|
+
- asynchronize.gemspec
|
22
|
+
- lib/asynchronize.rb
|
23
|
+
- readme.md
|
24
|
+
- spec/spec.rb
|
25
|
+
homepage: https://github.com/kennycoc/asynchronize
|
26
|
+
licenses:
|
27
|
+
- MIT
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 2.7.6
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: Easily make multiple methods asynchronous at once.
|
49
|
+
test_files:
|
50
|
+
- spec/spec.rb
|