asynchronize 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|