rubykiq 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YWIyYTZlNTdiMjRmZDEyMmM0ZDAyZjAxNDg0MThjMzhkZWJhMGEzYQ==
5
+ data.tar.gz: !binary |-
6
+ NmFjNWNjY2MzNjBlZTFjYTRiN2ZhYTc1N2EwMGExMzJmYmY3NTdjOQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YjA2NmU0YjZhMzZmM2M4MTc1YWYyOTg1NmYxNGUyNWMzMGRkMDVmNzMzNDQw
10
+ NmZiZGM4NmViMGI0YjMzOGRmOWIyMTNjYjczMmViMjYzNTJiODQ1YzY3MDU0
11
+ YzUzYTc3Nzg1MzJhZGY2ZDViMzNjM2RlOWNkOGFlMzI4Y2M0ZjU=
12
+ data.tar.gz: !binary |-
13
+ NzE1NTkwODZmMzU0YWU1N2I3MmQzZjliMzVhZThkM2QzNTMzMTEyNmJhOThh
14
+ MTUwYzE2NDgyNjQxYjNiNWRjZDRiMmQxMTMyOTI5ZWE5Y2UyMzJmMGQ2OTNk
15
+ YWZlN2FiYzMwOTg1MWIyM2UxMjY0YzE4ODM5M2I1Y2RjMDBmYTY=
data/.document ADDED
@@ -0,0 +1,3 @@
1
+ LICENSE.md
2
+ README.md
3
+ lib/**/*.rb
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+ *.lock
15
+
16
+ # YARD artifacts
17
+ .yardoc
18
+ _yardoc
19
+ doc/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --order random
3
+ --format RSpec::Smart::Formatter
data/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ bundler_args: --without development
2
+ language: ruby
3
+ rvm:
4
+ - rbx-18mode
5
+ - rbx-19mode
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+ - 1.8.7
9
+ - 1.9.2
10
+ - 1.9.3
11
+ - 2.0.0
12
+ services:
13
+ - redis-server
14
+ matrix:
15
+ allow_failures:
16
+ - rvm: rbx-18mode
17
+ - rvm: jruby-18mode
18
+ - rvm: 1.8.7
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --markup markdown
2
+ -
3
+ CHANGELOG.md
4
+ CONTRIBUTING.md
5
+ LICENSE.md
6
+ README.md
data/CHANGELOG.md ADDED
File without changes
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,46 @@
1
+ ## Contributing
2
+ In the spirit of [free software][free-sw], **everyone** is encouraged to help
3
+ improve this project.
4
+
5
+ [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html
6
+
7
+ Here are some ways *you* can contribute:
8
+
9
+ * by using alpha, beta, and prerelease versions
10
+ * by reporting bugs
11
+ * by suggesting new features
12
+ * by writing or editing documentation
13
+ * by writing specifications
14
+ * by writing code (**no patch is too small**: fix typos, add comments, clean up
15
+ inconsistent whitespace)
16
+ * by refactoring code
17
+ * by closing [issues][]
18
+ * by reviewing patches
19
+
20
+ [issues]: https://github.com/karlfreeman/rubykiq/issues
21
+
22
+ ## Submitting an Issue
23
+ We use the [GitHub issue tracker][issues] to track bugs and features. Before
24
+ submitting a bug report or feature request, check to make sure it hasn't
25
+ already been submitted. When submitting a bug report, please include a [Gist][]
26
+ that includes a stack trace and any details that may be necessary to reproduce
27
+ the bug, including your gem version, Ruby version, and operating system.
28
+ Ideally, a bug report should include a pull request with failing specs.
29
+
30
+ [gist]: https://gist.github.com/
31
+
32
+ ## Submitting a Pull Request
33
+ 1. [Fork the repository.][fork]
34
+ 2. [Create a topic branch.][branch]
35
+ 3. Add specs for your unimplemented feature or bug fix.
36
+ 4. Run `bundle exec rake spec`. If your specs pass, return to step 3.
37
+ 5. Implement your feature or bug fix.
38
+ 6. Run `bundle exec rake spec`. If your specs fail, return to step 5.
39
+ 7. Run `open coverage/index.html`. If your changes are not completely covered
40
+ by your tests, return to step 3.
41
+ 8. Add, commit, and push your changes.
42
+ 9. [Submit a pull request.][pr]
43
+
44
+ [fork]: http://help.github.com/fork-a-repo/
45
+ [branch]: http://learn.github.com/p/branching.html
46
+ [pr]: http://help.github.com/send-pull-requests/
data/Gemfile ADDED
@@ -0,0 +1,33 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rake", ">= 1.2"
4
+ gem "yard"
5
+
6
+ # platforms :ruby_18 do
7
+ # end
8
+ # platforms :ruby, :mswin, :mingw do
9
+ # end
10
+ # platforms :jruby do
11
+ # end
12
+
13
+ gem "hiredis", "~> 0.4.5", :require => false
14
+ gem "em-synchrony", :require => false
15
+
16
+ group :development do
17
+ gem "kramdown", ">= 0.14"
18
+ gem "pry"
19
+ gem "pry-debugger", :platforms => :mri_19
20
+ gem "awesome_print"
21
+ end
22
+
23
+ group :test do
24
+ gem "rspec"
25
+ gem "rspec-smart-formatter"
26
+ gem "vcr"
27
+ gem "timecop"
28
+ gem "simplecov", :require => false
29
+ gem "coveralls", :require => false
30
+ gem "cane"
31
+ end
32
+
33
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Karl Freeman
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Rubykiq ( WIP ) [![Gem Version](https://badge.fury.io/rb/rubykiq.png)][gem] [![Build Status](https://travis-ci.org/karlfreeman/rubykiq.png?branch=master)][travis] [![Dependency Status](https://gemnasium.com/karlfreeman/rubykiq.png?travis)][gemnasium] [![Coverage Status](https://coveralls.io/repos/karlfreeman/rubykiq/badge.png?branch=master)][coveralls] [![Code Climate](https://codeclimate.com/github/karlfreeman/rubykiq.png)][codeclimate]
2
+
3
+ [Sidekiq] agnostic enqueuing using Redis.
4
+
5
+ [gem]: https://rubygems.org/gems/rubykiq
6
+ [travis]: http://travis-ci.org/karlfreeman/rubykiq
7
+ [gemnasium]: https://gemnasium.com/karlfreeman/rubykiq
8
+ [coveralls]: https://coveralls.io/r/karlfreeman/rubykiq
9
+ [codeclimate]: https://codeclimate.com/github/karlfreeman/rubykiq
10
+
11
+ [sidekiq]: http://mperham.github.com/sidekiq/
12
+
13
+ Sidekiq is a fantastic message processing library which has a simple and stable message format. Rubykiq aims to be a portable library to push jobs in to Sidekiq with as little overhead as possible Whilst having feature parity on Sidekiq::Client's conventions.
14
+
15
+ ## Features / Usage Examples
16
+
17
+ * [Redis][] has support for [alternative drivers](https://github.com/redis/redis-rb#alternate-drivers), Rubykiq is tested with these in mind. ( eg :synchrony )
18
+ * The `:at` parameter supports `Time`, `Date` and any `Time.parse`-able strings.
19
+ * Pushing multiple and singular jobs has the same interface, simply nest args.
20
+ * Slightly less gem dependecies, and by that I mean Sidekiq::Client without Celluloid ( which is already very light! )
21
+
22
+ [redis]: https://github.com/redis/redis-rb
23
+
24
+ ```ruby
25
+ require "rubykiq"
26
+
27
+ # will also detect REDIS_URL, REDIS_PROVIDER and REDISTOGO_URL ENV variables
28
+ Rubykiq.url = "redis://127.0.0.1:6379"
29
+
30
+ # alternative driver support ( :ruby, :hiredis, :synchrony )
31
+ Rubykiq.driver = :synchrony
32
+
33
+ # defaults to nil
34
+ Rubykiq.namespace = "background"
35
+
36
+ # uses "default" queue unless specified
37
+ Rubykiq.push(:class => "Worker", :args => ["foo", 1, :bat => "bar"])
38
+
39
+ # args are optionally set to empty
40
+ Rubykiq.push(:class => "Scheduler", :queue => "scheduler")
41
+
42
+ # will batch up multiple jobs
43
+ Rubykiq.push(:class => "Worker", :args => [["foo"], ["bar"]])
44
+
45
+ # at param can be a "Time", "Date" or any "Time.parse"-able strings
46
+ Rubykiq.push(:class => "DelayedHourMailer", :at => Time.now + 3600)
47
+ Rubykiq.push(:class => "DelayedDayMailer", :at => DateTime.now.next_day)
48
+ Rubykiq.push(:class => "DelayedMailer", :at => "2013-01-01T09:00:00Z")
49
+
50
+ # alias based sugar
51
+ job = { :class => "Worker" }
52
+ Rubykiq << job
53
+ ```
54
+ __It's advised that using [Sidekiq::Client's push] method when already a dependency is going to be better in most everyday cases__
55
+
56
+ [sidekiq::client's push]: https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/client.rb#L36
57
+
58
+ ## Supported Ruby Versions
59
+ This library aims to support and is [tested against][travis] the following Ruby
60
+ implementations:
61
+
62
+ * Ruby 1.9.2 (drivers: ruby, hiredis, synchrony)
63
+ * Ruby 1.9.3 (drivers: ruby, hiredis, synchrony)
64
+ * Ruby 2.0.0 (drivers: ruby, hiredis, synchrony)
65
+ * [JRuby][] (drivers: ruby)
66
+ * [Rubinius][] (drivers: ruby)
67
+
68
+ [jruby]: http://www.jruby.org/
69
+ [rubinius]: http://rubini.us/
70
+
71
+ # Credits
72
+
73
+ Inspiration:
74
+
75
+ - [Michael Grosser's Enqueue into Sidkiq post](http://grosser.it/2013/01/17/enqueue-into-sidekiq-via-pure-redis-without-loading-sidekiq/)
76
+
77
+ Cribbed:
78
+
79
+ - [Sidekiq's internal client class](https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/client.rb)
80
+ - [Sidekiq's internal redis class](https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/redis_connection.rb)
81
+ - [Sidekiq's FAQ](https://github.com/mperham/sidekiq/wiki/FAQ)
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require "yard"
6
+ YARD::Rake::YardocTask.new
7
+
8
+ require "rspec/core/rake_task"
9
+ desc "Run all examples"
10
+ RSpec::Core::RakeTask.new(:spec)
11
+
12
+ begin
13
+ require "cane/rake_task"
14
+ namespace :metric do
15
+ desc "Analyze for code quality"
16
+ Cane::RakeTask.new(:quality) do |cane|
17
+ cane.abc_max = 27
18
+ cane.no_doc = true
19
+ cane.style_measure = 200
20
+ end
21
+ end
22
+ rescue LoadError
23
+ warn "Cane is not available, metric:quality task not provided."
24
+ end
25
+
26
+ task :default => :spec
27
+ task :test => :spec
@@ -0,0 +1,213 @@
1
+ require 'connection_pool'
2
+ require 'securerandom'
3
+ require 'multi_json'
4
+ require 'time'
5
+
6
+ module Rubykiq
7
+
8
+ class Client
9
+
10
+ # An array of valid keys in the options hash when configuring a `Rubykiq::Client`
11
+ VALID_OPTIONS_KEYS = [
12
+ :redis_pool_size,
13
+ :redis_pool_timeout,
14
+ :url,
15
+ :namespace,
16
+ :driver,
17
+ :retry,
18
+ :queue
19
+ ].freeze
20
+
21
+ # A hash of valid options and their default values
22
+ DEFAULT_OPTIONS = {
23
+ :redis_pool_size => 1,
24
+ :redis_pool_timeout => 1,
25
+ :url => nil,
26
+ :namespace => nil,
27
+ :driver => :ruby,
28
+ :retry => true,
29
+ :queue => "default"
30
+ }.freeze
31
+
32
+ # Bang open the valid options
33
+ attr_accessor(*VALID_OPTIONS_KEYS)
34
+
35
+ # Initialize a new Client object
36
+ #
37
+ # @param options [Hash]
38
+ def initialize(options = {})
39
+ reset_options
40
+ options.each_pair do |key, value|
41
+ send("#{key}=", value) if VALID_OPTIONS_KEYS.include?(key)
42
+ end
43
+ end
44
+
45
+ # Fetch the ::ConnectionPool of Rubykiq::Connections
46
+ #
47
+ # @return [::ConnectionPool]
48
+ def connection_pool(options={}, &block)
49
+ options = valid_options.merge(options)
50
+
51
+ @connection_pool ||= ::ConnectionPool.new(:timeout => redis_pool_timeout, :size => redis_pool_size) do
52
+ Rubykiq::Connection.new(options)
53
+ end
54
+
55
+ if block_given?
56
+ @connection_pool.with(&block)
57
+ else
58
+ return @connection_pool
59
+ end
60
+
61
+ end
62
+
63
+ # Push a Sidekiq job to Redis. Accepts a number of options:
64
+ #
65
+ # :class - the worker class to call, required.
66
+ # :queue - the named queue to use, optional ( default: "default" )
67
+ # :args - an array of simple arguments to the perform method, must be JSON-serializable, optional ( default: [] )
68
+ # :retry - whether to retry this job if it fails, true or false, default true, optional ( default: true )
69
+ # :at - when the job should be executed. This can be a `Time`, `Date` or any `Time.parse`-able strings, optional.
70
+ #
71
+ # Returns nil if not pushed to Redis. In the case of an indvidual job a job ID will be returned,
72
+ # if multiple jobs are pushed the size of the jobs will be returned
73
+ #
74
+ # Example:
75
+ # Rubykiq.push(:class => "Worker", :args => ["foo", 1, :bat => "bar"])
76
+ # Rubykiq.push(:class => "Scheduler", :queue => "scheduler")
77
+ # Rubykiq.push(:class => "DelayedMailer", :at => "2013-01-01T09:00:00Z")
78
+ # Rubykiq.push(:class => "Worker", :args => [["foo"], ["bar"]])
79
+ #
80
+ # @param items [Array]
81
+ def push(items)
82
+ raise(ArgumentError, "Message must be a Hash") unless items.is_a?(Hash)
83
+ raise(ArgumentError, "Message args must be an Array") if items[:args] && !items[:args].is_a?(Array)
84
+
85
+ # args are optional
86
+ items[:args] ||= []
87
+
88
+ # determine if this items arg's is a nested array
89
+ items[:args].first.is_a?(Array) ? push_many(items) : push_one(items)
90
+
91
+ end
92
+ alias_method :<<, :push
93
+
94
+
95
+ private
96
+
97
+ # Create a hash of options and their values
98
+ def valid_options
99
+ VALID_OPTIONS_KEYS.inject({}){|o,k| o.merge!(k => send(k)) }
100
+ end
101
+
102
+ # Create a hash of the default options and their values
103
+ def default_options
104
+ DEFAULT_OPTIONS
105
+ end
106
+
107
+ # Set the VALID_OPTIONS_KEYS with their DEFAULT_OPTIONS
108
+ def reset_options
109
+ VALID_OPTIONS_KEYS.each do |key|
110
+ send("#{key}=", default_options[key])
111
+ end
112
+ end
113
+
114
+ # when only one item is needed to persisted to redis
115
+ def push_one(item)
116
+
117
+ # we're expecting item to be a single item so simply normalize it
118
+ payload = normalize_item(item)
119
+
120
+ # if successfully persisted to redis return this item's `jid`
121
+ pushed = false
122
+ pushed = raw_push([payload]) if payload
123
+ pushed ? payload[:jid] : nil
124
+
125
+ end
126
+
127
+ # when multiple item's are needing to be persisted to redis
128
+ def push_many(items)
129
+
130
+ # we're expecting items to have an nested array of args, lets take each one and correctly normalize them
131
+ payloads = items[:args].map do |args|
132
+ raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[:foo => 'bar'], [:foo => 'foo']]" unless args.is_a?(Array)
133
+ # clone the original items ( for :queue, :class, etc.. )
134
+ item = items.clone
135
+ # merge this item's args ( eg the nested `arg` array )
136
+ item.merge!(:args => args) unless args.empty?
137
+ # normalize this individual item
138
+ item = normalize_item(item)
139
+ end.compact
140
+
141
+ # if successfully persisted to redis return the size of the jobs
142
+ pushed = false
143
+ pushed = raw_push(payloads) unless payloads.empty?
144
+ pushed ? payloads.size : nil
145
+
146
+ end
147
+
148
+ # persist the job message(s)
149
+ def raw_push(payloads)
150
+ pushed = false
151
+ Rubykiq.connection_pool do |connection|
152
+ if payloads.first[:at]
153
+ pushed = connection.zadd("schedule", payloads.map {|item| [ item[:at].to_s, ::MultiJson.encode(item) ]})
154
+ else
155
+ q = payloads.first[:queue]
156
+ to_push = payloads.map { |item| ::MultiJson.encode(item) }
157
+ _, pushed = connection.multi do
158
+ connection.sadd("queues", q)
159
+ connection.lpush("queue:#{q}", to_push)
160
+ end
161
+ end
162
+ end
163
+ pushed
164
+ end
165
+
166
+ #
167
+ def normalize_item(item)
168
+ raise(ArgumentError, "Message must be a Hash") unless item.is_a?(Hash)
169
+ raise(ArgumentError, "Message must include a class and set of arguments: #{item.inspect}") if !item[:class] || !item[:args]
170
+ raise(ArgumentError, "Message args must be an Array") if item[:args] && !item[:args].is_a?(Array)
171
+ raise(ArgumentError, "Message class must be a String representation of the class name") unless item[:class].is_a?(String)
172
+
173
+ # normalize the time
174
+ item[:at] = normalize_time(item[:at]) if item[:at]
175
+ pre_normalized_item = item.clone
176
+
177
+ # args are optional
178
+ pre_normalized_item[:args] ||= []
179
+
180
+ # apply the default options
181
+ [:retry, :queue].each do |key|
182
+ pre_normalized_item[key] = send("#{key}") unless pre_normalized_item.has_key?(key)
183
+ end
184
+
185
+ # provide a job ID
186
+ pre_normalized_item[:jid] = ::SecureRandom.hex(12)
187
+
188
+ return pre_normalized_item
189
+
190
+ end
191
+
192
+ # Given an object meant to represent time, try to convert it intelligently to a float
193
+ def normalize_time(time)
194
+
195
+ # if the time param is a `Date` / `String` convert it to a `Time` object
196
+ if time.is_a?(Date)
197
+ normalized_time = time.to_time
198
+ elsif time.is_a?(String)
199
+ normalized_time = Time.parse(time)
200
+ else
201
+ normalized_time = time
202
+ end
203
+
204
+ # convert the `Time` object to a float ( if necessary )
205
+ normalized_time = normalized_time.to_f unless normalized_time.is_a?(Numeric)
206
+
207
+ return normalized_time
208
+
209
+ end
210
+
211
+ end
212
+
213
+ end
@@ -0,0 +1,38 @@
1
+ require 'redis'
2
+ require 'redis/namespace'
3
+
4
+ module Rubykiq
5
+
6
+ class Connection
7
+
8
+ extend Forwardable
9
+ def_delegators :@redis_connection, :multi, :namespace, :sadd, :zadd, :lpush, :lpop, :lrange, :llen, :zcard, :zrange, :flushdb
10
+ def_delegators :@redis_client, :host, :port, :db, :password
11
+
12
+ # Initialize a new Connection object
13
+ #
14
+ # @param options [Hash]
15
+ def initialize(options = {})
16
+ url = options.delete(:url) { determine_redis_provider }
17
+ namespace = options.delete(:namespace)
18
+ driver = options.delete(:driver)
19
+ @redis_connection ||= build_conection(url, namespace, driver)
20
+ @redis_client ||= @redis_connection.client
21
+ return @redis_connection
22
+ end
23
+
24
+ private
25
+
26
+ # lets try and fallback to another redis url
27
+ def determine_redis_provider
28
+ ENV["REDISTOGO_URL"] || ENV["REDIS_PROVIDER"] || ENV["REDIS_URL"] || "redis://localhost:6379/0"
29
+ end
30
+
31
+ # construct a namespaced redis connection
32
+ def build_conection(url, namespace, driver)
33
+ client = ::Redis.connect(:url => url, :driver => driver)
34
+ return ::Redis::Namespace.new(namespace, :redis => client)
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ module Rubykiq
2
+ VERSION = "0.0.1" unless defined?(Rubykiq::VERSION)
3
+ end
data/lib/rubykiq.rb ADDED
@@ -0,0 +1,23 @@
1
+ require "forwardable"
2
+ require "rubykiq/client"
3
+ require "rubykiq/connection"
4
+
5
+ module Rubykiq
6
+ extend SingleForwardable
7
+
8
+ def_delegators :client, :<<, :push, :connection_pool
9
+
10
+ # delegate all VALID_OPTIONS_KEYS accessors to the client
11
+ def_delegators :client, *Rubykiq::Client::VALID_OPTIONS_KEYS
12
+
13
+ # delegate all VALID_OPTIONS_KEYS setters to the client ( Hacky I know... )
14
+ def_delegators :client, *(Rubykiq::Client::VALID_OPTIONS_KEYS.dup.collect! do |key| "#{key}=".to_sym; end)
15
+
16
+ # Fetch the Rubykiq::Client
17
+ #
18
+ # @return [Rubykiq::Client]
19
+ def self.client(options={})
20
+ @client ||= Rubykiq::Client.new(options)
21
+ end
22
+
23
+ end
data/rubykiq.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rubykiq/version"
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.add_dependency "redis", "~> 3.0"
7
+ gem.add_dependency "redis-namespace", "~> 1.0"
8
+ gem.add_dependency "multi_json", "~> 1.0"
9
+ gem.add_dependency "connection_pool", "~> 1.0"
10
+ gem.add_development_dependency "bundler", "~> 1.0"
11
+ gem.name = "rubykiq"
12
+ gem.version = Rubykiq::VERSION
13
+ gem.authors = ["Karl Freeman"]
14
+ gem.email = ["karlfreeman@gmail.com"]
15
+ gem.license = "MIT"
16
+ gem.description = %q{Sidekiq agnostic enqueuing using Redis}
17
+ gem.summary = %q{Sidekiq agnostic enqueuing using Redis}
18
+ gem.homepage = "https://github.com/karlfreeman/rubykiq"
19
+ gem.files = `git ls-files`.split("\n")
20
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ gem.require_paths = ["lib"]
22
+ gem.required_ruby_version = ">= 1.9.2"
23
+ end
@@ -0,0 +1,41 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
2
+
3
+ require "bundler"
4
+ Bundler.setup
5
+ begin; require "awesome_print"; rescue LoadError; end
6
+
7
+ require "rspec"
8
+
9
+ require "support/pry"
10
+ require "support/timecop"
11
+ require "support/simplecov"
12
+
13
+ require "rubykiq"
14
+
15
+ # used as a stupid mixin class
16
+ class DummyClass
17
+ end
18
+
19
+ #
20
+ RSpec.configure do |config|
21
+ config.expect_with :rspec do |c|
22
+ c.syntax = :expect
23
+ end
24
+ end
25
+
26
+ #
27
+ def jruby?
28
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
29
+ end
30
+
31
+ #
32
+ def wrap_in_synchrony?(driver)
33
+
34
+ yield unless driver == :synchrony
35
+
36
+ EM.synchrony do
37
+ yield if block_given?
38
+ EM.stop
39
+ end
40
+
41
+ end
@@ -0,0 +1,3 @@
1
+ if !ENV["CI"] && defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
2
+ require "pry"
3
+ end
@@ -0,0 +1,15 @@
1
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
2
+
3
+ require "simplecov"
4
+
5
+ if ENV["CI"]
6
+ require "coveralls"
7
+ Coveralls.wear!
8
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
9
+ end
10
+
11
+ SimpleCov.start do
12
+ add_filter "vendor"
13
+ end
14
+
15
+ end
@@ -0,0 +1,2 @@
1
+ require "timecop"
2
+ Timecop.freeze
@@ -0,0 +1,141 @@
1
+ require "spec_helper"
2
+ require "hiredis"
3
+ require "em-synchrony"
4
+
5
+ describe Rubykiq::Client do
6
+
7
+ before(:all) do
8
+ Timecop.freeze
9
+ end
10
+
11
+ after(:all) do
12
+ Timecop.return
13
+ end
14
+
15
+ let (:ruby_client) { Rubykiq::Client.new }
16
+ let (:hiredis_client) { Rubykiq::Client.new(:driver => :hiredis) }
17
+ let (:synchrony_client) { Rubykiq::Client.new(:driver => :synchrony) }
18
+
19
+ # eg with a variety of drivers
20
+ [:ruby, :hiredis, :synchrony].each do |driver|
21
+
22
+ # skip incompatible drivers when running in JRuby
23
+ next if jruby? && (driver == :hiredis || :synchrony)
24
+
25
+ context "with #{driver}" do
26
+
27
+ # make sure the let is the current client being tested
28
+ let(:client) { self.send("#{driver}_client") }
29
+
30
+ describe :defaults do
31
+ subject { client }
32
+ its(:namespace) { should be_nil }
33
+ its(:driver) { should be driver }
34
+ its(:retry) { should be_true }
35
+ its(:queue) { should eq "default" }
36
+ end
37
+
38
+ describe :push do
39
+
40
+ context :validations do
41
+
42
+ context "with an incorrect message type" do
43
+ it "raises an ArgumentError" do
44
+ expect{ client.push([]) }.to raise_error(ArgumentError, /Message must be a Hash/)
45
+ expect{ client.push("{}") }.to raise_error(ArgumentError, /Message must be a Hash/)
46
+ expect{ client.push(DummyClass.new) }.to raise_error(ArgumentError, /Message must be a Hash/)
47
+ end
48
+ end
49
+
50
+ context "without a class" do
51
+ it "raises an ArgumentError" do
52
+ expect{ client.push(:args => ['foo', 1, { :bat => "bar" }]) }.to raise_error(ArgumentError, /Message must include a class/)
53
+ end
54
+ end
55
+
56
+ context "with an incorrect args type" do
57
+ it "raises an ArgumentError" do
58
+ expect{ client.push(:class => "MyWorker", :args => { :bat => "bar" }) }.to raise_error(ArgumentError, /Message args must be an Array/)
59
+ end
60
+ end
61
+
62
+ context "with an incorrect class type" do
63
+ it "raises an ArgumentError" do
64
+ expect{ client.push(:class => DummyClass, :args => ["foo", 1, { :bat => "bar" }]) }.to raise_error(ArgumentError, /Message class must be a String representation of the class name/)
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ # eg singular and batch
71
+ args = [[{:bat => "bar"}],[[{:bat => "bar"}],[{:bat => "foo"}]]]
72
+ args.each do |args|
73
+
74
+ context "with args #{args}" do
75
+
76
+ it "should create #{args.length} job(s)" do
77
+
78
+ wrap_in_synchrony?(driver) do
79
+
80
+ client.connection_pool do |connection|
81
+ connection.flushdb
82
+ end
83
+
84
+ expect { client.push(:class => "MyWorker", :args => args) }.to change {
85
+ client.connection_pool do |connection| connection.llen("queue:default"); end
86
+ }.from(0).to(args.length)
87
+
88
+ raw_jobs = client.connection_pool do |connection| connection.lrange("queue:default", 0, args.length); end
89
+ raw_jobs.each do |job|
90
+ job = MultiJson.decode(job, :symbolize_keys => true)
91
+ expect(job).to have_key(:jid)
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+ # eg with a variety of different time types
99
+ times = [ Time.now, DateTime.now, Time.now.utc.iso8601, Time.now.to_f ]
100
+ times.each do |time|
101
+
102
+ context "with time #{time} ( #{time.class} )" do
103
+
104
+ it "should create #{args.length} job(s)" do
105
+
106
+ wrap_in_synchrony?(driver) do
107
+
108
+ client.connection_pool do |connection|
109
+ connection.flushdb
110
+ end
111
+
112
+ expect { client.push(:class => "MyWorker", :args => args, :at => time) }.to change {
113
+ client.connection_pool do |connection| connection.zcard("schedule"); end
114
+ }.from(0).to(args.length)
115
+
116
+ raw_jobs = client.connection_pool do |connection| connection.zrange("schedule", 0, args.length); end
117
+ raw_jobs.each do |job|
118
+ job = MultiJson.decode(job, :symbolize_keys => true)
119
+ expect(job).to have_key(:at)
120
+ expect(job[:at]).to be_within(1).of(Time.now.to_f)
121
+ end
122
+
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
141
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ describe Rubykiq::Connection do
4
+
5
+ describe :defaults do
6
+ subject { Rubykiq::Connection.new }
7
+ its(:namespace) { should be_nil }
8
+ its(:host) { should eq "localhost" }
9
+ its(:port) { should be 6379 }
10
+ its(:db) { should be 0 }
11
+ its(:password) { should be_nil }
12
+ end
13
+
14
+ describe :options do
15
+
16
+ context :custom do
17
+ subject { Rubykiq::Connection.new(:namespace => "yyy") }
18
+ its(:namespace) { should eq "yyy" }
19
+ end
20
+
21
+ context :inherited_settings do
22
+ it "should work" do
23
+ client = Rubykiq::Client.new(:namespace => "xxx")
24
+ client.connection_pool do |connection|
25
+ expect(connection.namespace).to eq "xxx"
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ describe :env do
33
+ subject { Rubykiq::Connection.new }
34
+
35
+ [{:name => "REDISTOGO_URL", :value => "redistogo"}, {:name => "REDIS_PROVIDER", :value => "redisprovider"}, {:name => "REDIS_URL", :value => "redisurl"} ].each do | test_case |
36
+ context "with ENV[#{test_case[:name]}]" do
37
+ before do
38
+ ENV[test_case[:name]] = "redis://#{test_case[:value]}:6379/0"
39
+ end
40
+ after do
41
+ ENV[test_case[:name]] = nil
42
+ end
43
+ its(:host) { should eq test_case[:value] }
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,31 @@
1
+ require "spec_helper"
2
+
3
+ describe Rubykiq do
4
+
5
+ describe :version do
6
+ subject { Rubykiq::VERSION }
7
+ it { should be_kind_of(String) }
8
+ end
9
+
10
+ describe :client do
11
+ subject { Rubykiq.client }
12
+ it { should be_kind_of(Rubykiq::Client) }
13
+ end
14
+
15
+ describe :connection_pool do
16
+ subject { Rubykiq.connection_pool }
17
+ it { should be_kind_of(::ConnectionPool) }
18
+ end
19
+
20
+ # for every valid option
21
+ Rubykiq::Client::VALID_OPTIONS_KEYS.each do |key|
22
+
23
+ describe key do
24
+ subject { Rubykiq }
25
+ it {should respond_to key }
26
+ it {should respond_to "#{key}=".to_sym }
27
+ end
28
+
29
+ end
30
+
31
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubykiq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Karl Freeman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis-namespace
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: multi_json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: connection_pool
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ description: Sidekiq agnostic enqueuing using Redis
84
+ email:
85
+ - karlfreeman@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .document
91
+ - .gitignore
92
+ - .rspec
93
+ - .travis.yml
94
+ - .yardopts
95
+ - CHANGELOG.md
96
+ - CONTRIBUTING.md
97
+ - Gemfile
98
+ - LICENSE.md
99
+ - README.md
100
+ - Rakefile
101
+ - lib/rubykiq.rb
102
+ - lib/rubykiq/client.rb
103
+ - lib/rubykiq/connection.rb
104
+ - lib/rubykiq/version.rb
105
+ - rubykiq.gemspec
106
+ - spec/spec_helper.rb
107
+ - spec/support/pry.rb
108
+ - spec/support/simplecov.rb
109
+ - spec/support/timecop.rb
110
+ - spec/unit/client_spec.rb
111
+ - spec/unit/connection_spec.rb
112
+ - spec/unit/rubykiq_spec.rb
113
+ homepage: https://github.com/karlfreeman/rubykiq
114
+ licenses:
115
+ - MIT
116
+ metadata: {}
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: 1.9.2
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 2.0.1
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: Sidekiq agnostic enqueuing using Redis
137
+ test_files:
138
+ - spec/spec_helper.rb
139
+ - spec/support/pry.rb
140
+ - spec/support/simplecov.rb
141
+ - spec/support/timecop.rb
142
+ - spec/unit/client_spec.rb
143
+ - spec/unit/connection_spec.rb
144
+ - spec/unit/rubykiq_spec.rb
145
+ has_rdoc: