rubykiq 0.0.1

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