Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
contains implementation of SQSJobQueue
  • Loading branch information
David Dawson committed Jan 11, 2012
0 parents commit 50f9077
Show file tree
Hide file tree
Showing 17 changed files with 921 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
gems/
gems_dev/
.bundle
*.swp
3 changes: 3 additions & 0 deletions CHANGELOG.rdoc
@@ -0,0 +1,3 @@
= Simple Job

== Version 0.0.0
14 changes: 14 additions & 0 deletions Gemfile
@@ -0,0 +1,14 @@
source 'http://rubygems.org'

gem 'activemodel', '~> 3.0', :require => nil
gem 'activesupport', '~> 3.0', :require => nil
gem 'aws-sdk', '~> 1.2', :require => nil

group :rake do
gem 'simple_gem', :require => 'tasks/simple_gem'
end

group :test do
gem 'rspec', '~> 2.8'
end

48 changes: 48 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,48 @@
GEM
remote: http://rubygems.org/
specs:
activemodel (3.1.3)
activesupport (= 3.1.3)
builder (~> 3.0.0)
i18n (~> 0.6)
activesupport (3.1.3)
multi_json (~> 1.0)
aws-sdk (1.2.6)
httparty (~> 0.7)
json (~> 1.4)
nokogiri (>= 1.4.4)
uuidtools (~> 2.1)
builder (3.0.0)
diff-lcs (1.1.3)
httparty (0.8.1)
multi_json
multi_xml
i18n (0.6.0)
json (1.6.4)
multi_json (1.0.4)
multi_xml (0.4.1)
nokogiri (1.5.0)
rake (0.9.2.2)
rspec (2.8.0)
rspec-core (~> 2.8.0)
rspec-expectations (~> 2.8.0)
rspec-mocks (~> 2.8.0)
rspec-core (2.8.0)
rspec-expectations (2.8.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.8.0)
simple_gem (0.0.1)
activesupport (~> 3.0)
rake (>= 0.8.7)
rspec (~> 2.8)
uuidtools (2.1.2)

PLATFORMS
ruby

DEPENDENCIES
activemodel (~> 3.0)
activesupport (~> 3.0)
aws-sdk (~> 1.2)
rspec (~> 2.8)
simple_gem
7 changes: 7 additions & 0 deletions LICENSE.txt
@@ -0,0 +1,7 @@
Copyright (c) 2012 RevPAR Collective, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
93 changes: 93 additions & 0 deletions README.rdoc
@@ -0,0 +1,93 @@
= Simple Job

A gem containing libraries that support running background jobs or tasks. It's designed to make it easy to:

* Define a job
* Enqueue a job
* Poll for and execute jobs

It is architected to support multiple types of queue implementations, but currently, it only includes an implementation using AWS SQS (http://aws.amazon.com/sqs/). Alternate queue implementations could be plugged in by the client using lib/simple_job/sqs_job_queue.rb as an example.

The AWS SQS queue implementation requires the aws-sdk gem, which must be initialized (by calling AWS.config) for this API to be capable of enqueuing or polling for jobs.


== Queue Configuration

Queue configuration must be done by both the client and the server.

Only the queues that will be used by each must be defined (a client may configure a subset of the queues used by the server, so long as it configures all the queues that it uses).

SQSJobQueue is the queue implementation used by default. This may be overridden by calling SimpleJob::JobQueue.config :implementation => 'alternate_queue_implementation_identifier'

=== Minimal configuration - specify queue prefix and define one default queue

SimpleJob::SQSJobQueue.config :queue_prefix => 'my-job'
SimpleJob::SQSJobQueue.define_queue 'normal', :default => true

=== Complex configuration with explicit queue implementation, non-rails-defined environment, and multiple queues

SimpleJob::JobQueue.config :implementation => 'sqs'
SimpleJob::SQSJobQueue.config :queue_prefix => 'stash-job', :environment => 'production'
SimpleJob::SQSJobQueue.define_queue 'normal', :visibility_timeout => 60, :default => true
SimpleJob::SQSJobQueue.define_queue 'high-priority', :visibility_timeout => 10
SimpleJob::SQSJobQueue.define_queue 'long-running', :visibility_timeout => 3600


== Job Definition

class FooSender
include SimpleJob::JobDefinition

simple_job_attribute :target, :foo_content # defines getters/setters for each, and
# adds them to serialized message

validates :target, :presence => true # standard ActiveModel validation

def execute
puts "#{foo_content} -> #{target}"
end
end


== Job Client Usage

=== Typical usage of default queue

You may call #enqueue with no arguments, in which case JobQueue.default will be used.

f = FooSender.new(:target => 'joe', :foo_content => 'foo!') # can also assign attributes with f.target=, f.foo_content=
if f.enqueue(queue)
puts 'i just sent some foo to joe!'
else
puts "the following errors occurred: #{f.errors.full_messages.join('; ')}"
end

json = f.to_json # { "type": "foo_sender", "version": "1", "data": { "target": "joe", "foo_content": "foo!" } }
f_copy = FooSender.new.from_json(json)

=== Simple usage with explicit queue

To explicitly specify the queue to use, simply specify its type when calling enqueue.

f = FooSender.new
f.target = 'bob'
f.foo_content = 'foo!'
f.enqueue!('normal') # raises exception if operation fails

=== Queue configuration for multiple enqueue operations

Alternatively, the queue may be attached to the job upfront for multiple enqueue operations:

FooSender.job_queue(SimpleJob::JobQueue['high-priority'])
f1 = FooSender.new(:target => 'cookie monster', :foo_content => 'cookies and milk')
f1.enqueue
f2 = FooSender.new(:target => 'oscar the grouch', :foo_content => 'pizza')
f2.enqueue


== Job Server Usage

JobQueue.default.poll do |message|
FooSender
end

8 changes: 8 additions & 0 deletions Rakefile
@@ -0,0 +1,8 @@
require 'bundler'
Bundler.require(:rake)

# Configure gem building
require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'simple_job', 'version'))
SimpleGem.current_version = SimpleJob::VERSION
SimpleGem.current_gemspec = 'simple_job.gemspec'

7 changes: 7 additions & 0 deletions lib/simple_job.rb
@@ -0,0 +1,7 @@
module SimpleJob

autoload :JobDefinition, 'simple_job/job_definition'
autoload :JobQueue, 'simple_job/job_queue'
autoload :SQSJobQueue, 'simple_job/sqs_job_queue'

end
180 changes: 180 additions & 0 deletions lib/simple_job/job_definition.rb
@@ -0,0 +1,180 @@
require 'active_model'
require 'active_support/inflector'

module SimpleJob
module JobDefinition

RESERVED_ATTRIBUTES = [ :type, :version, :data ]

def self.included(klass)

klass.extend(ClassMethods)

klass.class_eval do
include ::ActiveModel::Validations
include ::ActiveModel::Serializers::JSON
end

klass.include_root_in_json = false
klass.register_simple_job

end

# should be overridden by including classes
if !method_defined?(:execute)
def execute
end
end

def attributes
{
'type' => type,
'version' => version,
'data' => data,
}
end

def attributes=(attributes)
attributes.each do |key, value|
send("#{key}=", value)
end
end

def data
@data ||= {}
self.class.simple_job_attributes.each do |attribute|
@data[attribute.to_s] ||= nil
end
@data
end

def data=(data)
self.attributes = data
end

def type
self.class.definition[:type]
end

def type=(type)
if type.to_sym != self.type
raise "tried to deserialize object with type #{type}, but this object only " +
"supports type: #{self.type}"
end
end

def versions
self.class.definition[:versions]
end

def version
versions.first
end

def version=(version)
if !versions.include?(version.to_s)
raise "tried to deserialize object with version #{version}, but this object " +
"only supports versions: #{versions.join(", ")}"
end
end

def enqueue(queue_type = nil)
if valid?
queue = (queue_type && JobQueue[queue_type]) || self.class.job_queue || JobQueue.default
queue.enqueue(self.to_json)
else
false
end
end

def enqueue!(queue_type = nil)
enqueue(queue_type) || raise("object is not valid: #{errors.full_messages.join('; ')}")
end

def read_simple_job_attribute(attribute)
data[attribute.to_s]
end

def write_simple_job_attribute(attribute, value)
data[attribute.to_s] = value
end

def initialize(attributes = {})
attributes.each do |key, value|
send("#{key}=", value)
end
end

def self.job_definition_class_for(type, version)
@job_definitions.each do |definition|
if (definition[:type] == type.to_sym) && (definition[:versions].include?(version))
return definition[:class]
end
end
nil
end

def self.job_definitions
@job_definitions ||= []
end

private

module ClassMethods

def definition
@definition
end

def register_simple_job(options = {})
default_type = self.name.underscore.to_sym

@definition = {
:class => self,
:type => default_type,
:versions => [ '1' ],
}.merge(options)

@definition[:type] = @definition[:type].to_sym
@definition[:versions] = Array(@definition[:versions])
@definition[:versions].collect! { |value| value.to_s }

::SimpleJob::JobDefinition.job_definitions.delete_if { |item| item[:type] == default_type }
::SimpleJob::JobDefinition.job_definitions << @definition
end

def job_queue(queue_type = nil)
@job_queue = JobQueue[queue_type] if queue_type
@job_queue
end

def simple_job_attributes
@simple_job_attributes ||= []
end

def simple_job_attribute(*attributes)
attributes.each do |attribute|
attribute = attribute.to_sym

if RESERVED_ATTRIBUTES.include?(attribute)
raise "attempted to declare reserved attribute: #{attribute}"
end

simple_job_attributes << attribute

class_eval <<-__EOF__
def #{attribute}
read_simple_job_attribute(:#{attribute})
end
def #{attribute}=(value)
write_simple_job_attribute(:#{attribute}, value)
end
__EOF__
end
end

end

end
end

0 comments on commit 50f9077

Please sign in to comment.