postqueue 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 +7 -0
- data/README.md +41 -0
- data/lib/postqueue/enqueue.rb +23 -0
- data/lib/postqueue/item.rb +30 -0
- data/lib/postqueue/processing.rb +97 -0
- data/lib/postqueue/version.rb +3 -0
- data/lib/postqueue.rb +9 -0
- data/spec/postqueue/enqueue_spec.rb +21 -0
- data/spec/postqueue/postqueue_spec.rb +7 -0
- data/spec/postqueue/process_errors_spec.rb +41 -0
- data/spec/postqueue/process_one_spec.rb +36 -0
- data/spec/postqueue/process_spec.rb +83 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/configure_active_record.rb +24 -0
- data/spec/support/models.rb +123 -0
- data/spec/support/schema.rb +88 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 922c5d4f8d07a93c7e889709d136de7159014599
|
4
|
+
data.tar.gz: fb6d7ac28ed62728a688a4e1cf8f7b6120626b55
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d9371b02973a1ff7776daa9f0f2dac442723acc42d52c4745497438d261fbdd5956178af4d56aea962d9cd5f040a112f9be834c25d7602b4c01b913bf417b3d8
|
7
|
+
data.tar.gz: 0172ea33447642ca43b836b662a52401bae80810940dfd54e24d423600b7462d5b2a9f9376ce35e0b6a46598cd438de3989f4b6d6ea228c7c494aa8f651c5ae7
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Postqueue
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/postqueue`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'postqueue'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install postqueue
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/postqueue.
|
36
|
+
|
37
|
+
|
38
|
+
## License
|
39
|
+
|
40
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
41
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Postqueue
|
2
|
+
module Enqueue
|
3
|
+
Item = ::Postqueue::Item
|
4
|
+
|
5
|
+
def enqueue(op:, entity_type:, entity_id:)
|
6
|
+
# An optimized code path, as laid out below, is 4 times as fast.
|
7
|
+
# However, exec_query changed from Rails 4 to Rails 5.
|
8
|
+
|
9
|
+
# sql = "INSERT INTO postqueue (op, entity_type, entity_id) VALUES($1, $2, $3)"
|
10
|
+
# binds = [ ]
|
11
|
+
#
|
12
|
+
# binds << ActiveRecord::Attribute.from_user("name", op, ::ActiveRecord::Type::String.new)
|
13
|
+
# binds << ActiveRecord::Attribute.from_user("entity_type", entity_type, ::ActiveRecord::Type::String.new)
|
14
|
+
# binds << ActiveRecord::Attribute.from_user("entity_id", entity_id, ::ActiveRecord::Type::Integer.new)
|
15
|
+
# # Note: Rails 4 does not understand prepare: true
|
16
|
+
# db.exec_query(sql, 'SQL', binds, prepare: true)
|
17
|
+
|
18
|
+
Item.create!(op: op, entity_type: entity_type, entity_id: entity_id)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
extend Enqueue
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
module Postqueue
|
4
|
+
class Item < ActiveRecord::Base
|
5
|
+
self.table_name = :postqueue
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.unmigrate!
|
9
|
+
Item.connection.execute <<-SQL
|
10
|
+
DROP TABLE IF EXISTS postqueue;
|
11
|
+
SQL
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.migrate!
|
15
|
+
Item.connection.execute <<-SQL
|
16
|
+
CREATE TABLE postqueue (
|
17
|
+
id SERIAL PRIMARY KEY,
|
18
|
+
op VARCHAR,
|
19
|
+
entity_type VARCHAR,
|
20
|
+
entity_id INTEGER NOT NULL DEFAULT 0,
|
21
|
+
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
|
22
|
+
next_run_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
|
23
|
+
failed_attempts INTEGER NOT NULL DEFAULT 0
|
24
|
+
);
|
25
|
+
|
26
|
+
CREATE INDEX postqueue_idx1 ON postqueue(entity_id);
|
27
|
+
CREATE INDEX postqueue_idx2 ON postqueue(next_run_at);
|
28
|
+
SQL
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Postqueue
|
2
|
+
MAX_ATTEMPTS = 3
|
3
|
+
|
4
|
+
module Processing
|
5
|
+
# Processes many entries
|
6
|
+
#
|
7
|
+
# process limit: 100, skip_duplicates: true
|
8
|
+
def process(options = {}, &block)
|
9
|
+
limit = options.fetch(:limit, 100)
|
10
|
+
skip_duplicates = options.fetch(:skip_duplicates, true)
|
11
|
+
options.delete :limit
|
12
|
+
options.delete :skip_duplicates
|
13
|
+
|
14
|
+
status, result = Item.transaction do
|
15
|
+
process_inside_transaction options, limit: limit, skip_duplicates: skip_duplicates, &block
|
16
|
+
end
|
17
|
+
|
18
|
+
raise result if status == :err
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
# Process a single entry from the queue
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
#
|
26
|
+
# process_one do |op, entity_type
|
27
|
+
# end
|
28
|
+
def process_one(options = {}, &block)
|
29
|
+
options = options.merge(limit: 1, skip_duplicates: false)
|
30
|
+
process options, &block
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def select_and_lock(relation, limit:)
|
36
|
+
relation = relation.where("failed_attempts < ? AND next_run_at < ?", MAX_ATTEMPTS, Time.now).order(:next_run_at, :id)
|
37
|
+
|
38
|
+
sql = relation.to_sql + " FOR UPDATE SKIP LOCKED"
|
39
|
+
sql += " LIMIT #{limit}" if limit
|
40
|
+
items = Item.find_by_sql(sql)
|
41
|
+
|
42
|
+
items
|
43
|
+
end
|
44
|
+
|
45
|
+
# The actual processing. Returns [ :ok, number-of-items ] or [ :err, exception ]
|
46
|
+
def process_inside_transaction(options, limit:, skip_duplicates:, &block)
|
47
|
+
relation = Item.all
|
48
|
+
relation = relation.where(options[:where]) if options[:where]
|
49
|
+
|
50
|
+
first_match = select_and_lock(relation, limit: 1).first
|
51
|
+
return [ :ok, nil ] unless first_match
|
52
|
+
|
53
|
+
# find all matching entries with the same entity_type/op value
|
54
|
+
if limit > 1
|
55
|
+
batch_relation = relation.where(entity_type: first_match.entity_type, op: first_match.op)
|
56
|
+
matches = select_and_lock(batch_relation, limit: limit)
|
57
|
+
else
|
58
|
+
matches = [ first_match ]
|
59
|
+
end
|
60
|
+
|
61
|
+
entity_ids = matches.map(&:entity_id)
|
62
|
+
|
63
|
+
# When skipping dupes we'll find and lock all entries that match entity_type,
|
64
|
+
# op, and one of the entity_ids in the first batch of matches
|
65
|
+
if skip_duplicates
|
66
|
+
entity_ids.uniq!
|
67
|
+
process_relations = relation.where(entity_type: first_match.entity_type, op: first_match.op, entity_id: entity_ids)
|
68
|
+
process_items = select_and_lock process_relations, limit: nil
|
69
|
+
else
|
70
|
+
process_items = matches
|
71
|
+
end
|
72
|
+
|
73
|
+
# Actually process the queue items
|
74
|
+
result = [ first_match.op, first_match.entity_type, entity_ids ]
|
75
|
+
result = yield *result if block_given?
|
76
|
+
|
77
|
+
if result == false
|
78
|
+
postpone process_items
|
79
|
+
else
|
80
|
+
Item.where(id: process_items.map(&:id)).delete_all
|
81
|
+
end
|
82
|
+
|
83
|
+
[ :ok, result ]
|
84
|
+
rescue => e
|
85
|
+
postpone process_items
|
86
|
+
[ :err, e ]
|
87
|
+
end
|
88
|
+
|
89
|
+
def postpone(items)
|
90
|
+
ids = items.map(&:id)
|
91
|
+
sql = "UPDATE postqueue SET failed_attempts = failed_attempts+1, next_run_at = next_run_at + interval '10 second' WHERE id IN (#{ids.join(",")})"
|
92
|
+
Item.connection.exec_query(sql)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
extend Processing
|
97
|
+
end
|
data/lib/postqueue.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ::Postqueue::Enqueue do
|
4
|
+
before do
|
5
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 12
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:item) { Postqueue::Item.first }
|
9
|
+
|
10
|
+
it 'enqueues items' do
|
11
|
+
expect(item.op).to eq("myop")
|
12
|
+
expect(item.entity_type).to eq("mytype")
|
13
|
+
expect(item.entity_id).to eq(12)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'sets defaults' do
|
17
|
+
expect(item.created_at).to be > (Time.now - 1.second)
|
18
|
+
expect(item.next_run_at).to be > (Time.now - 1.second)
|
19
|
+
expect(item.failed_attempts).to eq(0)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "::Postqueue.process_one" do
|
4
|
+
class E < RuntimeError; end
|
5
|
+
|
6
|
+
before do
|
7
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 12
|
8
|
+
end
|
9
|
+
|
10
|
+
it "fails when block raises an exception and reraises the exception" do
|
11
|
+
expect { Postqueue.process_one do |op, type, ids| raise E end }.to raise_error(E)
|
12
|
+
expect(items.map(&:entity_id)).to contain_exactly(12)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "fails when block returns false" do
|
16
|
+
Postqueue.process_one do |op, type, ids| false end
|
17
|
+
expect(items.map(&:entity_id)).to contain_exactly(12)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "keeps item in the queue after failure, with an increased failed_attempt count" do
|
21
|
+
called_block = 0
|
22
|
+
Postqueue.process_one do called_block += 1; false end
|
23
|
+
expect(called_block).to eq(1)
|
24
|
+
|
25
|
+
expect(items.map(&:entity_id)).to contain_exactly(12)
|
26
|
+
expect(items.first.failed_attempts).to eq(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "ignores items with a failed_attempt count > MAX_ATTEMPTS" do
|
30
|
+
expect(Postqueue::MAX_ATTEMPTS).to be >= 3
|
31
|
+
items.update_all(failed_attempts: 3)
|
32
|
+
|
33
|
+
called_block = 0
|
34
|
+
r = Postqueue.process_one do called_block += 1; false end
|
35
|
+
expect(r).to eq(nil)
|
36
|
+
expect(called_block).to eq(0)
|
37
|
+
|
38
|
+
expect(items.map(&:entity_id)).to contain_exactly(12)
|
39
|
+
expect(items.first.failed_attempts).to eq(3)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "::Postqueue.process_one" do
|
4
|
+
before do
|
5
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 12
|
6
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 13
|
7
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 14
|
8
|
+
end
|
9
|
+
|
10
|
+
it "processes one entry" do
|
11
|
+
r = Postqueue.process_one
|
12
|
+
expect(r).to eq(["myop", "mytype", [12]])
|
13
|
+
expect(items.map(&:entity_id)).to contain_exactly(13, 14)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "honors search conditions" do
|
17
|
+
Postqueue.enqueue op: "otherop", entity_type: "mytype", entity_id: 112
|
18
|
+
|
19
|
+
r = Postqueue.process_one(where: { op: "otherop" })
|
20
|
+
expect(r).to eq(["otherop", "mytype", [112]])
|
21
|
+
expect(items.map(&:entity_id)).to contain_exactly(12, 13, 14)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "yields a block and returns it" do
|
25
|
+
Postqueue.enqueue op: "otherop", entity_type: "mytype", entity_id: 112
|
26
|
+
r = Postqueue.process_one(where: { op: "otherop" }) do |op, type, ids|
|
27
|
+
expect(op).to eq("otherop")
|
28
|
+
expect(type).to eq("mytype")
|
29
|
+
expect(ids).to eq([112])
|
30
|
+
"yihaa"
|
31
|
+
end
|
32
|
+
|
33
|
+
expect(r).to eq("yihaa")
|
34
|
+
expect(items.map(&:entity_id)).to contain_exactly(12, 13, 14)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "::Postqueue.process" do
|
4
|
+
context 'when having entries with the same entity_type and op' do
|
5
|
+
before do
|
6
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 12
|
7
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 13
|
8
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 14
|
9
|
+
end
|
10
|
+
|
11
|
+
it "processes one entries" do
|
12
|
+
r = Postqueue.process limit: 1
|
13
|
+
expect(r).to eq(["myop", "mytype", [12]])
|
14
|
+
expect(items.map(&:entity_id)).to contain_exactly(13, 14)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "processes two entries" do
|
18
|
+
r = Postqueue.process limit: 2
|
19
|
+
expect(r).to eq(["myop", "mytype", [12, 13]])
|
20
|
+
expect(items.map(&:entity_id)).to contain_exactly(14)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "processes many entries" do
|
24
|
+
r = Postqueue.process
|
25
|
+
expect(r).to eq(["myop", "mytype", [12, 13, 14]])
|
26
|
+
expect(items.map(&:entity_id)).to contain_exactly()
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when having entries with different entity_type and op' do
|
31
|
+
before do
|
32
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 12
|
33
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 13
|
34
|
+
Postqueue.enqueue op: "otherop", entity_type: "mytype", entity_id: 14
|
35
|
+
Postqueue.enqueue op: "myop", entity_type: "othertype", entity_id: 15
|
36
|
+
Postqueue.enqueue op: "otherop", entity_type: "othertype", entity_id: 16
|
37
|
+
end
|
38
|
+
|
39
|
+
it "processes one entries" do
|
40
|
+
r = Postqueue.process limit: 1
|
41
|
+
expect(r).to eq(["myop", "mytype", [12]])
|
42
|
+
expect(items.map(&:entity_id)).to contain_exactly(13, 14, 15, 16)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "processes two entries" do
|
46
|
+
r = Postqueue.process limit: 2
|
47
|
+
expect(r).to eq(["myop", "mytype", [12, 13]])
|
48
|
+
expect(items.map(&:entity_id)).to contain_exactly(14, 15, 16)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "processes only matching entries when asked for more" do
|
52
|
+
r = Postqueue.process
|
53
|
+
expect(r).to eq(["myop", "mytype", [12, 13]])
|
54
|
+
expect(items.map(&:entity_id)).to contain_exactly(14, 15, 16)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "honors search conditions" do
|
58
|
+
r = Postqueue.process(where: { op: "otherop" })
|
59
|
+
expect(r).to eq(["otherop", "mytype", [14]])
|
60
|
+
expect(items.map(&:entity_id)).to contain_exactly(12, 13, 15, 16)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when having duplicate entries' do
|
65
|
+
before do
|
66
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 12
|
67
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 13
|
68
|
+
Postqueue.enqueue op: "myop", entity_type: "mytype", entity_id: 12
|
69
|
+
end
|
70
|
+
|
71
|
+
it "removes duplicates from the queue" do
|
72
|
+
r = Postqueue.process limit: 1
|
73
|
+
expect(r).to eq(["myop", "mytype", [12]])
|
74
|
+
expect(items.map(&:entity_id)).to contain_exactly(13)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "does not remove duplicates when skip_duplicates is set to false" do
|
78
|
+
r = Postqueue.process limit: 1, skip_duplicates: false
|
79
|
+
expect(r).to eq(["myop", "mytype", [12]])
|
80
|
+
expect(items.map(&:entity_id)).to contain_exactly(13, 12)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
path = File.expand_path('../../mpx/lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
|
3
|
+
|
4
|
+
ENV['RACK_ENV'] = 'test'
|
5
|
+
|
6
|
+
require 'rspec'
|
7
|
+
require 'pry'
|
8
|
+
require 'simplecov'
|
9
|
+
|
10
|
+
SimpleCov.start do
|
11
|
+
minimum_coverage 94
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'postqueue'
|
15
|
+
require './spec/support/configure_active_record'
|
16
|
+
|
17
|
+
def items
|
18
|
+
Postqueue::Item.all
|
19
|
+
end
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.run_all_when_everything_filtered = true
|
23
|
+
config.filter_run focus: (ENV['CI'] != 'true')
|
24
|
+
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
25
|
+
config.order = 'random'
|
26
|
+
|
27
|
+
config.before(:all) { }
|
28
|
+
config.after { }
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require_relative './models'
|
3
|
+
|
4
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection(adapter: 'postgresql',
|
7
|
+
database: 'postqueue_test',
|
8
|
+
username: 'postqueue',
|
9
|
+
password: 'postqueue')
|
10
|
+
|
11
|
+
require_relative 'schema.rb'
|
12
|
+
require_relative 'models.rb'
|
13
|
+
|
14
|
+
Postqueue.unmigrate!
|
15
|
+
Postqueue.migrate!
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.around(:each) do |example|
|
19
|
+
ActiveRecord::Base.connection.transaction do
|
20
|
+
example.run
|
21
|
+
raise ActiveRecord::Rollback, 'Clean up'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
__END__
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
class MockAssocationInfo < OpenStruct
|
6
|
+
def virtual?; mode == :virtual; end
|
7
|
+
def belongs_to?; mode == :belongs_to; end
|
8
|
+
def habtm?; mode == :has_and_belongs_to_many; end
|
9
|
+
end
|
10
|
+
|
11
|
+
module AnalyticsReflectionStub
|
12
|
+
attr :analytics_reflection
|
13
|
+
def set_analytics_reflection(hsh)
|
14
|
+
@analytics_reflection = OpenStruct.new(hsh)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr :analytics_parent
|
18
|
+
def set_analytics_parent(analytics_parent)
|
19
|
+
@analytics_parent = analytics_parent
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Unicorn < ActiveRecord::Base
|
24
|
+
extend AnalyticsReflectionStub
|
25
|
+
|
26
|
+
def self.name_without_prefix
|
27
|
+
'UnicornWithoutPrefix'
|
28
|
+
end
|
29
|
+
|
30
|
+
validates_presence_of :name
|
31
|
+
|
32
|
+
set_analytics_reflection associations_by_foreign_keys: {}, analytics_keys: [:name]
|
33
|
+
end
|
34
|
+
|
35
|
+
class Manticore < ActiveRecord::Base
|
36
|
+
def self.name_without_prefix
|
37
|
+
'ManticoreWithoutPrefix'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Rider < ActiveRecord::Base
|
42
|
+
end
|
43
|
+
|
44
|
+
class Foal < ActiveRecord::Base
|
45
|
+
extend AnalyticsReflectionStub
|
46
|
+
|
47
|
+
belongs_to :parent, class_name: 'Unicorn'
|
48
|
+
has_and_belongs_to_many :riders, class_name: 'Rider'
|
49
|
+
|
50
|
+
set_analytics_reflection associations_by_foreign_keys:
|
51
|
+
{
|
52
|
+
parent_id: MockAssocationInfo.new(mode: :belongs_to, klass: Unicorn, name: :parent),
|
53
|
+
rider_ids: MockAssocationInfo.new(mode: :has_and_belongs_to_many, klass: Rider, name: :riders),
|
54
|
+
stable_ids: MockAssocationInfo.new(mode: :virtual, name: :stables)
|
55
|
+
},
|
56
|
+
analytics_keys: [:nick_name, :age, :parent]
|
57
|
+
set_analytics_parent :parent
|
58
|
+
end
|
59
|
+
|
60
|
+
class Pegasus < ActiveRecord::Base
|
61
|
+
extend AnalyticsReflectionStub
|
62
|
+
|
63
|
+
belongs_to :parent, class_name: 'Foal'
|
64
|
+
|
65
|
+
set_analytics_reflection associations_by_foreign_keys: { :parent_id => OpenStruct.new(klass: Foal, name: :parent) },
|
66
|
+
analytics_keys: [:nick_name, :age, :parent]
|
67
|
+
set_analytics_parent :parent
|
68
|
+
|
69
|
+
attr_reader :affiliation_id
|
70
|
+
end
|
71
|
+
|
72
|
+
class Dragon < ActiveRecord::Base
|
73
|
+
def self.name_without_prefix
|
74
|
+
'DragonWithoutPrefix'
|
75
|
+
end
|
76
|
+
|
77
|
+
def describe
|
78
|
+
'yihaa'
|
79
|
+
end
|
80
|
+
|
81
|
+
extend AnalyticsReflectionStub
|
82
|
+
end
|
83
|
+
|
84
|
+
class Asset < ActiveRecord::Base
|
85
|
+
extend AnalyticsReflectionStub
|
86
|
+
attr_reader :affiliation_id
|
87
|
+
end
|
88
|
+
|
89
|
+
class Product < ActiveRecord::Base
|
90
|
+
extend AnalyticsReflectionStub
|
91
|
+
attr_reader :affiliation_id
|
92
|
+
end
|
93
|
+
|
94
|
+
class User < ActiveRecord::Base
|
95
|
+
extend AnalyticsReflectionStub
|
96
|
+
attr_reader :affiliation_id
|
97
|
+
|
98
|
+
def analytics_title
|
99
|
+
"my analytics_title"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Grouping < ActiveRecord::Base
|
104
|
+
extend AnalyticsReflectionStub
|
105
|
+
attr_reader :affiliation_id
|
106
|
+
end
|
107
|
+
|
108
|
+
class Group < Grouping
|
109
|
+
end
|
110
|
+
|
111
|
+
class MockProductAsset < ActiveRecord::Base
|
112
|
+
has_one :asset
|
113
|
+
has_one :product
|
114
|
+
|
115
|
+
attr_accessor :asset, :product
|
116
|
+
end
|
117
|
+
|
118
|
+
class MockGroupUser < ActiveRecord::Base
|
119
|
+
has_one :group
|
120
|
+
has_one :user
|
121
|
+
|
122
|
+
attr_accessor :group, :user
|
123
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
__END__
|
2
|
+
|
3
|
+
ActiveRecord::Schema.define do
|
4
|
+
self.verbose = false
|
5
|
+
|
6
|
+
create_table :unicorns, force: true do |t|
|
7
|
+
t.string :name, null: false
|
8
|
+
t.string :affiliation_id
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table :foals, force: true do |t|
|
12
|
+
t.integer :parent_id, null: false
|
13
|
+
t.string :nick_name
|
14
|
+
t.integer :age
|
15
|
+
t.datetime :created_at
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table :riders, force: true do |t|
|
19
|
+
t.string :nick_name
|
20
|
+
end
|
21
|
+
|
22
|
+
create_table :foal_riders, force: true do |t|
|
23
|
+
t.integer :rider_id
|
24
|
+
t.integer :foal_id
|
25
|
+
end
|
26
|
+
|
27
|
+
create_table :pegasus, force: true do |t|
|
28
|
+
t.integer :parent_id, null: false
|
29
|
+
t.string :nick_name
|
30
|
+
t.integer :age
|
31
|
+
t.datetime :created_at
|
32
|
+
end
|
33
|
+
|
34
|
+
create_table :mock_product_assets, force: true do |t|
|
35
|
+
t.integer :asset_id
|
36
|
+
t.string :access_level
|
37
|
+
t.integer :product_id
|
38
|
+
end
|
39
|
+
|
40
|
+
create_table :mock_group_users, force: true do |t|
|
41
|
+
t.integer :user_id
|
42
|
+
t.string :access_level
|
43
|
+
t.integer :group_id
|
44
|
+
end
|
45
|
+
|
46
|
+
create_table :assets, force: true
|
47
|
+
|
48
|
+
create_table :products, force: true
|
49
|
+
|
50
|
+
create_table :users, force: true do |t|
|
51
|
+
t.string :title
|
52
|
+
end
|
53
|
+
|
54
|
+
create_table :groupings, force: true
|
55
|
+
|
56
|
+
create_table :manticores, force: true do |t|
|
57
|
+
t.string :dummy_field
|
58
|
+
end
|
59
|
+
|
60
|
+
create_table :dragons, force: true do |t|
|
61
|
+
t.string :full_name
|
62
|
+
end
|
63
|
+
|
64
|
+
execute "INSERT INTO unicorns(affiliation_id, id, name) VALUES('mpx', 1, 'Faith')"
|
65
|
+
execute "INSERT INTO unicorns(affiliation_id, id, name) VALUES('mpx', 2, 'Faery')"
|
66
|
+
execute "INSERT INTO unicorns(affiliation_id, id, name) VALUES('mpx', 3, 'Yaser')"
|
67
|
+
|
68
|
+
execute "INSERT INTO foals(id, parent_id, nick_name, age, created_at) VALUES(1, 1, 'Little Faith', 12, 0)"
|
69
|
+
execute "INSERT INTO foals(id, parent_id, nick_name, age, created_at) VALUES(2, 1, 'Faith Nick', 9, 0)"
|
70
|
+
|
71
|
+
execute "INSERT INTO riders(id, nick_name) VALUES(1, 'Storm Rider')"
|
72
|
+
execute "INSERT INTO riders(id, nick_name) VALUES(2, 'Desert Rider')"
|
73
|
+
|
74
|
+
execute "INSERT INTO foal_riders(rider_id, foal_id) VALUES(1, 1)"
|
75
|
+
execute "INSERT INTO foal_riders(rider_id, foal_id) VALUES(2, 1)"
|
76
|
+
|
77
|
+
execute "INSERT INTO pegasus(parent_id, nick_name, age, created_at) VALUES(1, 'Derpy', 12, 0)"
|
78
|
+
|
79
|
+
execute "INSERT INTO dragons(full_name) VALUES('Chrysophylax Dives')"
|
80
|
+
execute "INSERT INTO dragons(full_name) VALUES('Nepomuk')"
|
81
|
+
execute "INSERT INTO dragons(full_name) VALUES('Smaug')"
|
82
|
+
|
83
|
+
execute "INSERT INTO manticores(dummy_field) VALUES('Dumb Manticore')"
|
84
|
+
|
85
|
+
execute "INSERT INTO users(id, title) VALUES(67, 'sixtyseven')"
|
86
|
+
|
87
|
+
execute "INSERT INTO groupings(id) VALUES(42)"
|
88
|
+
end
|
metadata
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: postqueue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- radiospiel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.4.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.4.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 10.5.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 10.5.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.7.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.7.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activerecord
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '4'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '4'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: timecop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pg
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: a postgres based queue implementation
|
126
|
+
email:
|
127
|
+
- radiospiel@open-lab.org
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- README.md
|
133
|
+
- lib/postqueue.rb
|
134
|
+
- lib/postqueue/enqueue.rb
|
135
|
+
- lib/postqueue/item.rb
|
136
|
+
- lib/postqueue/processing.rb
|
137
|
+
- lib/postqueue/version.rb
|
138
|
+
- spec/postqueue/enqueue_spec.rb
|
139
|
+
- spec/postqueue/postqueue_spec.rb
|
140
|
+
- spec/postqueue/process_errors_spec.rb
|
141
|
+
- spec/postqueue/process_one_spec.rb
|
142
|
+
- spec/postqueue/process_spec.rb
|
143
|
+
- spec/spec_helper.rb
|
144
|
+
- spec/support/configure_active_record.rb
|
145
|
+
- spec/support/models.rb
|
146
|
+
- spec/support/schema.rb
|
147
|
+
homepage: https://github.com/radiospiel/postqueue
|
148
|
+
licenses:
|
149
|
+
- MIT
|
150
|
+
metadata: {}
|
151
|
+
post_install_message:
|
152
|
+
rdoc_options: []
|
153
|
+
require_paths:
|
154
|
+
- lib
|
155
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - ">="
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
requirements: []
|
166
|
+
rubyforge_project:
|
167
|
+
rubygems_version: 2.5.1
|
168
|
+
signing_key:
|
169
|
+
specification_version: 4
|
170
|
+
summary: a postgres based queue implementation
|
171
|
+
test_files: []
|