timberline 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 01da0a8b72d0153c044386ba5d2a2947ceba809a
4
+ data.tar.gz: b56730906aa005b6107cea4f1c693362e8a1ee99
5
+ SHA512:
6
+ metadata.gz: e997f4fc10c86d1c64f2f4a18982e1fa9a2a30042d078988c3db13b1f675f7ca5260bad1b45a9b75b055292ff5e0a8d712e0fc7fe5e9ab0804d417510d0b71a2
7
+ data.tar.gz: a4c464628b537811773da27a6f95828bba46566b23bd53bfec452d17497dd99a464ef8c7adb3373df1f0ee4fd1d5534e42cbc9de8fb6a3ac18b860c4be747e7e
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ timberline
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby-19mode
5
+ - jruby-head
6
+ - 2.0.0
7
+ - 2.1.0
8
+ - ruby-head
9
+ services:
10
+ - redis-server
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in treeline.gemspec
3
+ # Gem dependencies specified in timberline.gemspec
4
4
  gemspec
data/README.markdown CHANGED
@@ -1,5 +1,7 @@
1
1
  # Timberline
2
2
 
3
+ ![](https://travis-ci.org/treehouse/timberline.svg)
4
+
3
5
  ## Purpose
4
6
 
5
7
  Timberline is a dead-simple queuing service written in Ruby and backed by Redis.
@@ -64,7 +66,7 @@ queue (more on that later). There are 3 ways to configure Timberline:
64
66
 
65
67
  1. The most direct way is to configure Timberline via ruby code as follows:
66
68
 
67
- Timberline.config do |c|
69
+ Timberline.configure do |c|
68
70
  c.database = 1
69
71
  c.host = "192.168.1.105"
70
72
  c.port = 12345
@@ -81,10 +83,13 @@ queue (more on that later). There are 3 ways to configure Timberline:
81
83
  automatically detect it and load it up. The syntax for this file is
82
84
  shockingly boring:
83
85
 
84
- database: 1
85
- host: 192.168.1.105
86
- port: 12345
87
- password: foobar
86
+ development:
87
+ database: 1
88
+ host: 192.168.1.105
89
+ port: 12345
90
+ password: foobar
91
+ production:
92
+ ...etc.
88
93
 
89
94
  3. Like the yaml format but you're not using Rails? Don't worry, just write your
90
95
  yaml file and set the TIMBERLINE\_YAML constant inside your app like so:
@@ -205,7 +210,7 @@ should probably change, but it probably won't until someone needs it to.
205
210
  Gem requirements/etc. should be handled by Bundler.
206
211
 
207
212
  ## License
208
- Copyright (C) 2012 by Tommy Morgan
213
+ Copyright (C) 2014 by Tommy Morgan
209
214
 
210
215
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
211
216
 
data/Rakefile CHANGED
@@ -1,8 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
+ require 'rspec/core/rake_task'
3
4
 
4
- Rake::TestTask.new do |t|
5
- t.libs << "test"
6
- t.test_files = FileList['test/unit/test_*.rb']
7
- t.verbose = true
8
- end
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
@@ -35,6 +35,7 @@ class Timberline
35
35
  config
36
36
  end
37
37
 
38
+ private
38
39
  def load_from_yaml(yaml_config)
39
40
  raise "Missing yaml configs!" if yaml_config.nil?
40
41
  ["database","host","port","timeout","password","logger","namespace"].each do |setting|
@@ -3,13 +3,16 @@ class Timberline
3
3
  attr_reader :queue_name, :read_timeout
4
4
 
5
5
  def initialize(queue_name, read_timeout= 0)
6
+ if queue_name.nil?
7
+ raise ArgumentError.new("Queue name must be provided.")
8
+ end
6
9
  @queue_name = queue_name
7
10
  @read_timeout = read_timeout
8
11
  @redis = Timberline.redis
9
12
  @redis.sadd "timberline_queue_names", queue_name
10
13
  end
11
14
 
12
- def delete!
15
+ def delete
13
16
  @redis.del @queue_name
14
17
  @redis.keys("#{@queue_name}:*").each do |key|
15
18
  @redis.del key
@@ -1,3 +1,3 @@
1
1
  class Timberline
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/timberline.rb CHANGED
@@ -13,141 +13,137 @@ require_relative "timberline/envelope"
13
13
 
14
14
  class Timberline
15
15
  class << self
16
-
17
16
  attr_reader :config
17
+ attr_accessor :watch_proc
18
+ end
18
19
 
19
- def redis=(server)
20
- initialize_if_necessary
21
- if server.is_a? Redis
22
- @redis = Redis::Namespace.new(@config.namespace, :redis => server)
23
- elsif server.is_a? Redis::Namespace
24
- @redis = server
25
- elsif server.nil?
26
- @redis = nil
27
- else
28
- raise "Not a valid Redis connection."
29
- end
20
+ def self.redis=(server)
21
+ initialize_if_necessary
22
+ if server.is_a? Redis
23
+ @redis = Redis::Namespace.new(@config.namespace, :redis => server)
24
+ elsif server.is_a? Redis::Namespace
25
+ @redis = server
26
+ elsif server.nil?
27
+ @redis = nil
28
+ else
29
+ raise "Not a valid Redis connection."
30
30
  end
31
+ end
31
32
 
32
- def redis
33
- initialize_if_necessary
34
- if @redis.nil?
35
- self.redis = Redis.new(@config.redis_config)
36
- end
37
-
38
- @redis
33
+ def self.redis
34
+ initialize_if_necessary
35
+ if @redis.nil?
36
+ self.redis = Redis.new(@config.redis_config)
39
37
  end
40
38
 
41
- def all_queues
42
- Timberline.redis.smembers("timberline_queue_names").map { |name| queue(name) }
43
- end
39
+ @redis
40
+ end
44
41
 
45
- def error_queue
46
- @error_queue ||= Queue.new("timberline_errors")
47
- end
42
+ def self.all_queues
43
+ Timberline.redis.smembers("timberline_queue_names").map { |name| queue(name) }
44
+ end
48
45
 
49
- def queue(queue_name)
50
- if @queue_list[queue_name].nil?
51
- @queue_list[queue_name] = Queue.new(queue_name)
52
- end
53
- @queue_list[queue_name]
54
- end
46
+ def self.error_queue
47
+ @error_queue ||= Queue.new("timberline_errors")
48
+ end
55
49
 
56
- def push(queue_name, data, metadata={})
57
- queue(queue_name).push(data, metadata)
58
- end
50
+ def self.queue(queue_name)
51
+ Queue.new(queue_name)
52
+ end
59
53
 
60
- def retry_item(item)
61
- if (item.retries < max_retries)
62
- item.retries += 1
63
- item.last_tried_at = Time.now.to_f
64
- queue(item.origin_queue).push(item)
65
- raise ItemRetried
66
- else
67
- error_item(item)
68
- end
69
- end
54
+ def self.push(queue_name, data, metadata={})
55
+ queue(queue_name).push(data, metadata)
56
+ end
70
57
 
71
- def error_item(item)
72
- item.fatal_error_at = Time.now.to_f
73
- error_queue.push(item)
74
- raise ItemErrored
58
+ def self.retry_item(item)
59
+ if (item.retries < max_retries)
60
+ item.retries += 1
61
+ item.last_tried_at = Time.now.to_f
62
+ queue(item.origin_queue).push(item)
63
+ else
64
+ error_item(item)
75
65
  end
66
+ end
76
67
 
77
- def pause(queue_name)
78
- queue(queue_name).pause
79
- end
68
+ def self.error_item(item)
69
+ item.fatal_error_at = Time.now.to_f
70
+ error_queue.push(item)
71
+ end
80
72
 
81
- def unpause(queue_name)
82
- queue(queue_name).unpause
83
- end
73
+ def self.pause(queue_name)
74
+ queue(queue_name).pause
75
+ end
84
76
 
85
- def config(&block)
86
- initialize_if_necessary
87
- yield @config
88
- end
77
+ def self.unpause(queue_name)
78
+ queue(queue_name).unpause
79
+ end
89
80
 
90
- def max_retries
91
- initialize_if_necessary
92
- @config.max_retries
93
- end
81
+ def self.configure(&block)
82
+ initialize_if_necessary
83
+ yield @config
84
+ end
94
85
 
95
- def stat_timeout
96
- initialize_if_necessary
97
- @config.stat_timeout
98
- end
86
+ def self.max_retries
87
+ initialize_if_necessary
88
+ @config.max_retries
89
+ end
99
90
 
100
- def stat_timeout_seconds
101
- initialize_if_necessary
102
- @config.stat_timeout * 60
103
- end
91
+ def self.stat_timeout
92
+ initialize_if_necessary
93
+ @config.stat_timeout
94
+ end
95
+
96
+ def self.stat_timeout_seconds
97
+ initialize_if_necessary
98
+ @config.stat_timeout * 60
99
+ end
104
100
 
105
- def watch(queue_name, &block)
106
- queue = queue(queue_name)
107
- while(true)
108
- item = queue.pop
109
- fix_binding(block)
110
- item.started_processing_at = Time.now.to_f
111
-
112
- begin
113
- block.call(item, self)
114
- rescue ItemRetried
115
- queue.add_retry_stat(item)
116
- rescue ItemErrored
117
- queue.add_error_stat(item)
118
- else
119
- item.finished_processing_at = Time.now.to_f
120
- queue.add_success_stat(item)
121
- end
101
+ def self.watch(queue_name, &block)
102
+ queue = queue(queue_name)
103
+ while(self.watch?)
104
+ item = queue.pop
105
+ fix_binding(block)
106
+ item.started_processing_at = Time.now.to_f
107
+
108
+ begin
109
+ block.call(item, self)
110
+ rescue ItemRetried
111
+ queue.add_retry_stat(item)
112
+ rescue ItemErrored
113
+ queue.add_error_stat(item)
114
+ else
115
+ item.finished_processing_at = Time.now.to_f
116
+ queue.add_success_stat(item)
122
117
  end
123
118
  end
119
+ end
124
120
 
125
- private
126
- # Don't know if I like doing this, but we want the configuration to be
127
- # lazy-loaded so as to be sure and give users a chance to set up their
128
- # configurations.
129
- def initialize_if_necessary
130
- @config ||= Config.new
131
- end
121
+ private
122
+ def self.initialize_if_necessary
123
+ @config ||= Config.new
124
+ end
132
125
 
133
- # Hacky-hacky. I like the idea of calling retry_item(item) and
134
- # error_item(item)
135
- # directly from the watch block, but this seems ugly. There may be a better
136
- # way to do this.
137
- def fix_binding(block)
138
- binding = block.binding
139
- binding.eval <<-HERE
140
- def retry_item(item)
141
- Timberline.retry_item(item)
142
- raise Timberline::ItemRetried
143
- end
144
-
145
- def error_item(item)
146
- Timberline.error_item(item)
147
- raise Timberline::ItemErrored
148
- end
149
- HERE
150
- end
126
+ # Hacky-hacky. I like the idea of calling retry_item(item) and
127
+ # error_item(item)
128
+ # directly from the watch block, but this seems ugly. There may be a better
129
+ # way to do this.
130
+ def self.fix_binding(block)
131
+ binding = block.binding
132
+ binding.eval <<-HERE
133
+ def retry_item(item)
134
+ Timberline.retry_item(item)
135
+ raise Timberline::ItemRetried
136
+ end
137
+
138
+ def error_item(item)
139
+ Timberline.error_item(item)
140
+ raise Timberline::ItemErrored
141
+ end
142
+ HERE
143
+ end
144
+
145
+ def self.watch?
146
+ watch_proc.nil? ? true : watch_proc.call
151
147
  end
152
148
 
153
149
  class ItemRetried < Exception
@@ -156,5 +152,3 @@ class Timberline
156
152
  class ItemErrored < Exception
157
153
  end
158
154
  end
159
-
160
- Timberline.instance_variable_set("@queue_list", {})
@@ -0,0 +1,7 @@
1
+ development:
2
+ host: localhost
3
+ port: 12345
4
+ timeout: 10
5
+ password: foo
6
+ database: 3
7
+ namespace: treecurve
@@ -0,0 +1,170 @@
1
+ require 'spec_helper'
2
+
3
+ describe Timberline::Config do
4
+ describe "newly created" do
5
+ context "when the TIMBERLINE_YAML constant is defined" do
6
+ context "and the specified file exists" do
7
+ before do
8
+ SpecSupport::TimberlineYaml.load_constant
9
+ end
10
+
11
+ after do
12
+ SpecSupport::TimberlineYaml.destroy_constant
13
+ end
14
+
15
+ it "loads the host variable from the config file" do
16
+ expect(subject.host).to eq("localhost")
17
+ end
18
+
19
+ it "loads the port variable from the config file" do
20
+ expect(subject.port).to eq(12345)
21
+ end
22
+
23
+ it "loads the timeout variable from the config file" do
24
+ expect(subject.timeout).to eq(10)
25
+ end
26
+
27
+ it "loads the password from the config file" do
28
+ expect(subject.password).to eq("foo")
29
+ end
30
+
31
+ it "loads the database from the config file" do
32
+ expect(subject.database).to eq(3)
33
+ end
34
+
35
+ it "loads the namespace from the config file" do
36
+ expect(subject.namespace).to eq("treecurve")
37
+ end
38
+ end
39
+
40
+ context "and the specified file doesn't exist" do
41
+ before do
42
+ SpecSupport::TimberlineYaml.load_constant_for_missing_file
43
+ end
44
+
45
+ after do
46
+ SpecSupport::TimberlineYaml.destroy_constant
47
+ end
48
+
49
+ it "raises an exception to let you know the file doesn't exist" do
50
+ expect { Timberline::Config.new }.to raise_error
51
+ end
52
+ end
53
+ end
54
+
55
+ context "if Rails.root is defined" do
56
+ context "and a config/timberline.yaml file exists" do
57
+ before do
58
+ SpecSupport::FakeRails.create_fake_env
59
+ end
60
+
61
+ after do
62
+ SpecSupport::FakeRails.destroy_fake_env
63
+ end
64
+
65
+ it "loads the host variable from the config file" do
66
+ expect(subject.host).to eq("localhost")
67
+ end
68
+
69
+ it "loads the port variable from the config file" do
70
+ expect(subject.port).to eq(12345)
71
+ end
72
+
73
+ it "loads the timeout variable from the config file" do
74
+ expect(subject.timeout).to eq(10)
75
+ end
76
+
77
+ it "loads the password from the config file" do
78
+ expect(subject.password).to eq("foo")
79
+ end
80
+
81
+ it "loads the database from the config file" do
82
+ expect(subject.database).to eq(3)
83
+ end
84
+
85
+ it "loads the namespace from the config file" do
86
+ expect(subject.namespace).to eq("treecurve")
87
+ end
88
+ end
89
+
90
+ context "and a config/timberline.yaml file does not exist" do
91
+ before do
92
+ SpecSupport::FakeRails.create_fake_env_without_config
93
+ end
94
+
95
+ after do
96
+ SpecSupport::FakeRails.destroy_fake_env
97
+ end
98
+
99
+ it "configures a default namespace of 'timberline'" do
100
+ expect(subject.namespace).to eq("timberline")
101
+ end
102
+
103
+ it "configures a default max_retries of 5" do
104
+ expect(subject.max_retries).to eq(5)
105
+ end
106
+
107
+ it "configures a stat_timeout of 60" do
108
+ expect(subject.stat_timeout).to eq(60)
109
+ end
110
+
111
+ it "doesn't configure any redis settings so that redis will use its own defaults" do
112
+ [:database, :host, :port, :timeout, :password, :logger].each do |value|
113
+ expect(subject.send(value)).to be_nil
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ context "when no configuration exists" do
120
+ it "configures a default namespace of 'timberline'" do
121
+ expect(subject.namespace).to eq("timberline")
122
+ end
123
+
124
+ it "configures a default max_retries of 5" do
125
+ expect(subject.max_retries).to eq(5)
126
+ end
127
+
128
+ it "configures a stat_timeout of 60" do
129
+ expect(subject.stat_timeout).to eq(60)
130
+ end
131
+
132
+ it "doesn't configure any redis settings so that redis will use its own defaults" do
133
+ [:database, :host, :port, :timeout, :password, :logger].each do |value|
134
+ expect(subject.send(value)).to be_nil
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ describe "#redis_config" do
141
+ subject { Timberline::Config.new }
142
+
143
+ it "returns a hash" do
144
+ expect(subject.redis_config).to be_a Hash
145
+ end
146
+
147
+ context "when only defaults have been configured" do
148
+ it "is empty" do
149
+ expect(subject.redis_config).to be_empty
150
+ end
151
+ end
152
+
153
+ context "when it has been fully configured" do
154
+ before do
155
+ subject.database = 1
156
+ subject.host = "localhost"
157
+ subject.port = 1234
158
+ subject.timeout = 15
159
+ subject.password = "fritters"
160
+ subject.logger = Logger.new(STDOUT)
161
+ end
162
+
163
+ it "includes the appropriate redis configuration keys" do
164
+ [:db, :host, :port, :timeout, :password, :logger].each do |key|
165
+ expect(subject.redis_config).to have_key key
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe Timberline::Envelope do
4
+ describe "newly created" do
5
+ it "has no contents" do
6
+ expect(subject.contents).to be_nil
7
+ end
8
+
9
+ it "has a metadata object" do
10
+ expect(subject.metadata).not_to be_nil
11
+ end
12
+
13
+ it "has an empty hash for metadata" do
14
+ expect(subject.metadata).to eq({})
15
+ end
16
+ end
17
+
18
+ describe "#to_s" do
19
+ context "When the envelope has no contents" do
20
+ before do
21
+ subject.contents = nil
22
+ end
23
+
24
+ it "raises a MissingContentException" do
25
+ expect { subject.to_s }.to raise_error(Timberline::MissingContentException)
26
+ end
27
+ end
28
+
29
+ context "When the envelope has contents" do
30
+ before do
31
+ subject.contents = "Wheeee!"
32
+ end
33
+
34
+ it "renders a JSON string of the envelope's data" do
35
+ expect(subject.to_s).to eq(JSON.unparse({contents: "Wheeee!"}))
36
+ end
37
+ end
38
+
39
+ context "When the envelope has contents and metadata" do
40
+ before do
41
+ subject.contents = "Wheeee!"
42
+ subject.fritters = "the bacon kind"
43
+ end
44
+
45
+ it "renders a JSON string of the envelope's data" do
46
+ expect(subject.to_s).to eq(JSON.unparse({contents: "Wheeee!", fritters: "the bacon kind"}))
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "#method_missing" do
52
+ context "When the missing method has an = at the end" do
53
+ before do
54
+ subject.fritters = "the bacon kind"
55
+ end
56
+
57
+ it "acts like a setter for the metadata object" do
58
+ expect(subject.metadata["fritters"]).to eq("the bacon kind")
59
+ end
60
+ end
61
+
62
+ context "When the missing method doesn't have an = at the end" do
63
+ before do
64
+ subject.metadata["fritters"] = "the bacon kind"
65
+ end
66
+
67
+ it "acts like a getter for the metadata object" do
68
+ expect(subject.fritters).to eq("the bacon kind")
69
+ end
70
+ end
71
+ end
72
+
73
+ describe ".from_json" do
74
+ let(:data_hash) { { contents: "hey guys!", test: true, fritters: "the bacon kind" } }
75
+ let(:json_string) { JSON.unparse(data_hash) }
76
+ let(:envelope) { Timberline::Envelope.from_json(json_string) }
77
+
78
+ it "creates a Timberline::Envelope" do
79
+ expect(envelope).to be_a Timberline::Envelope
80
+ end
81
+
82
+ it "correctly parses the contents" do
83
+ expect(envelope.contents).to eq(data_hash[:contents])
84
+ end
85
+
86
+ it "correctly parses the metadata" do
87
+ expect(envelope.test).to be true
88
+ expect(envelope.fritters).to eq "the bacon kind"
89
+ end
90
+ end
91
+ end