line_up 0.0.2

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.
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: []