activeasync 0.0.1

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