apostle 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.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ # YARD artifacts
10
+ .yardoc
11
+ _yardoc
12
+ coverage
13
+ doc/
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ test/tmp
19
+ test/version_tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in penpal.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Apostle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ 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, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # Apostle
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'apostle'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install apostle
18
+
19
+ ## Domain Key
20
+
21
+ You will need to provide your apostle domain key to send emails. Apostle looks in `ENV['APOSTLE_DOMAIN_KEY']`, or you can set it manually.
22
+
23
+ ```ruby
24
+ Apostle.configure do |config|
25
+ config.domain_key = 'Your domain key'
26
+ end
27
+ ```
28
+
29
+ ## Sending Email
30
+
31
+ Sending an email is easy. A minimal email might look like this.
32
+
33
+ ```ruby
34
+ Apostle::Mail.new('welcome_email', email: "mal@mal.co.nz").deliver!
35
+ ```
36
+ The first param `Apostle::Mail` expects is the template slug, and the second is a hash of mail information.
37
+
38
+ ### Adding data
39
+
40
+ You can assign any data you want, and it will be passed to the API for hydrating your template. If you had a template that required `{{username}}`, you could send them like this:
41
+
42
+ ```ruby
43
+ mail = Apostle::Mail.new('welcome_email', email: 'mal@mal.co.nz', username: 'Snikch').deliver!
44
+ ```
45
+
46
+ You can also set any data directly on the mail object.
47
+
48
+ ```ruby
49
+ mail = Apostle::Mail.new('welcome_email')
50
+ mail.email = 'mal@mal.co.nz'
51
+ mail.username = 'Snikch'
52
+ mail.deliver!
53
+ ```
54
+
55
+ ### Setting name
56
+
57
+ In addition to the email, you can provide the name to be used in the `to` field of the email.
58
+
59
+ ```ruby
60
+ Apostle::Mail.new('welcome_email', email: "mal@mal.co.nz", name: "Mal Curtis").deliver!
61
+ # Sends email with "to: Mal Curtis <mal@mal.co.nz>"
62
+ ```
63
+
64
+ ### Setting from address
65
+
66
+ Although the `from` address is set up in your template, you can override that for any individual email, or provide a reply to address.
67
+
68
+ ```ruby
69
+ mail.from = 'support@example.com'
70
+ mail.reply_to = 'noreply@example.com'
71
+ ```
72
+
73
+
74
+ ### Additional Headers
75
+
76
+ You can provide custom headers to be sent with your email via `#header`.
77
+
78
+ ```ruby
79
+ # Set
80
+ mail.header 'X-Some-Header', 'my custom header'
81
+
82
+ # Get
83
+ mail.header 'X-Some-Header'
84
+ => "my custom header"
85
+ ```
86
+
87
+ ## Sending Multiple Emails
88
+
89
+ To speed up processing, you can send more than one email at a time.
90
+
91
+ ```ruby
92
+ queue = Apostle::Queue.new
93
+
94
+ 3.times do |count|
95
+ queue << Apostle::Mail.new("welcome_email", email: "user#{count}@example.com")
96
+ end
97
+
98
+ queue.deliver!
99
+ ```
100
+
101
+ If any of the emails are invalid this will raise an exception and no emails will be sent; i.e. missing a template slug, or delivery address.
102
+
103
+ You can either catch `Apostle::DeliveryError`, or call the safer `#deliver`, then access a hash of results on the queue via `#results`.
104
+
105
+ ```ruby
106
+ queue = Apostle::Queue.new
107
+
108
+ queue << Apostle::Mail.new("welcome_email", email: "mal@mal.co.nz")
109
+ queue << Apostle::Mail.new("welcome_email")
110
+
111
+ queue.deliver
112
+ => false
113
+
114
+ queue.results
115
+ => {
116
+ :valid=>[#<Apostle::Mail:0x007fcee5ab2550>],
117
+ :invalid=>[#<Apostle::Mail:0x007fcee5ab23c0>]
118
+ }
119
+ queue.results[:invalid].first._exception
120
+ => #<Apostle::DeliveryError @message="No recipient provided">
121
+ ```
122
+
123
+ ### Helpers
124
+
125
+ You have access to `#size` and `#clear` on the queue. You can use this to group requests.
126
+
127
+ ```ruby
128
+ users.each do |user|
129
+ queue << Penpan::Mail.new('welcome', email: user.email)
130
+ if queue.size == 1000
131
+ queue.deliver
132
+ queue.clear
133
+ end
134
+ end
135
+ ```
136
+
137
+ Or use the helper method `#flush`, which does exactly this, calls `#deliver` then `#clear` if delivery succeeds.
138
+
139
+ ```ruby
140
+ users.each do |user|
141
+ queue << Penpan::Mail.new('welcome', email: user.email)
142
+ if queue.size == 1000
143
+ queue.flush
144
+ end
145
+ end
146
+ ```
147
+
148
+ ## Delivery Failure
149
+
150
+ If delivery to Apostle fails, an exception will be raised. There are various events that could cause a failure:
151
+
152
+ * `Apostle::Unauthorized`: The domain key was not provided, or valid
153
+ * `Apostle::UnprocessableEntity`: The server returned a 422 response. Check the content of the message for more details, but this will likely be a validation / content error
154
+ * `Apostle::ServerError`: Something went wrong at the Apostle API, you should try again with exponential backoff
155
+ * `Apostle::Forbidden`: The server returned a 403 response. This should not occur on the delivery API
156
+ * `Apostle::DeliveryError`: Anything which isn't covered by the above exceptions
157
+
158
+ ## Contributing
159
+
160
+ 1. Fork it
161
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
162
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
163
+ 4. Push to the branch (`git push origin my-new-feature`)
164
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,91 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module Apostle
5
+
6
+ class Mail
7
+
8
+ attr_accessor :data,
9
+ :email,
10
+ :from,
11
+ :headers,
12
+ :layout_id,
13
+ :name,
14
+ :reply_to,
15
+ :template_id,
16
+ :_exception
17
+
18
+ def initialize(template_id, options = {})
19
+ @template_id = template_id
20
+ @data = {}
21
+
22
+ options.each do |k, v|
23
+ self.send("#{k}=", v)
24
+ end
25
+ end
26
+
27
+ # Provide a getter and setters for headers
28
+ def header(name, value = nil)
29
+ if value
30
+ (@headers ||= {})[name] = value
31
+ else
32
+ (@headers || {})[name]
33
+ end
34
+ end
35
+
36
+ # Allow convenience setters for the data payload
37
+ # E.G. mail.potato= "Something" will set @data['potato']
38
+ def method_missing(method, *args)
39
+ return unless method.match /.*=/
40
+ @data[method.to_s.gsub(/=$/, '')] = args.first
41
+ end
42
+
43
+ def deliver
44
+ begin
45
+ deliver!
46
+ true
47
+ rescue DeliveryError => e
48
+ false
49
+ end
50
+ end
51
+
52
+ # Shortcut method to deliver a single message
53
+ def deliver!
54
+ return true unless Apostle.deliver
55
+
56
+ unless template_id && template_id != ""
57
+ raise DeliveryError,
58
+ "No email template_id provided"
59
+ end
60
+
61
+ queue = Apostle::Queue.new
62
+ queue.add self
63
+ queue.deliver!
64
+
65
+ # Return true or false depending on successful delivery
66
+ if queue.results[:valid].include?(self)
67
+ return true
68
+ else
69
+ raise _exception
70
+ end
71
+ end
72
+
73
+ def to_h
74
+ {
75
+ "#{self.email.to_s}" => {
76
+ "data" => @data,
77
+ "from" => from.to_s,
78
+ "headers" => headers,
79
+ "layout_id" => layout_id.to_s,
80
+ "name" => name.to_s,
81
+ "reply_to" => reply_to.to_s,
82
+ "template_id" => template_id.to_s
83
+ }.delete_if { |k, v| !v || v == '' }
84
+ }
85
+ end
86
+
87
+ def to_json
88
+ JSON.generate(to_h)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,150 @@
1
+ require 'json'
2
+
3
+ module Apostle
4
+
5
+ class Queue
6
+
7
+ attr_accessor :emails, :results
8
+
9
+ def initialize
10
+ @emails = []
11
+ end
12
+
13
+ def <<(email)
14
+ add(email)
15
+ end
16
+
17
+ def add(email)
18
+ @emails << email
19
+ end
20
+
21
+ def size
22
+ emails.size
23
+ end
24
+
25
+ def clear
26
+ emails = []
27
+ results = nil
28
+ end
29
+
30
+ def flush
31
+ deliver && clear
32
+ end
33
+
34
+ def deliver
35
+ deliver!
36
+ rescue DeliveryError
37
+ false
38
+ end
39
+
40
+ def deliver!
41
+ return true unless Apostle.deliver
42
+
43
+ # Validate the minimum requirement of a recipient and template
44
+ unless Apostle.domain_key
45
+ raise DeliveryError,
46
+ "No Apostle Domain Key has been defined. Preferably this should be in your environment, as ENV['APOSTLE_DOMAIN_KEY']. If you need to configure this manually, you can call Apostle.configure.
47
+
48
+ Apostle.configure do |config|
49
+ config.domain_key = 'Your domain key'
50
+ end"
51
+ end
52
+
53
+ raise DeliveryError, "Mail queue is empty" if emails.size == 0
54
+
55
+ payload, @results = process_emails
56
+
57
+ if @results[:invalid].size > 0
58
+ raise DeliveryError,
59
+ "Invalid emails: #{@results[:invalid].size}"
60
+ end
61
+
62
+ # Deliver the payload
63
+ response = deliver_payload(payload)
64
+
65
+ true
66
+ end
67
+
68
+ private
69
+
70
+ def process_emails
71
+ results = { valid: [], invalid: [] }
72
+
73
+ payload = {recipients: {}}
74
+
75
+ emails.each do |mail|
76
+ # Validate each mail
77
+ begin
78
+ unless mail.email && mail.email != ""
79
+ raise DeliveryError,
80
+ "No recipient provided"
81
+ end
82
+
83
+ unless mail.template_id && mail.template_id != ""
84
+ raise DeliveryError,
85
+ "No email template_id provided"
86
+ end
87
+
88
+ payload[:recipients].merge!(mail.to_h)
89
+ results[:valid] << mail
90
+ rescue => e
91
+ results[:invalid] << mail
92
+ mail._exception = e
93
+ end
94
+ end
95
+
96
+ [payload, results]
97
+ end
98
+
99
+ def deliver_payload(payload)
100
+ delivery_api = Apostle.delivery_host
101
+
102
+ req = Net::HTTP::Post.new(
103
+ "/",
104
+ 'Content-Type' =>'application/json',
105
+ "Authorization" => "Bearer #{Apostle.domain_key}")
106
+ if delivery_api.user
107
+ req.basic_auth delivery_api.user, delivery_api.password
108
+ end
109
+ req.body = JSON.generate(payload)
110
+ response = Net::HTTP.
111
+ new(delivery_api.host, delivery_api.port).
112
+ start do |http|
113
+ http.request(req)
114
+ end
115
+
116
+ # Successful request
117
+ if [200, 201, 202].include?(response.code.to_i)
118
+ return true
119
+ end
120
+
121
+ begin
122
+ json = JSON.parse(response.body)
123
+ rescue JSON::ParserError
124
+ json = {}
125
+ end
126
+
127
+ if json["message"]
128
+ message = json["message"]
129
+ else
130
+ response.body
131
+ end
132
+
133
+ raise case response.code.to_i
134
+ when 401
135
+ Apostle::Unauthorized
136
+ when 403
137
+ Apostle::Forbidden
138
+ when 422
139
+ Apostle::UnprocessableEntity
140
+ when 500
141
+ Apostle::ServerError
142
+ else
143
+ Apostle::DeliveryError
144
+ end, message
145
+
146
+ response
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,3 @@
1
+ module Apostle
2
+ VERSION = "0.0.1"
3
+ end
data/lib/apostle.rb ADDED
@@ -0,0 +1,53 @@
1
+ require "apostle/version"
2
+ require 'apostle/mail'
3
+ require 'apostle/queue'
4
+ require 'uri'
5
+
6
+ module Apostle
7
+ @@delivery_host = URI(ENV['APOSTLE_DELIVERY_HOST'] || 'http://deliver.apostle.io')
8
+ @@domain_key = ENV['APOSTLE_DOMAIN_KEY']
9
+ @@deliver = true
10
+
11
+ def self.delivery_host=(host)
12
+ @@delivery_host = host
13
+ end
14
+
15
+ # Lazily create a delivery_host URI
16
+ def self.delivery_host
17
+ if @@delivery_host.is_a?(URI)
18
+ @@delivery_host
19
+ else
20
+ URI(@@delivery_host)
21
+ end
22
+ end
23
+
24
+ def self.domain_key=(key)
25
+ @@domain_key = key
26
+ end
27
+
28
+ def self.domain_key
29
+ @@domain_key
30
+ end
31
+
32
+ def self.deliver=(bool)
33
+ @@deliver = !!bool
34
+ end
35
+
36
+ def self.deliver
37
+ @@deliver
38
+ end
39
+
40
+ class << self
41
+ def configure
42
+ yield self
43
+ end
44
+ end
45
+
46
+ Error = Class.new(StandardError)
47
+ DeliveryError = Class.new(Error)
48
+ Unauthorized = Class.new(DeliveryError)
49
+ Forbidden = Class.new(DeliveryError)
50
+ UnprocessableEntity = Class.new(DeliveryError)
51
+ ServerError = Class.new(DeliveryError)
52
+
53
+ end
data/penpal.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/apostle/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Mal Curtis"]
6
+ gem.email = ["mal@sitepoint.com"]
7
+ gem.description = %q{: Write a gem description}
8
+ gem.summary = %q{: Write a gem summary}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "apostle"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Apostle::VERSION
17
+
18
+ gem.add_development_dependency "minitest"
19
+ gem.add_development_dependency "webmock"
20
+ end
data/spec/mail_spec.rb ADDED
@@ -0,0 +1,65 @@
1
+ require_relative 'spec_helper'
2
+ require 'apostle'
3
+
4
+ describe Apostle::Mail do
5
+ before do
6
+ Apostle.configure do |config|
7
+ config.domain_key = "abc"
8
+ end
9
+ end
10
+
11
+ describe "#initialize" do
12
+ it "assigns template and attributes" do
13
+ mail = Apostle::Mail.new "template_slug",
14
+ email: "email address",
15
+ unknown_key: "val"
16
+
17
+ mail.email.must_equal "email address"
18
+ mail.template_id.must_equal "template_slug"
19
+ mail.data.must_equal({ "unknown_key" => "val" })
20
+ end
21
+ end
22
+
23
+ describe "#deliver!" do
24
+ it "returns raises an exception without a template id" do
25
+ mail = Apostle::Mail.new nil
26
+ expect(mail.deliver!).to raise_error(Apostle::DeliveryError, "No template")
27
+ end
28
+
29
+ it "delegates to a queue" do
30
+ end
31
+
32
+ it "raises any error returned"
33
+ end
34
+
35
+ describe "to_json" do
36
+ it "returns a json representaion of the hash"
37
+ end
38
+
39
+ describe "to_h" do
40
+ it "returns a hash of attributes" do
41
+ mail = Apostle::Mail.new "template_slug"
42
+ mail.from = :f
43
+ mail.email = 123
44
+ mail.reply_to = "someone"
45
+ mail.name = "name"
46
+ mail.data = { "Hello" => "World" }
47
+ mail.foo = 'Bar'
48
+ mail.to_h.must_equal({"123" => {
49
+ "template_id" => "template_slug",
50
+ "from" => "f",
51
+ "reply_to" => "someone",
52
+ "name" => "name",
53
+ "data" => { "Hello" => "World", "foo" => "Bar"}
54
+ }})
55
+ end
56
+ it "removes nil entries" do
57
+ mail = Apostle::Mail.new "slug"
58
+ mail.email = "to"
59
+ mail.to_h.must_equal({"to" => {
60
+ "template_id" => "slug",
61
+ "data" => {}
62
+ }})
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,59 @@
1
+ require_relative 'spec_helper'
2
+
3
+ Apostle.domain_key = "abc123"
4
+
5
+ describe Apostle::Queue do
6
+ it "sends the auth header" do
7
+ stub = stub_request(:any, Apostle.delivery_host.to_s).with(
8
+ headers: { "Authorization" => "Bearer abc123" }
9
+ )
10
+ queue = Apostle::Queue.new
11
+ queue.send :deliver_payload, {}
12
+ assert_requested(stub)
13
+ end
14
+
15
+ it "sends grouped requests" do
16
+ queue = Apostle::Queue.new
17
+ mail1 = Apostle::Mail.new "slug1", email: "recipient1"
18
+ mail2 = Apostle::Mail.new "slug2", email: "recipient2"
19
+
20
+ queue << mail1
21
+ queue << mail2
22
+
23
+ queue.emails.must_equal([mail1, mail2])
24
+
25
+ payload, results = queue.send :process_emails
26
+ results.must_equal({valid: [mail1, mail2], invalid: []})
27
+ payload.must_equal({
28
+ recipients: {
29
+ "recipient1" => {"template_id" => "slug1", "data" => {} },
30
+ "recipient2" => {"template_id" => "slug2", "data" => {} }
31
+ }
32
+ })
33
+ end
34
+
35
+ it "validates emails" do
36
+ queue = Apostle::Queue.new
37
+ mail1 = Apostle::Mail.new nil, email: "recipient1@example.com"
38
+ mail2 = Apostle::Mail.new "slug2"
39
+
40
+ queue << mail1
41
+ queue << mail2
42
+
43
+ queue.emails.must_equal([mail1, mail2])
44
+
45
+ payload, results = queue.send :process_emails
46
+ results.must_equal({invalid: [mail1, mail2], valid: []})
47
+
48
+ mail1._exception.message.must_equal("No email template_id provided")
49
+ mail2._exception.message.must_equal("No recipient provided")
50
+ payload[:recipients].must_equal({})
51
+ end
52
+
53
+ describe "#deliver" do
54
+ it "returns false if no delivery occurs" do
55
+ queue = Apostle::Queue.new
56
+ queue.deliver.must_equal(false)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ require "minitest/autorun"
2
+ require 'webmock/minitest'
3
+
4
+ $LOAD_PATH.unshift "./lib"
5
+ require "apostle"
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apostle
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mal Curtis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: minitest
16
+ requirement: &70173682453920 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70173682453920
25
+ - !ruby/object:Gem::Dependency
26
+ name: webmock
27
+ requirement: &70173682452780 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70173682452780
36
+ description: ': Write a gem description'
37
+ email:
38
+ - mal@sitepoint.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - Gemfile
45
+ - LICENSE
46
+ - README.md
47
+ - Rakefile
48
+ - lib/apostle.rb
49
+ - lib/apostle/mail.rb
50
+ - lib/apostle/queue.rb
51
+ - lib/apostle/version.rb
52
+ - penpal.gemspec
53
+ - spec/mail_spec.rb
54
+ - spec/queue_spec.rb
55
+ - spec/spec_helper.rb
56
+ homepage: ''
57
+ licenses: []
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 1.8.11
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: ': Write a gem summary'
80
+ test_files:
81
+ - spec/mail_spec.rb
82
+ - spec/queue_spec.rb
83
+ - spec/spec_helper.rb
84
+ has_rdoc: