pq-wsm 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +86 -0
- data/Rakefile +38 -0
- data/lib/generators/pc_queues/install_generator.rb +9 -0
- data/lib/generators/pc_queues/migration_generator.rb +15 -0
- data/lib/generators/pc_queues/templates/README +15 -0
- data/lib/generators/pc_queues/templates/initializer.rb +6 -0
- data/lib/generators/pc_queues/templates/migration.rb +36 -0
- data/lib/pc_queues.rb +43 -0
- data/lib/pc_queues/acts_as_enqueable.rb +31 -0
- data/lib/pc_queues/acts_as_queue_owner.rb +30 -0
- data/lib/pc_queues/priority_queue_item.rb +13 -0
- data/lib/pc_queues/queue.rb +312 -0
- data/lib/pc_queues/queue_item.rb +58 -0
- data/lib/pc_queues/queue_rule.rb +32 -0
- data/lib/pc_queues/queue_rule_set.rb +40 -0
- data/lib/pc_queues/queue_rules/boolean_queue_rule.rb +34 -0
- data/lib/pc_queues/queue_rules/numeric_queue_rule.rb +42 -0
- data/lib/pc_queues/queue_rules/sample_queue_rule.rb +36 -0
- data/lib/pc_queues/queue_rules/string_match_queue_rule.rb +44 -0
- data/lib/pc_queues/railtie.rb +10 -0
- data/lib/pc_queues/version.rb +3 -0
- data/lib/tasks/pc_queues_tasks.rake +4 -0
- data/spec/boolean_queue_rule_spec.rb +34 -0
- data/spec/numeric_queue_rule_spec.rb +123 -0
- data/spec/queue_spec.rb +494 -0
- data/spec/sample_queue_rule_spec.rb +34 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/string_match_rule_spec.rb +76 -0
- data/spec/support/active_record.rb +42 -0
- data/spec/support/application.rb +122 -0
- data/spec/support/queue_helpers.rb +16 -0
- data/spec/support/redis_helpers.rb +32 -0
- data/spec/support/time.rb +23 -0
- metadata +131 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
#
|
2
|
+
# This source file is part of project: PcQueues
|
3
|
+
#
|
4
|
+
# A Proctoring Workflow Platform
|
5
|
+
#
|
6
|
+
# Copyright (c) ProctorCam Inc. 2014 All rights reserved
|
7
|
+
#
|
8
|
+
module PcQueues
|
9
|
+
# QueueItems are the container for object instances that are
|
10
|
+
# enqueued and dequeued from a Queue
|
11
|
+
class QueueItem < ActiveRecord::Base
|
12
|
+
# <<<<<<< HEAD
|
13
|
+
# attr_accessible :enqueued_time, :position, :queue, :type, :enqueable
|
14
|
+
def self.accessible_attributes
|
15
|
+
[:enqueued_time, :position, :queue, :type, :enqueable]
|
16
|
+
end
|
17
|
+
# =======
|
18
|
+
# attr_accessible :enqueued_time, :position, :queue, :type, :enqueable
|
19
|
+
|
20
|
+
# attr_accessor :cur_position
|
21
|
+
|
22
|
+
# >>>>>>> 5a7112729e02a8228e3591999ec3a5d0b912b86f
|
23
|
+
belongs_to :queue, :class_name => 'PcQueues::Queue'
|
24
|
+
belongs_to :enqueable, :polymorphic => true
|
25
|
+
|
26
|
+
# Ensure there are no duplicate enqueables within any queue
|
27
|
+
validates_uniqueness_of :enqueable_id, :scope => :queue_id
|
28
|
+
|
29
|
+
# Attributes include
|
30
|
+
# :position -> the 1-based position when added to the queue
|
31
|
+
# :equeued_time -> the seconds since the epoch when the item was added
|
32
|
+
# to the queue
|
33
|
+
|
34
|
+
def stats
|
35
|
+
{ :position => cur_position, estimated_time_left: time_remaining }
|
36
|
+
end
|
37
|
+
|
38
|
+
# The advancement rate for this item
|
39
|
+
# -> round to nearest thousandth digit
|
40
|
+
def rate()
|
41
|
+
@rate ||= (wait_time > 0 && advancement / wait_time.to_f) || 0
|
42
|
+
end
|
43
|
+
|
44
|
+
# number of positions this item has travelled
|
45
|
+
def advancement()
|
46
|
+
position - cur_position
|
47
|
+
end
|
48
|
+
|
49
|
+
# The wait time in seconds for this item
|
50
|
+
def wait_time()
|
51
|
+
Time.now.to_i - enqueued_time
|
52
|
+
end
|
53
|
+
|
54
|
+
def time_remaining()
|
55
|
+
rate * position || wait_time
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#
|
2
|
+
# This source file is part of project: PcQueues
|
3
|
+
#
|
4
|
+
# Copyright (c) ProctorCam Inc. 2014 All rights reserved
|
5
|
+
#
|
6
|
+
module PcQueues
|
7
|
+
# QueueRule is the base class (STI) for Rules that are evaluated as part of a QueueRuleSet
|
8
|
+
# There must be at least one implmentor of the passes? method in the hierachy of QueueRule
|
9
|
+
# being stored in a QueueRuleSet.
|
10
|
+
class QueueRule < ActiveRecord::Base
|
11
|
+
# attr_accessible :numeric_value, :string_value, :bool_value, :type
|
12
|
+
def self.accessible_attributes
|
13
|
+
[:numeric_value, :string_value, :bool_value, :type]
|
14
|
+
end
|
15
|
+
belongs_to :queue_rule_set
|
16
|
+
|
17
|
+
# A convenience method for creating QueueRule instances with more declaritive
|
18
|
+
# code. Derived classes can implement and super to this.
|
19
|
+
#
|
20
|
+
# Eg. rule_set.queue_rules.create(MyNewRule.options special_opt1: value)
|
21
|
+
#
|
22
|
+
def self.options(opts = {})
|
23
|
+
opts[:type] = self.name
|
24
|
+
opts
|
25
|
+
end
|
26
|
+
|
27
|
+
# All derived classes must implement this method and it must return a Boolean
|
28
|
+
def passes?(enqueable, *args)
|
29
|
+
raise NotImplementedError, "The class: #{self.class.name} requires an implementation of #{__method__}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#
|
2
|
+
# This source file is part of project: PcQueues
|
3
|
+
#
|
4
|
+
# A Proctoring Workflow Platform
|
5
|
+
#
|
6
|
+
# Copyright (c) ProctorCam Inc. 2014 All rights reserved
|
7
|
+
#
|
8
|
+
module PcQueues
|
9
|
+
# A QueueRuleSet is a collection of QueueRules that can is evaluated
|
10
|
+
# as an AND/OR clause depending on the value of is_any which indicates
|
11
|
+
# an OR
|
12
|
+
class QueueRuleSet < ActiveRecord::Base
|
13
|
+
# attr_accessible :is_any
|
14
|
+
def self.accessible_attributes
|
15
|
+
[:is_any]
|
16
|
+
end
|
17
|
+
belongs_to :queue
|
18
|
+
|
19
|
+
has_many :queue_rules, dependent: :destroy
|
20
|
+
|
21
|
+
def passes?(enqueuable, *args)
|
22
|
+
if is_any
|
23
|
+
any_pass?(enqueuable, *args)
|
24
|
+
else
|
25
|
+
all_pass?(enqueuable, *args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def any_pass?(enqueuable, *args)
|
30
|
+
queue_rules.all.each { |rule| return true if rule.passes?(enqueuable, *args) }
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def all_pass?(enqueuable, *args)
|
35
|
+
queue_rules.all.each { |rule| return false unless rule.passes?(enqueuable, *args) }
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#
|
2
|
+
# This source file is part of project: PcQueues
|
3
|
+
#
|
4
|
+
# Copyright (c) ProctorCam Inc. 2014 All rights reserved
|
5
|
+
#
|
6
|
+
|
7
|
+
module PcQueues
|
8
|
+
module QueueRules
|
9
|
+
###
|
10
|
+
# A BooleanQueueRule is a QueueRule that returns true when
|
11
|
+
# the value matches the bool_value attribute
|
12
|
+
#
|
13
|
+
class BooleanQueueRule < QueueRule
|
14
|
+
|
15
|
+
def self.options(opts = {})
|
16
|
+
opts[:bool_value] ||= opts[:value]
|
17
|
+
opts[:bool_value] = true if opts[:bool_value].nil?
|
18
|
+
opts.except! :value
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
# Implementors must provide an implementation of this method that takes
|
23
|
+
# the args splat passed to the queue and returns a numberic value
|
24
|
+
def value(obj, *args)
|
25
|
+
raise NotImplementedError, "The class: #{self.class.name} requires an implementation of #{__method__}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def passes?(obj, *args)
|
29
|
+
value(obj, *args) == bool_value
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# This source file is part of project: PcQueues
|
3
|
+
#
|
4
|
+
# Copyright (c) ProctorCam Inc. 2014 All rights reserved
|
5
|
+
#
|
6
|
+
|
7
|
+
module PcQueues
|
8
|
+
module QueueRules
|
9
|
+
###
|
10
|
+
# A NumericQueueRule is a QueueRule that returns true when
|
11
|
+
# comparing the value to the numeric_value using an operator
|
12
|
+
# stored as a string.
|
13
|
+
#
|
14
|
+
# These operators must be one of: <, >, ==, >=, <=
|
15
|
+
#
|
16
|
+
class NumericQueueRule < QueueRule
|
17
|
+
validates :string_value, inclusion: { in: %w[< > == <= >= !=] }
|
18
|
+
|
19
|
+
def self.options(opts = {})
|
20
|
+
opts[:numeric_value] ||= opts[:value]
|
21
|
+
opts[:string_value] ||= opts[:operator]
|
22
|
+
opts.except! :operator, :value
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
def operator
|
27
|
+
string_value
|
28
|
+
end
|
29
|
+
|
30
|
+
# Implementors must provide an implementation of this method that takes
|
31
|
+
# the args splat passed to the queue and returns a numberic value
|
32
|
+
def value(obj, *args)
|
33
|
+
raise NotImplementedError, "The class: #{self.class.name} requires an implementation of #{__method__}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def passes?(obj, *args)
|
37
|
+
value(obj, *args).send(operator, numeric_value)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#
|
2
|
+
# This source file is part of project: PcQueues
|
3
|
+
#
|
4
|
+
# Copyright (c) ProctorCam Inc. 2014 All rights reserved
|
5
|
+
#
|
6
|
+
|
7
|
+
module PcQueues
|
8
|
+
module QueueRules
|
9
|
+
###
|
10
|
+
# A SampleQueueRule is a QueueRule that will 'pass' for a perscribed
|
11
|
+
# sample of occurrences
|
12
|
+
#
|
13
|
+
# Sampling is random, so you many find slightly more or less than the perscribed
|
14
|
+
# number of items passing.
|
15
|
+
#
|
16
|
+
class SampleQueueRule < QueueRule
|
17
|
+
|
18
|
+
def self.options(opts = {})
|
19
|
+
opts[:numeric_value] ||= opts[:sample_percent]
|
20
|
+
opts.except! :sample_percent
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
@@die = Random.new
|
25
|
+
|
26
|
+
def sample_percent
|
27
|
+
numeric_value
|
28
|
+
end
|
29
|
+
|
30
|
+
def passes?(obj, *args)
|
31
|
+
sample_percent == 100 || @@die.rand(100) < sample_percent
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#
|
2
|
+
# This source file is part of project: PcQueues
|
3
|
+
#
|
4
|
+
# Copyright (c) ProctorCam Inc. 2014 All rights reserved
|
5
|
+
#
|
6
|
+
|
7
|
+
module PcQueues
|
8
|
+
module QueueRules
|
9
|
+
###
|
10
|
+
# A StringMatchQueueRule is a QueueRule that will 'pass' if the
|
11
|
+
# current value exactly matches a regular expression.
|
12
|
+
#
|
13
|
+
class StringMatchQueueRule < QueueRule
|
14
|
+
|
15
|
+
# Possible options for StringMatchQueueRule include:
|
16
|
+
#
|
17
|
+
# * _:regular_expression_ - an exact regular expression
|
18
|
+
# * _:contains_ - an exact string exists within the value
|
19
|
+
# * _:icontains_ - ignore case with string contained within the value
|
20
|
+
def self.options(opts = {})
|
21
|
+
opts[:string_value] ||= "/^#{opts[:regular_expression]}$/" if opts.key? :regular_expression
|
22
|
+
opts[:string_value] ||= "/#{opts[:contains]}/" if opts.key? :contains
|
23
|
+
opts[:string_value] ||= "/#{opts[:icontains]}/i" if opts.key? :icontains
|
24
|
+
opts.except! :regular_expression, :contains, :icontains
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def regular_expression
|
29
|
+
eval(string_value)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Implementors must provide an implementation of this method that takes
|
33
|
+
# the args splat passed to the queue and returns a String
|
34
|
+
def value(obj, *args)
|
35
|
+
raise NotImplementedError, "The class: #{self.class.name} requires an implementation of #{__method__}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def passes?(obj, *args)
|
39
|
+
!regular_expression.match(value(obj, *args)).nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe PcQueues::QueueRules::BooleanQueueRule do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
PcQueues.redis { |r| r.flushall }
|
7
|
+
|
8
|
+
@company = Company.create :name => "ProctorCam"
|
9
|
+
@company.create_hr_queue
|
10
|
+
|
11
|
+
@people = {
|
12
|
+
:john => Person.create(:name => "John", :age => 42, :is_female => false),
|
13
|
+
:sally => Person.create(:name => "Sally", :age => 29, :is_female => true),
|
14
|
+
:mildred => Person.create(:name => "Mildred", :age => 67, :is_female => true),
|
15
|
+
:lars => Person.create(:name => "Lars", :age => 34, :is_female => false)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
after :each do
|
20
|
+
@company.hr_queue.destroy
|
21
|
+
@company.destroy
|
22
|
+
end
|
23
|
+
|
24
|
+
it "does not pass if the boolean provided is different from the value passed" do
|
25
|
+
queue_rule = GenderQueueRule.create GenderQueueRule.options({:is_female => true})
|
26
|
+
expect(queue_rule.passes? @people[:john]).to eq(false)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "does should pass if the boolean provided is the same as the value passed" do
|
30
|
+
queue_rule = GenderQueueRule.create GenderQueueRule.options({:is_female => true})
|
31
|
+
expect(queue_rule.passes? @people[:mildred]).to eq(true)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe PcQueues::QueueRules::NumericQueueRule do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
PcQueues.redis { |r| r.flushall }
|
7
|
+
|
8
|
+
@company = Company.create :name => "ProctorCam"
|
9
|
+
@company.create_hr_queue
|
10
|
+
|
11
|
+
@people = {
|
12
|
+
:john => Person.create(:name => "John", :age => 42, :is_female => false),
|
13
|
+
:sally => Person.create(:name => "Sally", :age => 29, :is_female => true),
|
14
|
+
:mildred => Person.create(:name => "Mildred", :age => 67, :is_female => true),
|
15
|
+
:lars => Person.create(:name => "Lars", :age => 34, :is_female => false)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
after :each do
|
20
|
+
@company.hr_queue.destroy
|
21
|
+
@company.destroy
|
22
|
+
end
|
23
|
+
|
24
|
+
it "passes if the numeric value does pass the assertion" do
|
25
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:sally].age + 1, :operator => '>='})
|
26
|
+
expect(queue_rule.passes? @people[:john]).to eq(true)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "does not pass if the numeric value does not pass the assertion" do
|
30
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:sally].age + 1, :operator => '>='})
|
31
|
+
expect(queue_rule.passes? @people[:sally]).to eq(false)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "when using == operator" do
|
35
|
+
|
36
|
+
it "passes when values are the same" do
|
37
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age, :operator => '=='})
|
38
|
+
expect(queue_rule.passes? @people[:john]).to eq(true)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "does not pass when values are not the same" do
|
42
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age + 1, :operator => '=='})
|
43
|
+
expect(queue_rule.passes? @people[:john]).to eq(false)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "when using != operator" do
|
49
|
+
|
50
|
+
it "passes when values are the same" do
|
51
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age + 3, :operator => '!='})
|
52
|
+
expect(queue_rule.passes? @people[:john]).to eq(true)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "does not pass when values are not the same" do
|
56
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age, :operator => '!='})
|
57
|
+
expect(queue_rule.passes? @people[:john]).to eq(false)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "when using < operator" do
|
63
|
+
|
64
|
+
it "passes when values are the same" do
|
65
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age + 1, :operator => '<'})
|
66
|
+
expect(queue_rule.passes? @people[:john]).to eq(true)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "does not pass when values are not the same" do
|
70
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age, :operator => '<'})
|
71
|
+
expect(queue_rule.passes? @people[:john]).to eq(false)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "when using <= operator" do
|
77
|
+
|
78
|
+
it "passes when values are the same" do
|
79
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age + 1, :operator => '<='})
|
80
|
+
expect(queue_rule.passes? @people[:john]).to eq(true)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "does not pass when values are not the same" do
|
84
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age - 1, :operator => '<='})
|
85
|
+
expect(queue_rule.passes? @people[:john]).to eq(false)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "when using > operator" do
|
91
|
+
|
92
|
+
it "passes when values are the same" do
|
93
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age - 1, :operator => '>'})
|
94
|
+
expect(queue_rule.passes? @people[:john]).to eq(true)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "does not pass when values are not the same" do
|
98
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age, :operator => '>'})
|
99
|
+
expect(queue_rule.passes? @people[:john]).to eq(false)
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "when using >= operator" do
|
105
|
+
|
106
|
+
it "passes when values are the same" do
|
107
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age - 1, :operator => '>='})
|
108
|
+
expect(queue_rule.passes? @people[:john]).to eq(true)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "does not pass when values are not the same" do
|
112
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age + 1, :operator => '>='})
|
113
|
+
expect(queue_rule.passes? @people[:john]).to eq(false)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
it "does not take arbitrary assignments for operator" do
|
119
|
+
queue_rule = AgeQueueRule.create AgeQueueRule.options({:value => @people[:john].age - 1, :operator => 'NOT A THING'})
|
120
|
+
expect(queue_rule.valid?).to eq(false)
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|