pq-wsm 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 +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
|