activeasync 0.0.1

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.
Files changed (64) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile +4 -0
  4. data/MIT-LICENSE +20 -0
  5. data/Rakefile +21 -0
  6. data/activeasync.gemspec +31 -0
  7. data/lib/active_async.rb +23 -0
  8. data/lib/active_async/active_record.rb +13 -0
  9. data/lib/active_async/async.rb +38 -0
  10. data/lib/active_async/callbacks.rb +45 -0
  11. data/lib/active_async/fake_resque.rb +9 -0
  12. data/lib/active_async/version.rb +3 -0
  13. data/spec/active_async/active_record_spec.rb +28 -0
  14. data/spec/active_async/async_spec.rb +62 -0
  15. data/spec/active_async/callbacks_spec.rb +117 -0
  16. data/spec/active_async/fake_resque_spec.rb +9 -0
  17. data/spec/active_async_spec.rb +19 -0
  18. data/spec/dummy/.gitignore +15 -0
  19. data/spec/dummy/Rakefile +7 -0
  20. data/spec/dummy/app/assets/images/rails.png +0 -0
  21. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  22. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  23. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  24. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  25. data/spec/dummy/app/mailers/.gitkeep +0 -0
  26. data/spec/dummy/app/models/.gitkeep +0 -0
  27. data/spec/dummy/app/models/blog.rb +10 -0
  28. data/spec/dummy/app/models/post.rb +4 -0
  29. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/spec/dummy/config.ru +4 -0
  31. data/spec/dummy/config/application.rb +60 -0
  32. data/spec/dummy/config/boot.rb +6 -0
  33. data/spec/dummy/config/database.yml +25 -0
  34. data/spec/dummy/config/environment.rb +5 -0
  35. data/spec/dummy/config/environments/development.rb +32 -0
  36. data/spec/dummy/config/environments/production.rb +54 -0
  37. data/spec/dummy/config/environments/test.rb +37 -0
  38. data/spec/dummy/config/initializers/async.rb +2 -0
  39. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/spec/dummy/config/initializers/inflections.rb +15 -0
  41. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  42. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  43. data/spec/dummy/config/initializers/session_store.rb +8 -0
  44. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  45. data/spec/dummy/config/locales/en.yml +5 -0
  46. data/spec/dummy/config/routes.rb +58 -0
  47. data/spec/dummy/db/migrate/20120126234735_create_posts.rb +10 -0
  48. data/spec/dummy/db/migrate/20120127010859_create_blogs.rb +8 -0
  49. data/spec/dummy/db/schema.rb +28 -0
  50. data/spec/dummy/db/seeds.rb +7 -0
  51. data/spec/dummy/lib/assets/.gitkeep +0 -0
  52. data/spec/dummy/lib/tasks/.gitkeep +0 -0
  53. data/spec/dummy/log/.gitkeep +0 -0
  54. data/spec/dummy/public/404.html +26 -0
  55. data/spec/dummy/public/422.html +26 -0
  56. data/spec/dummy/public/500.html +25 -0
  57. data/spec/dummy/public/favicon.ico +0 -0
  58. data/spec/dummy/script/rails +6 -0
  59. data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
  60. data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
  61. data/spec/dummy/vendor/plugins/.gitkeep +0 -0
  62. data/spec/spec_helper.rb +31 -0
  63. data/spec/support/stub_resque.rb +12 -0
  64. metadata +232 -0
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rspec
6
+ log/*.log
7
+ spec/dummy/db/*.sqlite3
8
+ spec/dummy/log/*.log
9
+ spec/dummy/tmp/
10
+ tmp/
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## 0.0.1 (2012-01-26)
2
+
3
+ * async method: interface for sending methods with arguments to Resque
4
+ * :async => true option for running active model callbacks in the background
5
+ * ActiveRecord module to enable async option for after_save, after_update, and after_create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in activeasync.gemspec
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2011 Ross Kaffenberger
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ require 'rspec/core/rake_task'
6
+
7
+ desc "Run specs"
8
+ RSpec::Core::RakeTask.new do |t|
9
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
10
+ # Put spec opts in a file named .rspec in root
11
+ end
12
+
13
+ desc "Generate code coverage"
14
+ RSpec::Core::RakeTask.new(:coverage) do |t|
15
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
16
+ t.rcov = true
17
+ t.rcov_opts = ['--exclude', 'spec']
18
+ end
19
+
20
+ desc "Run the specs"
21
+ task :default => ["spec"]
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "active_async/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "activeasync"
7
+ s.version = ActiveAsync::VERSION
8
+ s.authors = ["Ross Kaffenberger"]
9
+ s.email = ["rosskaff@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Add async support to ruby objects}
12
+ s.description = %q{Provides async methods ruby objects for queuing background jobs. Currently supports Resque. Bonus: callback hooks for ActiveRecord objects}
13
+
14
+ s.rubyforge_project = "activeasync"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_runtime_dependency "rest-client"
23
+ s.add_dependency "resque", "~> 1.10"
24
+ s.add_dependency "activesupport", "~> 3.0"
25
+
26
+ s.add_development_dependency "rails", "~> 3.0"
27
+ s.add_development_dependency "rspec", "~> 2.8.0"
28
+ s.add_development_dependency "database_cleaner", "~> 0.7.0"
29
+ s.add_development_dependency "ruby-debug"
30
+ s.add_development_dependency "sqlite3"
31
+ end
@@ -0,0 +1,23 @@
1
+ require "resque"
2
+ require "active_support/concern"
3
+ require "active_async/version"
4
+ require "active_async/async"
5
+ require "active_async/callbacks"
6
+ require "active_async/active_record"
7
+
8
+ module ActiveAsync
9
+ extend self
10
+
11
+ def background
12
+ @background ||= ::Resque
13
+ end
14
+
15
+ def background=(background)
16
+ @background = background
17
+ end
18
+
19
+ def reset!
20
+ @background = nil
21
+ end
22
+
23
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveAsync
2
+ module ActiveRecord
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include ActiveAsync::Async
7
+ include ActiveAsync::Callbacks
8
+
9
+ define_async_callbacks :after_save, :after_update, :after_create
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ module ActiveAsync
2
+ module Async
3
+ extend ActiveSupport::Concern
4
+
5
+ CLASS_IDENTIFIER = "__class__".freeze
6
+
7
+ included do
8
+ class_attribute :queue
9
+ self.queue = :general
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ # This will be called by a worker when a job needs to be processed
15
+ def perform(id, method, *args)
16
+ async_class_or_instance(id).send(method, *args)
17
+ end
18
+
19
+ def async(method, *args)
20
+ ActiveAsync.background.enqueue(self, CLASS_IDENTIFIER, method, *args)
21
+ end
22
+
23
+ private
24
+
25
+ def async_class_or_instance(id)
26
+ id == CLASS_IDENTIFIER ? self : self.find(id)
27
+ end
28
+
29
+ end
30
+
31
+ # We can pass this any Repository instance method that we want to
32
+ # run later.
33
+ def async(method, *args)
34
+ ActiveAsync.background.enqueue(self.class, id, method, *args)
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ module ActiveAsync
2
+ module Callbacks
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def define_async_callbacks(*callback_names)
8
+ callback_names.each do |callback_name|
9
+ class_eval <<-RUBY
10
+ define_callbacks :async_#{callback_name}
11
+
12
+ def self.#{callback_name}_with_async(*methods)
13
+ #{callback_name}_without_async(*extract_async_methods(methods))
14
+ end
15
+
16
+ class << self
17
+ alias_method_chain :#{callback_name}, :async
18
+ end
19
+ RUBY
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def extract_async_methods(methods)
26
+ options = methods.extract_options!
27
+ methods = options[:async] ? define_async_methods(methods) : methods
28
+ methods.push(options.except(:async))
29
+ end
30
+
31
+ def define_async_methods(methods)
32
+ methods.map do |method_name|
33
+ async_name = "async_#{method_name}"
34
+ unless instance_methods.include?(async_name)
35
+ define_method(async_name) do
36
+ async(method_name)
37
+ end
38
+ end
39
+ async_name
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveAsync
2
+ module FakeResque
3
+ extend self
4
+
5
+ def self.enqueue(klass, *args)
6
+ klass.send(:perform, *args)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveAsync
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveAsync::ActiveRecord do
4
+
5
+ let(:blog) { Blog.new }
6
+
7
+ before(:each) do
8
+ Blog.stub!(:find).and_return(blog)
9
+ end
10
+
11
+ describe "included", :stub_resque do
12
+ it "should define async callback for save" do
13
+ blog.should_receive(:expensive_save_method)
14
+ blog.save
15
+ end
16
+
17
+ it "should define async callback for update" do
18
+ blog.save
19
+ blog.should_receive(:expensive_update_method)
20
+ blog.update_attributes(:title => "foo")
21
+ end
22
+
23
+ it "should define async callback for create" do
24
+ blog.should_receive(:expensive_create_method)
25
+ Blog.create
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveAsync::Async do
4
+
5
+ context "activerecord, resque", :stub_resque do
6
+ let(:post) { Post.create(:title => "A new post") }
7
+
8
+ describe "queue" do
9
+ it "should provide default queue" do
10
+ Post.queue.should == :general
11
+ end
12
+
13
+ it "allows override per class" do
14
+ Blog.queue = :blog
15
+ Blog.queue.should == :blog
16
+ Post.queue.should == :general
17
+ end
18
+ end
19
+
20
+ describe "perform" do
21
+ it "should send method for given instance id" do
22
+ Post.any_instance.should_receive(:expensive_method)
23
+ Post.perform(post.id, :expensive_method)
24
+ end
25
+ end
26
+
27
+ describe "async" do
28
+ it "should send method for given instance id" do
29
+ Post.should_receive(:expensive_method)
30
+ Post.async(:expensive_method)
31
+ end
32
+ end
33
+
34
+ describe "#async" do
35
+ it "should call the given method" do
36
+ Post.any_instance.should_receive(:expensive_method)
37
+ post.async(:expensive_method)
38
+ end
39
+
40
+ it "should call the given method" do
41
+ Post.any_instance.should_receive(:expensive_method).with(1, 2, 3)
42
+ post.async(:expensive_method, 1, 2, 3)
43
+ end
44
+
45
+ it "should call 'perform' class method as expected by Resque" do
46
+ Post.should_receive(:perform).with(post.id, :expensive_method)
47
+ post.async(:expensive_method)
48
+ end
49
+
50
+ it "should find the existing record" do
51
+ Post.should_receive(:find).with(post.id).and_return(post)
52
+ post.async(:expensive_method)
53
+ end
54
+
55
+ it "should raise error if record not found" do
56
+ post = Post.new
57
+ lambda { post.async(:expensive_method) }.should raise_error(ActiveRecord::RecordNotFound)
58
+ end
59
+ end
60
+ end
61
+
62
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveAsync::Callbacks do
4
+
5
+ describe "active model example", :stub_resque do
6
+ class DummyBase
7
+ extend ActiveModel::Callbacks
8
+
9
+ define_model_callbacks :save, :update, :create
10
+
11
+ def id; object_id; end
12
+ def find(id); end
13
+
14
+ def save; run_callbacks :save; end
15
+ def update; run_callbacks :update; end
16
+ def create; run_callbacks :create; end
17
+
18
+ include ActiveAsync::Async
19
+ include ActiveAsync::Callbacks
20
+
21
+ define_async_callbacks :after_save, :after_update, :after_create
22
+ end
23
+
24
+ class DummyUser < DummyBase
25
+ after_save :save_expensive_method, :async => true
26
+ after_update :update_expensive_method, :async => true
27
+ after_create :create_expensive_method, :async => true, :more_options => true
28
+
29
+ def save_expensive_method; self.class.run_expensive_method; end
30
+ def create_expensive_method; self.class.run_expensive_method; end
31
+ def update_expensive_method; self.class.run_expensive_method; end
32
+
33
+ def self.run_expensive_method
34
+ # please run with async!
35
+ end
36
+ end
37
+
38
+ let(:model) { DummyUser.new }
39
+
40
+ before(:each) do
41
+ DummyUser.stub!(:find).and_return(model)
42
+ end
43
+
44
+ describe "callbacks with async" do
45
+
46
+ describe "on create" do
47
+ it "should run create method" do
48
+ model.should_receive(:create_expensive_method).once
49
+ model.create
50
+ end
51
+
52
+ it "should run methods asynchronously on create" do
53
+ model.should_receive(:async).with(:create_expensive_method)
54
+ model.create
55
+ end
56
+
57
+ end
58
+
59
+ describe "on save" do
60
+ it "should run save method" do
61
+ model.should_receive(:save_expensive_method).once
62
+ model.save
63
+ end
64
+
65
+ it "should run methods asynchronously on create" do
66
+ model.should_receive(:async).with(:save_expensive_method)
67
+ model.save
68
+ end
69
+ end
70
+
71
+ describe "on update" do
72
+ it "should run update method" do
73
+ model.should_receive(:update_expensive_method).once
74
+ model.update
75
+ end
76
+
77
+ it "should run methods asynchronously on update" do
78
+ model.should_receive(:async).with(:update_expensive_method)
79
+ model.update
80
+ end
81
+ end
82
+
83
+ it "should run expensive method for each callback" do
84
+ DummyUser.should_receive(:run_expensive_method).exactly(1).times
85
+ model.create
86
+
87
+ DummyUser.should_receive(:run_expensive_method).exactly(1).times
88
+ model.save
89
+
90
+ DummyUser.should_receive(:run_expensive_method).exactly(1).times
91
+ model.update
92
+ end
93
+
94
+ describe ".extract_async_methods" do
95
+ it "should forward unmodified arguments if not asyncing" do
96
+ extracted_args = DummyUser.send(:extract_async_methods, [:method_name, {:more_options => true}])
97
+ extracted_args.should == [:method_name, {:more_options => true}]
98
+ end
99
+
100
+ it "should pass along all options to after_create macro execpt :async" do
101
+ extracted_args = DummyUser.send(:extract_async_methods, [:method_name, {:more_options => true, :async => true}])
102
+ extracted_args.should include(:more_options => true)
103
+ extracted_args.should_not include(:async => true)
104
+ end
105
+
106
+ it "should update method name and define async method when async is true" do
107
+ extracted_args = DummyUser.send(:extract_async_methods, [:method_name, {:async => true}])
108
+ extracted_args.should == ["async_method_name", {}]
109
+ DummyUser.instance_methods.should include("async_method_name")
110
+ end
111
+ end
112
+
113
+
114
+ end
115
+ end
116
+
117
+ end