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 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,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = "spec/*spec.rb"
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
@@ -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
@@ -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