timberline-rails 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bd1b6647a272ed349fa5841ea5c034cc5d08113c
4
- data.tar.gz: 91c83bda9ed02f63524e2f3dcc5c89f205e7d220
3
+ metadata.gz: 166b9a7d89b80420e9b0072e2f18a20c393a8cb8
4
+ data.tar.gz: b6a3be0f462f3a92a0a1ca452d650da96de826e1
5
5
  SHA512:
6
- metadata.gz: d760c7a1f26209a068f3c7f378b531ed3e81a3f4f3e1c8f983e6450c28865c7e364fd363fd3804b2b5fbb8f50366ccf350b784695e0d71b75c0538960420ba76
7
- data.tar.gz: 0d952ff2409dbf5d29f42eb24e27ebaf8357d45dde94fbdb5fdeb317089b1341d55d0286abb8378ded345746022d6e98aa1531d5b9f6e84e46dd7f823dd61cee
6
+ metadata.gz: cc1f6396cf3b87cc2f3d8fc85026a6310de21a8a215539632f387aad31e8256835fdb05c62dcc055c151167934593a24a413ce7a95953987a1f18d9ea637447d
7
+ data.tar.gz: dd79ad8efb7782543047877e749bccca601376c9f5470b0f0efa60bd0088d3c1ebb217012405c7e64d1ece94218c2df0c9e1b5be551d11f2d88e7ea1a2b78f41
data/CHANGELOG CHANGED
@@ -1,3 +1,6 @@
1
+ 0.2.0
2
+ - Add Timberline::Rails::ActiveRecord and Timberline::Rails::ActiveRecordWorker support
3
+ - Upgrade to Timberline v0.7.0 with Worker support
1
4
  0.1.0
2
5
  - First public release of Timberline-Rails
3
6
  - Add Rails-specific configuration logic for loading config/timberline.yml
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- timberline-rails (0.1.0)
5
- timberline (~> 0.6.0)
4
+ timberline-rails (0.2.0)
5
+ timberline (~> 0.7.0)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
@@ -34,13 +34,14 @@ GEM
34
34
  rspec-support (~> 3.0.0)
35
35
  rspec-support (3.0.0)
36
36
  slop (3.5.0)
37
- timberline (0.6.0)
37
+ timberline (0.7.0)
38
38
  daemons
39
39
  redis
40
40
  redis-expiring-set
41
41
  redis-namespace
42
42
  trollop
43
43
  trollop (2.0)
44
+ yard (0.8.7.4)
44
45
 
45
46
  PLATFORMS
46
47
  ruby
@@ -50,3 +51,4 @@ DEPENDENCIES
50
51
  rake
51
52
  rspec (~> 3.0.0)
52
53
  timberline-rails!
54
+ yard
data/README.markdown CHANGED
@@ -1,5 +1,8 @@
1
1
  # Timberline-Rails
2
2
 
3
+ ![](https://travis-ci.org/treehouse/timberline-rails.svg) 
4
+ [![Gem Version](https://badge.fury.io/rb/timberline-rails.svg)](http://badge.fury.io/rb/timberline-rails)
5
+
3
6
  ## Purpose
4
7
 
5
8
  Timberline-Rails adds some extra functionality to
@@ -11,22 +14,53 @@ in the context of a Rails application.
11
14
  ### Configuration
12
15
 
13
16
  In base Timberline, you have a few options for configuration, from defining a
14
- `TIMBERLINE\_YAML` constant that points to a yaml config file to defining the
17
+ `TIMBERLINE_YAML` constant that points to a yaml config file to defining the
15
18
  configuration in code using `Timberline.configure`. Timberline-Rails adds in
16
19
  automatic detection for a config file at `config/timberline.yml` in your Rails
17
20
  app, complete with environment support (just like database.yml).
18
21
 
19
- ## Usage
22
+ ### Timberline::Rails::ActiveRecord and Timberline::Rails::ActiveRecordWorker
20
23
 
21
- Timberline-Rails works exactly like Timberline; just make sure that you have
22
- `timberline-rails` listed in your Gemfile and you're good to go.
24
+ In order to make running jobs in the background as easy as possible,
25
+ Timberline-Rails comes with two new items to help:
26
+
27
+ #### Timberline::Rails::ActiveRecord
28
+
29
+ This is a module that you can include in your ActiveRecord models to give you
30
+ an easy DSL for marking certain methods to always run in the background.
31
+
32
+ Example:
33
+
34
+ class User < ActiveRecord::Model
35
+ include Timberline::Rails::ActiveRecord
36
+
37
+ # specify the queue for jobs to run on
38
+ timberline_queue "user_jobs"
23
39
 
24
- ## TODO
40
+ def send_some_email
41
+ ...
42
+ end
43
+ # Now whenever send_some_email gets called, we put a message onto the "user_jobs" queue
44
+ # rather than calling the method directly.
45
+ delay_method :send_some_email
46
+ end
25
47
 
26
- Still to be done:
48
+ #### Timberline::Rails::ActiveRecordWorker
27
49
 
28
- - **Rails Workers** - implement workers that expect to operate on ActiveRecord objects
29
- so you can quickly and easily background tasks in Rails.
50
+ This is a worker designed to parse messages put on a queue by Timberline::Rails::ActiveRecord.
51
+ To continue our example from above:
52
+
53
+ # run this script with rails/runner or use some other means to load your Rails environment
54
+
55
+ Timberline::Rails::ActiveRecordWorker.new("user_jobs").watch
56
+
57
+ ...and items will be pulled off of the `user_jobs` queue and processed (in this case,
58
+ we'll look up the appropriate User record and call `#send_some_email` on it directly).=
59
+
60
+ ## Usage
61
+
62
+ Timberline-Rails works exactly like Timberline; just make sure that you have
63
+ `timberline-rails` listed in your Gemfile and you're good to go.
30
64
 
31
65
  ## Contributions
32
66
 
@@ -0,0 +1,76 @@
1
+ class Timberline
2
+ class Rails
3
+ # Timberline::Rails::ActiveRecord will add some extra logic
4
+ # to your ActiveRecord models so that you can quickly and
5
+ # easily defer certain methods to background processing.
6
+ #
7
+ # Although this module is primarily intended for use with
8
+ # ActiveRecord models, it will actually work with any class
9
+ # that implements the following methods:
10
+ # - primary_key - a method that returns a String representing the
11
+ # primary_key for this model (for ActiveRecord this is usually "id")
12
+ # - find_by_[primary_key] - a method that returns an object matching
13
+ # the specified primary key, or nil if it does not exist (e.g. "find_by_id")
14
+ #
15
+ # @see Timberline::Rails::ActiveRecordWorker
16
+ #
17
+ # @example using Timberline::Rails::ActiveRecord to process an email in the background
18
+ # class User < ActiveRecord::Base
19
+ # include Timberline::Rails::ActiveRecord
20
+ #
21
+ # timberline_queue "users"
22
+ #
23
+ # def send_some_email
24
+ # # do stuff to send the email
25
+ # end
26
+ # delay_method :send_some_email
27
+ # end
28
+ #
29
+ module ActiveRecord
30
+ def self.included(klass)
31
+ klass.class_eval do
32
+ extend Timberline::Rails::ActiveRecord::ClassMethods
33
+ end
34
+ end
35
+
36
+ # Class methods that get added to any class that includes Timberline::Rails::ActiveRecord.
37
+ #
38
+ module ClassMethods
39
+ # Set the queue name for this model. Required.
40
+ # @param [String] queue_name the name of the queue that this model will use
41
+ # to process jobs.
42
+ #
43
+ def timberline_queue(queue_name)
44
+ @timberline_queue = Timberline.queue(queue_name)
45
+ end
46
+
47
+ # Push an item onto the queue specified for this model.
48
+ # @see delay_method
49
+ #
50
+ def timberline_push(content, metadata = {})
51
+ if @timberline_queue.nil?
52
+ raise "You must specify a queue name using .timberline_queue first"
53
+ end
54
+ @timberline_queue.push(content, metadata)
55
+ end
56
+
57
+ # Mark a method as one that needs to be executed in the background.
58
+ # @param [Symbol, String] method_name the name of the method to execute in the background.
59
+ # This method will be aliased to synchronous_[method_name] in the event
60
+ # that it needs to be called directly.
61
+ #
62
+ def delay_method(method_name)
63
+ alias_method "synchronous_#{method_name}", method_name
64
+ define_method "timberline_#{method_name}" do
65
+ model_name = self.class.name
66
+ model_key = self.send(self.class.primary_key)
67
+ self.class.timberline_push({ model_name: model_name,
68
+ model_key: model_key,
69
+ method_name: method_name })
70
+ end
71
+ alias_method method_name, "timberline_#{method_name}"
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,57 @@
1
+ class Timberline
2
+ class Rails
3
+ # The ActiveRecordWorker is designed to process items put on
4
+ # the queue by Timberline::Rails::ActiveRecord. It processes items
5
+ # off of the queue, instantiates the specified model, and calls the
6
+ # specified synchronous method. If the Timberline::Rails::ActiveRecord
7
+ # module is not in use on the model, and the synchronous_method_name method
8
+ # is not defined, the worker will attempt to call method_name instead, so
9
+ # that the worker can still be used for jobs that fit the Timberline::Rails::ActiveRecord
10
+ # format even when the module itself has not been used to dispatch the job.
11
+ #
12
+ # @see Timberline::Rails::ActiveRecord
13
+ #
14
+ class ActiveRecordWorker < ::Timberline::Worker
15
+
16
+ # Processes items off of a Timberline::Rails::ActiveRecord-compatible
17
+ # queue.
18
+ #
19
+ # @param [Timberline::Envelope] item an Envelope whose contents should be
20
+ # a hash containing three keys: method_name (the name of the method to call
21
+ # on the model), model_name (the name of the model on which to call the method),
22
+ # and model_key (the primary key of the model, for lookup purposes).
23
+ #
24
+ # @see Timberline::Rails::ActiveRecord::ClassMethods#delay_method
25
+ # @see Timberline::Worker#watch
26
+ def process_item(item)
27
+ contents = item.contents
28
+ method_name = contents["method_name"]
29
+ model_name = contents["model_name"]
30
+ model_key = contents["model_key"]
31
+ klass = model_name.split('::').reduce(Module, :const_get)
32
+ model = klass.send("find_by_#{klass.primary_key}", model_key)
33
+ if model.nil?
34
+ # Retry because there may have been a race condition
35
+ item.retry_reason = "Model not found"
36
+ retry_item(item)
37
+ else
38
+ if model.respond_to? "synchronous_#{method_name}"
39
+ model.send("synchronous_#{method_name}")
40
+ elsif model.respond_to? method_name
41
+ model.send(method_name)
42
+ else
43
+ item.error_reason = "Method not found on model"
44
+ error_item(item)
45
+ end
46
+ end
47
+ rescue ItemErrored, ItemRetried => e
48
+ # throw ItemErrored and ItemRetried back up the chain
49
+ raise e
50
+ rescue Exception => e
51
+ item.error_reason = e.message
52
+ item.error_backtrace = e.backtrace
53
+ error_item(item)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,8 @@
1
+ class Timberline
2
+ class Rails
3
+ # Raised when Timberline::Rails::ActiveRecord is included in a
4
+ # model that does not implement required functionality.
5
+ # @see Timberline::Rails::ActiveRecord
6
+ class InvalidModelError < StandardError; end
7
+ end
8
+ end
@@ -1,5 +1,6 @@
1
1
  class Timberline
2
2
  class Rails
3
- VERSION = "0.1.0"
3
+ # The canonical version of Timberline-Rails
4
+ VERSION = "0.2.0"
4
5
  end
5
6
  end
@@ -1,8 +1,16 @@
1
1
  require 'timberline'
2
2
  require 'timberline/rails/version'
3
+ require 'timberline/rails/exceptions'
4
+ require 'timberline/rails/active_record'
5
+ require 'timberline/rails/active_record_worker'
3
6
 
4
7
  class Timberline
8
+ # Re-open the Timberline::Config class from Timberline
5
9
  class Config
10
+ # Load config/timberline.yml from the Rails root if it exists.
11
+ # If it doesn't, or if we're somehow not in a Rails application,
12
+ # just use the default Timberline behavior for instantiating
13
+ # Config objects.
6
14
  def rails_initialize
7
15
  if defined? ::Rails
8
16
  config_file = File.join(::Rails.root, 'config', 'timberline.yml')
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe Timberline::Rails::ActiveRecord do
4
+ describe "including in a class" do
5
+ subject { SpecSupport::FakeRails::FakeModel }
6
+
7
+ it "defines the timberline_queue method" do
8
+ expect(subject).to respond_to :timberline_queue
9
+ end
10
+
11
+ it "defines the timberline_push method" do
12
+ expect(subject).to respond_to :timberline_push
13
+ end
14
+
15
+ it "defines the delay_method method" do
16
+ expect(subject).to respond_to :delay_method
17
+ end
18
+ end
19
+
20
+ describe "using delay_method" do
21
+ subject { SpecSupport::FakeRails::FakeModel.new }
22
+ let(:method_name) { "some_method" }
23
+
24
+ it "aliases the old method to synchronous_method_name" do
25
+ expect(subject).to respond_to("synchronous_#{method_name}")
26
+ end
27
+
28
+ it "sets up a new method at timberline_method_name" do
29
+ expect(subject).to respond_to("timberline_#{method_name}")
30
+ end
31
+
32
+ it "replaces the old method with timberline_method_name" do
33
+ expect(subject.method("timberline_#{method_name}")).to eq(subject.method(method_name))
34
+ end
35
+ end
36
+
37
+ describe "calling a delayed method" do
38
+ subject { SpecSupport::FakeRails::FakeModel.new }
39
+ let(:queue) { SpecSupport::FakeRails::FakeModel.instance_variable_get("@timberline_queue") }
40
+ let(:expected_data) { { model_name: "SpecSupport::FakeRails::FakeModel",
41
+ model_key: 1,
42
+ method_name: :some_method } }
43
+
44
+ it "pushes data about the method onto the queue" do
45
+ expect(queue).to receive(:push).with(expected_data, {})
46
+ subject.some_method
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe Timberline::Rails::ActiveRecordWorker do
4
+ subject { Timberline::Rails::ActiveRecordWorker.new("fake_queue") }
5
+ let(:queue) { Timberline.queue("fake_queue") }
6
+ let(:item) do
7
+ queue.push({ model_name: "SpecSupport::FakeRails::FakeModel",
8
+ model_key: 1,
9
+ method_name: :some_method })
10
+ queue.pop
11
+ end
12
+
13
+ describe "#process_item" do
14
+ context "when handling a method that has been delayed by Timberline::Rails::ActiveRecord" do
15
+ it "attempts to lookup the model" do
16
+ expect(SpecSupport::FakeRails::FakeModel)
17
+ .to receive(:find_by_id).with(1).and_call_original
18
+ subject.process_item(item)
19
+ end
20
+
21
+ it "calls the appropriate method on the model" do
22
+ expect_any_instance_of(SpecSupport::FakeRails::FakeModel)
23
+ .to receive(:synchronous_some_method)
24
+ subject.process_item(item)
25
+ end
26
+ end
27
+
28
+ context "when handling a method that has not been delayed by Timberline::Rails::ActiveRecord" do
29
+ before do
30
+ item.contents["method_name"] = :some_other_method
31
+ end
32
+
33
+ it "attempts to lookup the model" do
34
+ expect(SpecSupport::FakeRails::FakeModel)
35
+ .to receive(:find_by_id).with(1).and_call_original
36
+ subject.process_item(item)
37
+ end
38
+
39
+ it "calls the appropriate method on the model" do
40
+ expect_any_instance_of(SpecSupport::FakeRails::FakeModel)
41
+ .to receive(:some_other_method)
42
+ subject.process_item(item)
43
+ end
44
+ end
45
+
46
+ context "If the model can't be found" do
47
+ before do
48
+ allow(SpecSupport::FakeRails::FakeModel).to receive(:find_by_id) { nil }
49
+ end
50
+
51
+ it "retries the item" do
52
+ expect(subject).to receive(:retry_item).with(item)
53
+ subject.process_item(item)
54
+ end
55
+
56
+ it "gives the item a retry_reason" do
57
+ expect { subject.process_item(item) }.to raise_error(Timberline::ItemRetried)
58
+ expect(item.retry_reason).to include("Model not found")
59
+ end
60
+ end
61
+
62
+ context "If the method doesn't exist on the model" do
63
+ before do
64
+ item.contents["method_name"] = "gibberish"
65
+ end
66
+
67
+ it "errors the item" do
68
+ expect(subject).to receive(:error_item).with(item)
69
+ subject.process_item(item)
70
+ end
71
+
72
+ it "gives the item an error_reason" do
73
+ expect { subject.process_item(item) }.to raise_error(Timberline::ItemErrored)
74
+ expect(item.error_reason).to include("Method not found")
75
+ end
76
+ end
77
+
78
+ context "If the method raises an exception of its own" do
79
+ before do
80
+ allow_any_instance_of(SpecSupport::FakeRails::FakeModel).to receive(:synchronous_some_method) { raise "This doesn't work" }
81
+ end
82
+
83
+ it "errors the item" do
84
+ expect(subject).to receive(:error_item).with(item)
85
+ subject.process_item(item)
86
+ end
87
+
88
+ it "provides an error_reason" do
89
+ expect { subject.process_item(item) }.to raise_error(Timberline::ItemErrored)
90
+ expect(item.error_reason).to include("This doesn't work")
91
+ end
92
+
93
+ it "provides an error_backtrace" do
94
+ expect { subject.process_item(item) }.to raise_error(Timberline::ItemErrored)
95
+ expect(item.error_backtrace).to be_a Array
96
+ end
97
+ end
98
+ end
99
+ end
@@ -13,5 +13,32 @@ module SpecSupport
13
13
  def self.destroy_fake_env
14
14
  Object.send(:remove_const, :Rails)
15
15
  end
16
+
17
+ class FakeModel
18
+ include Timberline::Rails::ActiveRecord
19
+
20
+ timberline_queue "fake_queue"
21
+
22
+ def self.primary_key
23
+ "id"
24
+ end
25
+
26
+ def self.find_by_id(id)
27
+ FakeModel.new
28
+ end
29
+
30
+ def id
31
+ 1
32
+ end
33
+
34
+ def some_method
35
+ # left blank
36
+ end
37
+ delay_method :some_method
38
+
39
+ def some_other_method
40
+ # left blank
41
+ end
42
+ end
16
43
  end
17
44
  end
@@ -18,9 +18,10 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
 
21
- s.add_runtime_dependency "timberline", "~> 0.6.0"
21
+ s.add_runtime_dependency "timberline", "~> 0.7.0"
22
22
 
23
23
  s.add_development_dependency "rake"
24
24
  s.add_development_dependency "rspec", '~> 3.0.0'
25
25
  s.add_development_dependency "pry"
26
+ s.add_development_dependency "yard"
26
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timberline-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tommy Morgan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-06 00:00:00.000000000 Z
11
+ date: 2014-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: timberline
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: 0.6.0
19
+ version: 0.7.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ~>
25
25
  - !ruby/object:Gem::Version
26
- version: 0.6.0
26
+ version: 0.7.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: Timberline is a simple and extensible queuing system built in Ruby and
70
84
  backed by Redis. It makes as few assumptions as possible about how you want to interact
71
85
  with your queues while also allowing for some functionality that should be universally
@@ -85,9 +99,14 @@ files:
85
99
  - README.markdown
86
100
  - Rakefile
87
101
  - lib/timberline-rails.rb
102
+ - lib/timberline/rails/active_record.rb
103
+ - lib/timberline/rails/active_record_worker.rb
104
+ - lib/timberline/rails/exceptions.rb
88
105
  - lib/timberline/rails/version.rb
89
106
  - spec/fake_rails/config/timberline.yml
90
- - spec/lib/config_spec.rb
107
+ - spec/lib/timberline/config_spec.rb
108
+ - spec/lib/timberline/rails/active_record_spec.rb
109
+ - spec/lib/timberline/rails/active_record_worker_spec.rb
91
110
  - spec/spec_helper.rb
92
111
  - spec/support/fake_rails.rb
93
112
  - spec/support/timberline_reset.rb
@@ -116,3 +135,4 @@ signing_key:
116
135
  specification_version: 4
117
136
  summary: Rails logic and niceties for the Timberline queueing system.
118
137
  test_files: []
138
+ has_rdoc: