line_up 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2013 Bukowskis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # LineUp
2
+
3
+ With LineUp you can enqueue Resque jobs in arbitrary Redis namespaces.
4
+
5
+ [Resque](https://github.com/defunkt/resque) uses a [Set](http://redis.io/commands#set) and a [List](http://redis.io/commands#list) to keep track of all Queues and their Jobs.
6
+
7
+ * The Set is usually located at `resque:queues` and contains a list of (lower-cased, underscored) Strings, each representing a queue name
8
+ * Each queue is a List located at `resque:queue:my_job` (with `my_job` as queue name for `MyJob`-jobs in this example)
9
+ * Each job inside of a queue is a JSON or Marshal'ed Hash with the keys `class` and `args`, for example `{ class: 'MyJob', args: [123, some: thing] }.to_json`
10
+
11
+ Depending on how you configure the Redis backend for Resque, you will end up in a different namespace:
12
+
13
+ * If you use `Resque.redis = Redis::Namespace.new(:bob, ...)`, Resque [detects](https://github.com/defunkt/resque/blob/master/lib/resque.rb#L55) that you passed in an `Redis::Namespace` object and will __not__ add any additional namespace. So the queue Set in this example will be located at `bob:queues`
14
+ * Any other `Redis.new`-compatible object will get the `resque`-namespace added. So `Resque.redis = Redis.new(...)` will cause the queue Set to be located at `resque:queues`
15
+
16
+ If you use multiple applications, you should make sure that each of them has its own namespace. You would normally achieve that with `Resque.redis = Redis::Namespace.new('myapp:resque', ...)` so that the queue Set would be located at `myapp:resque:queues`.
17
+
18
+ So far so good, __but__ there is no way to enqueue a Job for an application from inside another namespace, say `otherapp:resque:queues`, without maintaining an additional connection to Redis in the other app's namespace. So far, the only solution has been to share the `resque:queues` namespace between all applications and have separate queue-names, such as `myapp-myjob` and `otherapp-myjob`, but that is not really separating namespaces.
19
+
20
+ That's where LineUp comes in, it doesn't even need Resque. It goes right into Redis (scary huh?), just as Resque does [internally](https://github.com/defunkt/resque/blob/master/lib/resque/queue.rb).
21
+
22
+ # Examples
23
+
24
+ ### Setup
25
+
26
+ If you use the `Raidis` gem, you _do not need_ any setup. Otherwise, a manual setup would look like this:
27
+
28
+ ```ruby
29
+ redis = Redis::Namespace.new 'myapp:resque', redis: Redis.new(...)
30
+
31
+ Resque.redis = redis
32
+ LineUp.redis = redis
33
+ ````
34
+
35
+ ### Usage
36
+
37
+ With the setup above, Resque lies in the `myapp:resque`-namespace. So you can enqeueue jobs to the very same application by using `Resque.enqueue(...)`.
38
+
39
+ This is how you can enqueue a job for another applications:
40
+
41
+ ```ruby
42
+ if LineUp.push :otherApp, :SomeJob, 12345, some: thing
43
+ # Yey, everything went well
44
+ else
45
+ # The "Trouble"-gem, has been notified and I can process the failure if I like
46
+ end
47
+ ```
48
+
49
+ This will enqueue to `other_app:resque:some_job` with arguments `[12345, { 'some' => 'thing' }]` and make sure that the `other_app:resque:queues` Set references the queue List.
50
+
51
+ # Gotchas
52
+
53
+ * `Resque.redis` MUST respond to a method called `#namespace` which takes a block and yields a new namespace. See [this commit](https://github.com/defunkt/redis-namespace/pull/50). Currently LineUp [requires a non-rubygems fork](https://github.com/bukowskis/line_up/blob/master/Gemfile) of `Redis::Namespace` in order to be able to use this cutting-edge method.
54
+ * Currently the jobs are encoded using `MultiJson` only, not `Marshal`, feel free to commit a patch if you need the latter
55
+ * You cannot share the `resque` root namespace. LineUp defaults to the `application:resque` namespace (because that's the only scenario I can think of that would make you want to use LineUp in the first place :)
@@ -0,0 +1,49 @@
1
+ require 'logger'
2
+
3
+ module LineUp
4
+ class Configuration
5
+ attr_accessor :logger, :redis
6
+
7
+ def initialize(options={})
8
+ @logger = options[:logger] || default_logger
9
+ @redis = options[:redis] || default_redis
10
+ end
11
+
12
+ private
13
+
14
+ def default_logger
15
+ if defined?(Rails)
16
+ Rails.logger
17
+ else
18
+ Logger.new(STDOUT)
19
+ end
20
+ end
21
+
22
+ def default_redis
23
+ return Raidis.redis if defined?(Raidis)
24
+ return Resque.redis if defined?(Resque)
25
+ Redis::Namespace.new nil
26
+ end
27
+ end
28
+ end
29
+
30
+ module LineUp
31
+
32
+ # Public: Returns the the Configuration instance.
33
+ #
34
+ def self.config
35
+ @config ||= Configuration.new
36
+ end
37
+
38
+ # Public: Yields the Configuration instance.
39
+ #
40
+ def self.configure(&block)
41
+ yield config
42
+ end
43
+
44
+ # Public: Reset the Configuration (useful for testing)
45
+ #
46
+ def self.reset!
47
+ @config = nil
48
+ end
49
+ end
@@ -0,0 +1,23 @@
1
+ require 'line_up/string_extensions'
2
+ require 'multi_json'
3
+
4
+ module LineUp
5
+ class Job
6
+
7
+ attr_reader :klass, :args
8
+
9
+ def initialize(klass, *args)
10
+ @klass = klass
11
+ @args = args
12
+ end
13
+
14
+ def encode
15
+ MultiJson.dump class: klass.to_s, args: args
16
+ end
17
+
18
+ def queue_name
19
+ StringExtensions.underscore(klass)
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ module LineUp
2
+ class StringExtensions
3
+
4
+ # See https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L90
5
+ def self.underscore(word)
6
+ word = word.to_s
7
+ word.gsub!(/::/, '/')
8
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
9
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
10
+ word.tr!("-", "_")
11
+ word.downcase!
12
+ word
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module LineUp
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 2
6
+
7
+ STRING = [MAJOR, MINOR, TINY].compact.join('.')
8
+ end
9
+ end
data/lib/line_up.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'trouble'
2
+
3
+ require 'line_up/configuration'
4
+ require 'line_up/job'
5
+
6
+ module LineUp
7
+ RedisNotConfiguredError = Class.new(RuntimeError)
8
+
9
+ def self.push(application, jobclass, *args)
10
+ redis_for application do |redis|
11
+ job = Job.new jobclass, *args
12
+ redis.sadd 'queues', job.queue_name
13
+ redis.rpush "queue:#{job.queue_name}", job.encode
14
+ end
15
+ log caller, application, jobclass, *args
16
+ true
17
+ rescue Exception => exception
18
+ Trouble.notify exception, caller: caller[1], message: "LineUp could not enqueue a Job", code: :enqueue_failed, redis: config.redis.inspect, application: application.inspect, job: jobclass.inspect, args: args.inspect
19
+ false
20
+ end
21
+
22
+ private
23
+
24
+ def self.redis_for(application, &block)
25
+ config.redis.namespace "#{StringExtensions.underscore(application)}:resque", &block
26
+ end
27
+
28
+ def self.log(caller, application, jobclass, *args)
29
+ return unless config.logger
30
+ rows = ['LINEUP ENQUEUED A JOB']
31
+ rows << " | Location: #{caller.first}"
32
+ rows << " | Application: #{application.inspect}"
33
+ rows << " | Job Class: #{jobclass.inspect}"
34
+ rows << " \\ Arguments: #{args.inspect}\n"
35
+ config.logger.debug rows.join("\n")
36
+ end
37
+
38
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ describe LineUp do
4
+
5
+ let(:application) { :otherApp }
6
+ let(:job) { :SendEmail }
7
+ let(:args) { [123, some: :thing] }
8
+ let(:redis) { $raw_redis }
9
+ let(:logger) { mock(:logger) }
10
+
11
+ let(:lineup) { LineUp }
12
+
13
+ describe '.push' do
14
+ it 'returns true if successful' do
15
+ lineup.push(application, job, *args).should be_true
16
+ end
17
+
18
+ it 'registers the queue' do
19
+ lineup.push application, job, *args
20
+ queues = redis.smembers('other_app:resque:queues').should == %w{ send_email }
21
+ end
22
+
23
+ it 'enqueues the job' do
24
+ lineup.push application, job, *args
25
+ jobs = redis.lrange('other_app:resque:queue:send_email', 0, -1)
26
+ jobs.size.should == 1
27
+ MultiJson.load(jobs.first).should == { 'class' => 'SendEmail', 'args' => [123, 'some' => 'thing'] }
28
+ end
29
+
30
+ context 'with a Logger' do
31
+ before do
32
+ lineup.config.logger = logger
33
+ end
34
+
35
+ it 'logs the enqueueing and returns true' do
36
+ logger.should_receive(:debug) do |string|
37
+ string.should include('LINEUP ENQUEUED')
38
+ string.should include('line_up_spec.rb')
39
+ string.should include(':otherApp')
40
+ string.should include(':SendEmail')
41
+ string.should include('[123, {:some=>:thing}]')
42
+ end
43
+ lineup.push(application, job, *args).should be_true
44
+ end
45
+ end
46
+
47
+ context 'when the key for the Queue Set is occupied by the wrong data format' do
48
+ before do
49
+ redis.set 'other_app:resque:queues', :anything_but_a_list
50
+ end
51
+
52
+ it 'catches the error and returns false' do
53
+ Trouble.should_receive(:notify) do |exception, metadata|
54
+ exception.should be_instance_of Redis::CommandError
55
+ metadata[:code].should == :enqueue_failed
56
+ metadata[:application].should == ':otherApp'
57
+ metadata[:job].should == ':SendEmail'
58
+ metadata[:args].should == '[123, {:some=>:thing}]'
59
+ metadata[:caller].should include('line_up_spec.rb')
60
+ end
61
+ lineup.push(application, job, *args).should be_false
62
+ end
63
+ end
64
+
65
+ context 'when the key for the List Job Queue is occupied by the wrong data format' do
66
+ before do
67
+ redis.set 'other_app:resque:queue:send_email', :anything_but_a_list
68
+ end
69
+
70
+ it 'catches the error and returns false' do
71
+ Trouble.should_receive(:notify) do |exception, metadata|
72
+ exception.should be_instance_of Redis::CommandError
73
+ metadata[:code].should == :enqueue_failed
74
+ metadata[:application].should == ':otherApp'
75
+ metadata[:job].should == ':SendEmail'
76
+ metadata[:args].should == '[123, {:some=>:thing}]'
77
+ metadata[:caller].should include('line_up_spec.rb')
78
+ end
79
+ lineup.push(application, job, *args).should be_false
80
+ end
81
+ end
82
+ end
83
+
84
+ describe '.config' do
85
+ before do
86
+ LineUp.reset!
87
+ end
88
+
89
+ it 'is an STDOUT logger' do
90
+ Logger.should_receive(:new).with(STDOUT).and_return logger
91
+ lineup.config.logger.should be logger
92
+ end
93
+
94
+ context 'with Rails' do
95
+ before do
96
+ ensure_module :Rails
97
+ Rails.stub!(:logger).and_return(logger)
98
+ end
99
+
100
+ it 'is the Rails logger' do
101
+ lineup.config.logger.should be Rails.logger
102
+ end
103
+ end
104
+ end
105
+
106
+ end
@@ -0,0 +1,40 @@
1
+ require 'redis-namespace'
2
+ require 'line_up'
3
+
4
+ def ensure_class_or_module(full_name, class_or_module)
5
+ full_name.to_s.split(/::/).inject(Object) do |context, name|
6
+ begin
7
+ context.const_get(name)
8
+ rescue NameError
9
+ if class_or_module == :class
10
+ context.const_set(name, Class.new)
11
+ else
12
+ context.const_set(name, Module.new)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def ensure_module(name)
19
+ ensure_class_or_module(name, :module)
20
+ end
21
+
22
+ def ensure_class(name)
23
+ ensure_class_or_module(name, :class)
24
+ end
25
+
26
+ RSpec.configure do |config|
27
+
28
+ config.before do
29
+ $raw_redis = Redis.new(db: 14)
30
+ LineUp.config.redis = Redis::Namespace.new :myapp, redis: $raw_redis
31
+ LineUp.config.logger = nil
32
+ Trouble.stub!(:notify)
33
+ end
34
+
35
+ config.after do
36
+ $raw_redis.flushdb
37
+ LineUp.reset!
38
+ end
39
+
40
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: line_up
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - bukowskis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: trouble
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: multi_json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: redis-namespace
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: guard-rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rb-fsevent
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: No more need to maintain two separate redis connections when using namespaces.
111
+ LineUp does not even need Resque itself.
112
+ email:
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - lib/line_up/configuration.rb
118
+ - lib/line_up/job.rb
119
+ - lib/line_up/string_extensions.rb
120
+ - lib/line_up/version.rb
121
+ - lib/line_up.rb
122
+ - spec/lib/line_up_spec.rb
123
+ - spec/spec_helper.rb
124
+ - README.md
125
+ - LICENSE
126
+ homepage: https://github.com/bukowskis/line_up
127
+ licenses:
128
+ - MIT
129
+ post_install_message:
130
+ rdoc_options:
131
+ - --encoding
132
+ - UTF-8
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ! '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ none: false
143
+ requirements:
144
+ - - ! '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubyforge_project:
149
+ rubygems_version: 1.8.23
150
+ signing_key:
151
+ specification_version: 3
152
+ summary: Enqueue Resque Jobs directly via Redis so that you can choose the namespace
153
+ yourself
154
+ test_files: []