cellect-server 0.1.3 → 1.0.0
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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +2 -8
- data/cellect-server.gemspec +4 -4
- data/cellect.gemspec +4 -3
- data/lib/cellect.rb +1 -1
- data/lib/cellect/node_set.rb +12 -12
- data/lib/cellect/server.rb +4 -4
- data/lib/cellect/server/adapters.rb +2 -2
- data/lib/cellect/server/adapters/default.rb +6 -6
- data/lib/cellect/server/adapters/postgres.rb +5 -5
- data/lib/cellect/server/api.rb +9 -9
- data/lib/cellect/server/api/helpers.rb +5 -5
- data/lib/cellect/server/api/sets.rb +2 -2
- data/lib/cellect/server/api/users.rb +5 -5
- data/lib/cellect/server/grouped_workflow.rb +10 -10
- data/lib/cellect/server/node_set.rb +2 -2
- data/lib/cellect/server/user.rb +10 -10
- data/lib/cellect/server/workflow.rb +21 -21
- data/lib/cellect/version.rb +1 -1
- data/spec/server/api/add_seen_spec.rb +2 -2
- data/spec/server/api/add_spec.rb +4 -4
- data/spec/server/api/remove_spec.rb +4 -4
- data/spec/server/api/sample_spec.rb +3 -3
- data/spec/server/api/user_load_spec.rb +2 -2
- data/spec/server/grouped_workflow_spec.rb +10 -10
- data/spec/server/node_set_spec.rb +1 -1
- data/spec/server/server_spec.rb +2 -2
- data/spec/server/user_spec.rb +5 -5
- data/spec/server/workflow_spec.rb +9 -9
- data/spec/spec_helper.rb +3 -4
- data/spec/support/shared_api_context.rb +2 -2
- data/spec/support/shared_examples_for_node_set.rb +3 -3
- data/spec/support/shared_examples_for_set.rb +6 -6
- data/spec/support/shared_examples_for_workflow.rb +5 -5
- data/spec/support/spec_adapter.rb +6 -6
- data/spec/support/zk_setup.rb +48 -25
- metadata +3 -3
data/lib/cellect/server/user.rb
CHANGED
@@ -3,14 +3,14 @@ module Cellect
|
|
3
3
|
class User
|
4
4
|
include Celluloid
|
5
5
|
include Celluloid::Logger
|
6
|
-
|
6
|
+
|
7
7
|
# Gracefully exit when the actor dies
|
8
8
|
trap_exit :workflow_crashed
|
9
9
|
finalizer :cancel_ttl_timer
|
10
|
-
|
10
|
+
|
11
11
|
attr_accessor :id, :workflow_name, :seen, :state
|
12
12
|
attr_accessor :ttl, :ttl_timer
|
13
|
-
|
13
|
+
|
14
14
|
# Sets up a new user with an empty seen set, then loads the actual data
|
15
15
|
def initialize(id, workflow_name: nil, ttl: nil)
|
16
16
|
self.id = id
|
@@ -20,7 +20,7 @@ module Cellect
|
|
20
20
|
@ttl = ttl
|
21
21
|
load_data
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
# Load the seen subjects for a user and restarts the TTL
|
25
25
|
def load_data
|
26
26
|
data = Cellect::Server.adapter.load_user(workflow_name, id) || []
|
@@ -30,36 +30,36 @@ module Cellect
|
|
30
30
|
self.state = :ready
|
31
31
|
restart_ttl_timer
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
# Returns the seen subjects set
|
35
35
|
def seen
|
36
36
|
restart_ttl_timer
|
37
37
|
@seen
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
# (Re)starts the inactivity countdown
|
41
41
|
def restart_ttl_timer
|
42
42
|
self.ttl_timer ||= after(ttl){ ttl_expired! }
|
43
43
|
ttl_timer.reset
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
# Releases the timer
|
47
47
|
def cancel_ttl_timer
|
48
48
|
ttl_timer.cancel if ttl_timer
|
49
49
|
self.ttl_timer = nil
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
# Removes the user from inactivity
|
53
53
|
def ttl_expired!
|
54
54
|
debug "User #{ id } TTL expired"
|
55
55
|
cancel_ttl_timer
|
56
56
|
Workflow[workflow_name].async.remove_user(id)
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
def ttl
|
60
60
|
@ttl || 60 * 15 # 15 minutes
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
# Handle errors and let the actor die
|
64
64
|
def workflow_crashed(actor, reason)
|
65
65
|
cancel_ttl_timer
|
@@ -2,33 +2,33 @@ module Cellect
|
|
2
2
|
module Server
|
3
3
|
class Workflow
|
4
4
|
include Celluloid
|
5
|
-
|
5
|
+
|
6
6
|
attr_accessor :name, :users, :subjects, :state
|
7
7
|
attr_accessor :pairwise, :prioritized
|
8
|
-
|
8
|
+
|
9
9
|
# Look up and/or load a workflow
|
10
10
|
def self.[](name)
|
11
11
|
Cellect::Server.adapter.load_workflows(name) unless Actor[name]
|
12
12
|
Actor[name].actors.first
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
# Load a workflow
|
16
16
|
def self.[]=(name, opts)
|
17
17
|
Actor[name] = supervise name, pairwise: opts['pairwise'], prioritized: opts['prioritized']
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
# The names of all workflows currently loaded
|
21
21
|
def self.names
|
22
22
|
actor_names = Celluloid.actor_system.registry.names.collect &:to_s
|
23
23
|
workflow_actors = actor_names.select{ |key| key =~ /^workflow_/ }
|
24
24
|
workflow_actors.collect{ |name| name.sub(/^workflow_/, '').to_sym }
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
# All currently loaded workflows
|
28
28
|
def self.all
|
29
29
|
names.collect{ |name| Workflow[name] }
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
# Sets up a new workflow and starts the data loading
|
33
33
|
def initialize(name, pairwise: false, prioritized: false)
|
34
34
|
self.name = name
|
@@ -38,7 +38,7 @@ module Cellect
|
|
38
38
|
self.subjects = set_klass.new
|
39
39
|
load_data
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
# Loads subjects from the adapter
|
43
43
|
def load_data
|
44
44
|
self.state = :initializing
|
@@ -48,31 +48,31 @@ module Cellect
|
|
48
48
|
end
|
49
49
|
self.state = :ready
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
# Look up and/or load a user
|
53
53
|
def user(id)
|
54
54
|
self.users[id] ||= User.supervise id, workflow_name: name
|
55
55
|
users[id].actors.first
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
# Get unseen subjects for a user
|
59
59
|
def unseen_for(user_id, limit: 5)
|
60
60
|
subjects.subtract user(user_id).seen, limit
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
# Add subjects to a users seen set
|
64
64
|
def add_seen_for(user_id, *subject_ids)
|
65
65
|
[subject_ids].flatten.compact.each do |subject_id|
|
66
66
|
user(user_id).seen.add subject_id
|
67
67
|
end
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
# Unload a user
|
71
71
|
def remove_user(user_id)
|
72
72
|
removed = self.users.delete user_id
|
73
73
|
removed.terminate if removed
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
# Get a sample of subjects for a user
|
77
77
|
#
|
78
78
|
# Accepts a hash in the form:
|
@@ -87,7 +87,7 @@ module Cellect
|
|
87
87
|
subjects.sample opts[:limit]
|
88
88
|
end
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
# Adds or updates a subject
|
92
92
|
#
|
93
93
|
# Accepts a hash in the form:
|
@@ -102,7 +102,7 @@ module Cellect
|
|
102
102
|
subjects.add opts[:subject_id]
|
103
103
|
end
|
104
104
|
end
|
105
|
-
|
105
|
+
|
106
106
|
# Removes a subject
|
107
107
|
#
|
108
108
|
# Accepts a hash in the form:
|
@@ -112,23 +112,23 @@ module Cellect
|
|
112
112
|
def remove(opts = { })
|
113
113
|
subjects.remove opts[:subject_id]
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
def pairwise?
|
117
117
|
!!pairwise
|
118
118
|
end
|
119
|
-
|
119
|
+
|
120
120
|
def prioritized?
|
121
121
|
!!prioritized
|
122
122
|
end
|
123
|
-
|
123
|
+
|
124
124
|
def grouped?
|
125
125
|
false
|
126
126
|
end
|
127
|
-
|
127
|
+
|
128
128
|
def ready?
|
129
129
|
state == :ready
|
130
130
|
end
|
131
|
-
|
131
|
+
|
132
132
|
# Provide a lookup for matching sets to workflow criteria
|
133
133
|
SET_KLASS = {
|
134
134
|
# priority, pairwise
|
@@ -137,12 +137,12 @@ module Cellect
|
|
137
137
|
[ true, false ] => DiffSet::PrioritySet,
|
138
138
|
[ true, true ] => DiffSet::PairwisePrioritySet
|
139
139
|
}
|
140
|
-
|
140
|
+
|
141
141
|
# Looks up the set class
|
142
142
|
def set_klass
|
143
143
|
SET_KLASS[[prioritized, pairwise]]
|
144
144
|
end
|
145
|
-
|
145
|
+
|
146
146
|
# General information about this workflow
|
147
147
|
def status
|
148
148
|
{
|
data/lib/cellect/version.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
module Cellect::Server
|
4
4
|
describe API do
|
5
5
|
include_context 'API'
|
6
|
-
|
6
|
+
|
7
7
|
{ 'Ungrouped' => nil, 'Grouped' => 'grouped' }.each_pair do |grouping_type, grouping|
|
8
8
|
SET_TYPES.shuffle.each do |set_type|
|
9
9
|
context "#{ grouping_type } #{ set_type }" do
|
@@ -11,7 +11,7 @@ module Cellect::Server
|
|
11
11
|
let(:workflow){ Workflow[workflow_type] }
|
12
12
|
let(:user){ workflow.user 123 }
|
13
13
|
before(:each){ pass_until workflow, is: :ready }
|
14
|
-
|
14
|
+
|
15
15
|
it 'should add seen subjects' do
|
16
16
|
async_workflow = double
|
17
17
|
expect(workflow).to receive(:async).and_return async_workflow
|
data/spec/server/api/add_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
module Cellect::Server
|
4
4
|
describe API do
|
5
5
|
include_context 'API'
|
6
|
-
|
6
|
+
|
7
7
|
{ 'Ungrouped' => nil, 'Grouped' => 'grouped' }.each_pair do |grouping_type, grouping|
|
8
8
|
SET_TYPES.shuffle.each do |set_type|
|
9
9
|
context "#{ grouping_type } #{ set_type }" do
|
@@ -11,14 +11,14 @@ module Cellect::Server
|
|
11
11
|
let(:workflow){ Workflow[workflow_type] }
|
12
12
|
let(:user){ workflow.user 123 }
|
13
13
|
before(:each){ pass_until workflow, is: :ready }
|
14
|
-
|
14
|
+
|
15
15
|
let(:opts) do
|
16
16
|
{ subject_id: 123 }.tap do |h|
|
17
17
|
h[:priority] = 456.0 if workflow.prioritized?
|
18
18
|
h[:group_id] = 1 if workflow.grouped?
|
19
19
|
end
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
it 'should add subjects' do
|
23
23
|
if workflow.grouped? && workflow.prioritized?
|
24
24
|
expect(workflow).to receive(:add).with subject_id: 123, group_id: 1, priority: 456.0
|
@@ -29,7 +29,7 @@ module Cellect::Server
|
|
29
29
|
else
|
30
30
|
expect(workflow).to receive(:add).with subject_id: 123, group_id: nil, priority: nil
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
put "/workflows/#{ workflow_type }/add", opts
|
34
34
|
expect(last_response.status).to eq 200
|
35
35
|
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
module Cellect::Server
|
4
4
|
describe API do
|
5
5
|
include_context 'API'
|
6
|
-
|
6
|
+
|
7
7
|
{ 'Ungrouped' => nil, 'Grouped' => 'grouped' }.each_pair do |grouping_type, grouping|
|
8
8
|
SET_TYPES.shuffle.each do |set_type|
|
9
9
|
context "#{ grouping_type } #{ set_type }" do
|
@@ -11,20 +11,20 @@ module Cellect::Server
|
|
11
11
|
let(:workflow){ Workflow[workflow_type] }
|
12
12
|
let(:user){ workflow.user 123 }
|
13
13
|
before(:each){ pass_until workflow, is: :ready }
|
14
|
-
|
14
|
+
|
15
15
|
let(:opts) do
|
16
16
|
{ subject_id: 123 }.tap do |h|
|
17
17
|
h[:group_id] = 1 if workflow.grouped?
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
it 'should remove subjects' do
|
22
22
|
if workflow.grouped?
|
23
23
|
expect(workflow).to receive(:remove).with subject_id: 123, group_id: 1, priority: nil
|
24
24
|
else
|
25
25
|
expect(workflow).to receive(:remove).with subject_id: 123, group_id: nil, priority: nil
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
put "/workflows/#{ workflow_type }/remove", opts
|
29
29
|
expect(last_response.status).to eq 200
|
30
30
|
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
module Cellect::Server
|
4
4
|
describe API do
|
5
5
|
include_context 'API'
|
6
|
-
|
6
|
+
|
7
7
|
{ 'Ungrouped' => nil, 'Grouped' => 'grouped' }.each_pair do |grouping_type, grouping|
|
8
8
|
SET_TYPES.shuffle.each do |set_type|
|
9
9
|
context "#{ grouping_type } #{ set_type }" do
|
@@ -11,14 +11,14 @@ module Cellect::Server
|
|
11
11
|
let(:workflow){ Workflow[workflow_type] }
|
12
12
|
let(:user){ workflow.user 123 }
|
13
13
|
before(:each){ pass_until workflow, is: :ready }
|
14
|
-
|
14
|
+
|
15
15
|
it 'should sample without a user, limit, or group' do
|
16
16
|
expect(workflow).to receive(:sample).with(limit: 5, user_id: nil, group_id: nil).and_call_original
|
17
17
|
get "/workflows/#{ workflow_type }"
|
18
18
|
expect(last_response.status).to eq 200
|
19
19
|
expect(json).to be_a Array
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
shoulda = grouping ? 'limit, group, and user' : 'limit and user'
|
23
23
|
it "should sample with a #{ shoulda }" do
|
24
24
|
group_id = grouping ? 1 : nil
|
@@ -3,14 +3,14 @@ require 'spec_helper'
|
|
3
3
|
module Cellect::Server
|
4
4
|
describe API do
|
5
5
|
include_context 'API'
|
6
|
-
|
6
|
+
|
7
7
|
{ 'Ungrouped' => nil, 'Grouped' => 'grouped' }.each_pair do |grouping_type, grouping|
|
8
8
|
SET_TYPES.shuffle.each do |set_type|
|
9
9
|
context "#{ grouping_type } #{ set_type }" do
|
10
10
|
let(:workflow_type){ [grouping, set_type].compact.join '_' }
|
11
11
|
let(:workflow){ Workflow[workflow_type] }
|
12
12
|
before(:each){ pass_until workflow, is: :ready }
|
13
|
-
|
13
|
+
|
14
14
|
it 'should load users' do
|
15
15
|
async_workflow = double
|
16
16
|
expect(workflow).to receive(:async).and_return async_workflow
|
@@ -9,49 +9,49 @@ module Cellect::Server
|
|
9
9
|
let(:user){ workflow.user 123 }
|
10
10
|
let(:set_klass){ workflow.prioritized? ? DiffSet::PrioritySet : DiffSet::RandomSet }
|
11
11
|
before(:each){ pass_until workflow, is: :ready }
|
12
|
-
|
12
|
+
|
13
13
|
it 'should provide unseen from a random group for users' do
|
14
14
|
workflow.groups = { }
|
15
15
|
workflow.groups[1] = set_klass.new
|
16
16
|
expect(workflow.groups[1]).to receive(:subtract).with user.seen, 3
|
17
17
|
workflow.unseen_for 123, limit: 3
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
it 'should provide unseen from a specific group for users' do
|
21
21
|
3.times{ |i| workflow.groups[i] = set_klass.new }
|
22
22
|
expect(workflow.group(1)).to receive(:subtract).with user.seen, 3
|
23
23
|
workflow.unseen_for 123, group_id: 1, limit: 3
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
it 'should sample subjects from a random group without a user' do
|
27
27
|
workflow.groups = { }
|
28
28
|
workflow.groups[1] = set_klass.new
|
29
29
|
expect(workflow.group(1)).to receive(:sample).with 3
|
30
30
|
workflow.sample limit: 3
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
it 'should sample subjects from a specific group without a user' do
|
34
34
|
3.times{ |i| workflow.groups[i] = set_klass.new }
|
35
35
|
expect(workflow.group(1)).to receive(:sample).with 3
|
36
36
|
workflow.sample group_id: 1, limit: 3
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
it 'should sample subjects from a random group for a user' do
|
40
40
|
workflow.groups = { }
|
41
41
|
workflow.groups[1] = set_klass.new
|
42
42
|
expect(workflow.groups[1]).to receive(:subtract).with user.seen, 3
|
43
43
|
workflow.sample user_id: 123, limit: 3
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
it 'should sample subjects from a specific group for a user' do
|
47
47
|
3.times{ |i| workflow.groups[i] = set_klass.new }
|
48
48
|
expect(workflow.group(1)).to receive(:subtract).with user.seen, 3
|
49
49
|
workflow.sample user_id: 123, group_id: 1, limit: 3
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
it 'should add subjects' do
|
53
53
|
workflow.groups[1] = set_klass.new
|
54
|
-
|
54
|
+
|
55
55
|
if workflow.prioritized?
|
56
56
|
expect(workflow.groups[1]).to receive(:add).with 123, 456
|
57
57
|
workflow.add subject_id: 123, group_id: 1, priority: 456
|
@@ -60,13 +60,13 @@ module Cellect::Server
|
|
60
60
|
workflow.add subject_id: 123, group_id: 1
|
61
61
|
end
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
64
|
it 'should remove subjects' do
|
65
65
|
workflow.groups[1] = set_klass.new
|
66
66
|
expect(workflow.groups[1]).to receive(:remove).with 123
|
67
67
|
workflow.remove subject_id: 123, group_id: 1
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
it 'should be grouped' do
|
71
71
|
expect(workflow).to be_grouped
|
72
72
|
end
|
@@ -4,7 +4,7 @@ module Cellect::Server
|
|
4
4
|
describe NodeSet do
|
5
5
|
it_behaves_like 'node set'
|
6
6
|
let(:node_set){ Cellect::Server.node_set.actors.first }
|
7
|
-
|
7
|
+
|
8
8
|
it 'should register this node' do
|
9
9
|
expect(node_set.id).to eq 'node0000000000'
|
10
10
|
expect(node_set.zk.get('/nodes/node0000000000').first).to match /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|