cellect-server 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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +2 -0
  5. data/README.md +19 -0
  6. data/Rakefile +9 -0
  7. data/cellect-server.gemspec +40 -0
  8. data/cellect.gemspec +33 -0
  9. data/lib/cellect.rb +7 -0
  10. data/lib/cellect/node_set.rb +38 -0
  11. data/lib/cellect/server.rb +30 -0
  12. data/lib/cellect/server/adapters.rb +13 -0
  13. data/lib/cellect/server/adapters/default.rb +60 -0
  14. data/lib/cellect/server/adapters/postgres.rb +64 -0
  15. data/lib/cellect/server/api.rb +48 -0
  16. data/lib/cellect/server/api/helpers.rb +44 -0
  17. data/lib/cellect/server/api/sets.rb +21 -0
  18. data/lib/cellect/server/api/users.rb +32 -0
  19. data/lib/cellect/server/grouped_project.rb +65 -0
  20. data/lib/cellect/server/node_set.rb +19 -0
  21. data/lib/cellect/server/project.rb +123 -0
  22. data/lib/cellect/server/user.rb +66 -0
  23. data/lib/cellect/version.rb +3 -0
  24. data/spec/fixtures/project_data/grouped_pairwise_priority.json +109 -0
  25. data/spec/fixtures/project_data/grouped_pairwise_random.json +89 -0
  26. data/spec/fixtures/project_data/grouped_priority.json +59 -0
  27. data/spec/fixtures/project_data/grouped_random.json +49 -0
  28. data/spec/fixtures/project_data/pairwise_priority.json +49 -0
  29. data/spec/fixtures/project_data/pairwise_random.json +39 -0
  30. data/spec/fixtures/project_data/priority.json +49 -0
  31. data/spec/fixtures/project_data/random.json +39 -0
  32. data/spec/fixtures/user_data/complete_user.json +118 -0
  33. data/spec/fixtures/user_data/new_user.json +26 -0
  34. data/spec/fixtures/user_data/partial_user.json +58 -0
  35. data/spec/server/api/add_seen_spec.rb +26 -0
  36. data/spec/server/api/add_spec.rb +40 -0
  37. data/spec/server/api/remove_spec.rb +35 -0
  38. data/spec/server/api/sample_spec.rb +34 -0
  39. data/spec/server/api/user_load_spec.rb +25 -0
  40. data/spec/server/grouped_project_spec.rb +76 -0
  41. data/spec/server/node_set_spec.rb +13 -0
  42. data/spec/server/project_spec.rb +62 -0
  43. data/spec/server/server_spec.rb +24 -0
  44. data/spec/server/user_spec.rb +32 -0
  45. data/spec/spec_helper.rb +43 -0
  46. data/spec/support/cellect_helper.rb +12 -0
  47. data/spec/support/shared_api_context.rb +11 -0
  48. data/spec/support/shared_examples_for_node_set.rb +27 -0
  49. data/spec/support/shared_examples_for_project.rb +26 -0
  50. data/spec/support/shared_examples_for_set.rb +34 -0
  51. data/spec/support/spec_adapter.rb +43 -0
  52. data/spec/support/zk_setup.rb +26 -0
  53. metadata +337 -0
@@ -0,0 +1,26 @@
1
+ {
2
+ "random": [
3
+
4
+ ],
5
+ "priority": [
6
+
7
+ ],
8
+ "grouped_random": [
9
+
10
+ ],
11
+ "grouped_priority": [
12
+
13
+ ],
14
+ "pairwise_random": [
15
+
16
+ ],
17
+ "pairwise_priority": [
18
+
19
+ ],
20
+ "grouped_pairwise_random": [
21
+
22
+ ],
23
+ "grouped_pairwise_priority": [
24
+
25
+ ]
26
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "random": [
3
+ 1,
4
+ 2,
5
+ 3,
6
+ 4,
7
+ 5
8
+ ],
9
+ "priority": [
10
+ 7,
11
+ 6,
12
+ 1,
13
+ 3,
14
+ 10
15
+ ],
16
+ "grouped_random": [
17
+ 1,
18
+ 2,
19
+ 3,
20
+ 4,
21
+ 5
22
+ ],
23
+ "grouped_priority": [
24
+ 8,
25
+ 7,
26
+ 10,
27
+ 5,
28
+ 6
29
+ ],
30
+ "pairwise_random": [
31
+ 1,
32
+ 2,
33
+ 3,
34
+ 4,
35
+ 5
36
+ ],
37
+ "pairwise_priority": [
38
+ 10,
39
+ 6,
40
+ 5,
41
+ 2,
42
+ 4
43
+ ],
44
+ "grouped_pairwise_random": [
45
+ 1,
46
+ 2,
47
+ 3,
48
+ 4,
49
+ 5
50
+ ],
51
+ "grouped_pairwise_priority": [
52
+ 16,
53
+ 17,
54
+ 3,
55
+ 8,
56
+ 7
57
+ ]
58
+ }
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe API do
5
+ include_context 'API'
6
+
7
+ { 'Ungrouped' => nil, 'Grouped' => 'grouped' }.each_pair do |grouping_type, grouping|
8
+ SET_TYPES.shuffle.each do |set_type|
9
+ context "#{ grouping_type } #{ set_type }" do
10
+ let(:project_type){ [grouping, set_type].compact.join '_' }
11
+ let(:project){ Project[project_type] }
12
+ let(:user){ project.user 123 }
13
+ before(:each){ pass_until project, is: :ready }
14
+
15
+ it 'should add seen subjects' do
16
+ async_project = double
17
+ project.should_receive(:async).and_return async_project
18
+ async_project.should_receive(:add_seen_for).with 123, 123
19
+ put "/projects/#{ project_type }/users/123/add_seen", subject_id: 123
20
+ last_response.status.should == 200
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe API do
5
+ include_context 'API'
6
+
7
+ { 'Ungrouped' => nil, 'Grouped' => 'grouped' }.each_pair do |grouping_type, grouping|
8
+ SET_TYPES.shuffle.each do |set_type|
9
+ context "#{ grouping_type } #{ set_type }" do
10
+ let(:project_type){ [grouping, set_type].compact.join '_' }
11
+ let(:project){ Project[project_type] }
12
+ let(:user){ project.user 123 }
13
+ before(:each){ pass_until project, is: :ready }
14
+
15
+ let(:opts) do
16
+ { subject_id: 123 }.tap do |h|
17
+ h[:priority] = 456.0 if project.prioritized?
18
+ h[:group_id] = 1 if project.grouped?
19
+ end
20
+ end
21
+
22
+ it 'should add subjects' do
23
+ if project.grouped? && project.prioritized?
24
+ project.should_receive(:add).with subject_id: 123, group_id: 1, priority: 456.0
25
+ elsif project.grouped?
26
+ project.should_receive(:add).with subject_id: 123, group_id: 1, priority: nil
27
+ elsif project.prioritized?
28
+ project.should_receive(:add).with subject_id: 123, group_id: nil, priority: 456.0
29
+ else
30
+ project.should_receive(:add).with subject_id: 123, group_id: nil, priority: nil
31
+ end
32
+
33
+ put "/projects/#{ project_type }/add", opts
34
+ last_response.status.should == 200
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe API do
5
+ include_context 'API'
6
+
7
+ { 'Ungrouped' => nil, 'Grouped' => 'grouped' }.each_pair do |grouping_type, grouping|
8
+ SET_TYPES.shuffle.each do |set_type|
9
+ context "#{ grouping_type } #{ set_type }" do
10
+ let(:project_type){ [grouping, set_type].compact.join '_' }
11
+ let(:project){ Project[project_type] }
12
+ let(:user){ project.user 123 }
13
+ before(:each){ pass_until project, is: :ready }
14
+
15
+ let(:opts) do
16
+ { subject_id: 123 }.tap do |h|
17
+ h[:group_id] = 1 if project.grouped?
18
+ end
19
+ end
20
+
21
+ it 'should remove subjects' do
22
+ if project.grouped?
23
+ project.should_receive(:remove).with subject_id: 123, group_id: 1, priority: nil
24
+ else
25
+ project.should_receive(:remove).with subject_id: 123, group_id: nil, priority: nil
26
+ end
27
+
28
+ put "/projects/#{ project_type }/remove", opts
29
+ last_response.status.should == 200
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe API do
5
+ include_context 'API'
6
+
7
+ { 'Ungrouped' => nil, 'Grouped' => 'grouped' }.each_pair do |grouping_type, grouping|
8
+ SET_TYPES.shuffle.each do |set_type|
9
+ context "#{ grouping_type } #{ set_type }" do
10
+ let(:project_type){ [grouping, set_type].compact.join '_' }
11
+ let(:project){ Project[project_type] }
12
+ let(:user){ project.user 123 }
13
+ before(:each){ pass_until project, is: :ready }
14
+
15
+ it 'should sample without a user, limit, or group' do
16
+ project.should_receive(:sample).with(limit: 5, user_id: nil, group_id: nil).and_call_original
17
+ get "/projects/#{ project_type }"
18
+ last_response.status.should == 200
19
+ json.should be_a Array
20
+ end
21
+
22
+ shoulda = grouping ? 'limit, group, and user' : 'limit and user'
23
+ it "should sample with a #{ shoulda }" do
24
+ group_id = grouping ? 1 : nil
25
+ project.should_receive(:sample).with(limit: 3, user_id: 123, group_id: group_id).and_call_original
26
+ get "/projects/#{ project_type }?limit=3&user_id=123#{ grouping ? '&group_id=1' : '' }"
27
+ last_response.status.should == 200
28
+ json.should be_a Array
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe API do
5
+ include_context 'API'
6
+
7
+ { 'Ungrouped' => nil, 'Grouped' => 'grouped' }.each_pair do |grouping_type, grouping|
8
+ SET_TYPES.shuffle.each do |set_type|
9
+ context "#{ grouping_type } #{ set_type }" do
10
+ let(:project_type){ [grouping, set_type].compact.join '_' }
11
+ let(:project){ Project[project_type] }
12
+ before(:each){ pass_until project, is: :ready }
13
+
14
+ it 'should load users' do
15
+ async_project = double
16
+ project.should_receive(:async).and_return async_project
17
+ async_project.should_receive(:user).with 123
18
+ post "/projects/#{ project_type }/users/123/load"
19
+ last_response.status.should == 201
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe GroupedProject do
5
+ SET_TYPES.collect{ |type| "grouped_#{ type }" }.each do |project_type|
6
+ context project_type do
7
+ it_behaves_like 'project', :project
8
+ let(:project){ GroupedProject[project_type] }
9
+ let(:user){ project.user 123 }
10
+ let(:set_klass){ project.prioritized? ? DiffSet::PrioritySet : DiffSet::RandomSet }
11
+ before(:each){ pass_until project, is: :ready }
12
+
13
+ it 'should provide unseen from a random group for users' do
14
+ project.groups = { }
15
+ project.groups[1] = set_klass.new
16
+ project.groups[1].should_receive(:subtract).with user.seen, 3
17
+ project.unseen_for 123, limit: 3
18
+ end
19
+
20
+ it 'should provide unseen from a specific group for users' do
21
+ 3.times{ |i| project.groups[i] = set_klass.new }
22
+ project.group(1).should_receive(:subtract).with user.seen, 3
23
+ project.unseen_for 123, group_id: 1, limit: 3
24
+ end
25
+
26
+ it 'should sample subjects from a random group without a user' do
27
+ project.groups = { }
28
+ project.groups[1] = set_klass.new
29
+ project.group(1).should_receive(:sample).with 3
30
+ project.sample limit: 3
31
+ end
32
+
33
+ it 'should sample subjects from a specific group without a user' do
34
+ 3.times{ |i| project.groups[i] = set_klass.new }
35
+ project.group(1).should_receive(:sample).with 3
36
+ project.sample group_id: 1, limit: 3
37
+ end
38
+
39
+ it 'should sample subjects from a random group for a user' do
40
+ project.groups = { }
41
+ project.groups[1] = set_klass.new
42
+ project.groups[1].should_receive(:subtract).with user.seen, 3
43
+ project.sample user_id: 123, limit: 3
44
+ end
45
+
46
+ it 'should sample subjects from a specific group for a user' do
47
+ 3.times{ |i| project.groups[i] = set_klass.new }
48
+ project.group(1).should_receive(:subtract).with user.seen, 3
49
+ project.sample user_id: 123, group_id: 1, limit: 3
50
+ end
51
+
52
+ it 'should add subjects' do
53
+ project.groups[1] = set_klass.new
54
+
55
+ if project.prioritized?
56
+ project.groups[1].should_receive(:add).with 123, 456
57
+ project.add subject_id: 123, group_id: 1, priority: 456
58
+ else
59
+ project.groups[1].should_receive(:add).with 123
60
+ project.add subject_id: 123, group_id: 1
61
+ end
62
+ end
63
+
64
+ it 'should remove subjects' do
65
+ project.groups[1] = set_klass.new
66
+ project.groups[1].should_receive(:remove).with 123
67
+ project.remove subject_id: 123, group_id: 1
68
+ end
69
+
70
+ it 'should be grouped' do
71
+ project.should be_grouped
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe NodeSet do
5
+ it_behaves_like 'node set'
6
+ let(:node_set){ Cellect::Server.node_set.actors.first }
7
+
8
+ it 'should register this node' do
9
+ node_set.id.should == 'node0000000000'
10
+ node_set.zk.get('/nodes/node0000000000').first.should =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe Project do
5
+ SET_TYPES.each do |project_type|
6
+ context project_type do
7
+ it_behaves_like 'project', :project
8
+ let(:project){ Project[project_type] }
9
+ let(:user){ project.user 123 }
10
+ before(:each){ pass_until project, is: :ready }
11
+
12
+ it 'should provide unseen for users' do
13
+ project.subjects.should_receive(:subtract).with user.seen, 3
14
+ project.unseen_for 123, limit: 3
15
+ end
16
+
17
+ it 'should sample subjects without a user' do
18
+ project.subjects.should_receive(:sample).with 3
19
+ project.sample limit: 3
20
+ end
21
+
22
+ it 'should sample subjects with a user' do
23
+ project.subjects.should_receive(:subtract).with user.seen, 3
24
+ project.sample user_id: 123, limit: 3
25
+ end
26
+
27
+ it 'should add subjects' do
28
+ if project.prioritized?
29
+ project.subjects.should_receive(:add).with 123, 456
30
+ project.add subject_id: 123, priority: 456
31
+ else
32
+ project.subjects.should_receive(:add).with 123
33
+ project.add subject_id: 123
34
+ end
35
+ end
36
+
37
+ it 'should remove subjects' do
38
+ project.subjects.should_receive(:add).with 123
39
+ project.add subject_id: 123
40
+ end
41
+
42
+ it 'should be notified of a user ttl expiry' do
43
+ async_project = double
44
+ project.should_receive(:async).and_return async_project
45
+ async_project.should_receive(:remove_user).with user.id
46
+ user.ttl_expired!
47
+ end
48
+
49
+ it 'should remove users when their ttl expires' do
50
+ id = user.id
51
+ project.remove_user id
52
+ project.users.should_not have_key id
53
+ expect{ user.id }.to raise_error
54
+ end
55
+
56
+ it 'should not be grouped' do
57
+ project.should_not be_grouped
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe Cellect do
5
+ context 'default adapter' do
6
+ let(:default){ Cellect::Server::Adapters::Default.new }
7
+
8
+ it 'should raise a NotImplementedError when using the default adapter' do
9
+ expect{ default.project_list }.to raise_error NotImplementedError
10
+ expect{ default.load_data_for(Project.new('test')) }.to raise_error NotImplementedError
11
+ expect{ default.load_user 'random', 123 }.to raise_error NotImplementedError
12
+ end
13
+
14
+ it 'should return a project given a set of options' do
15
+ default.project_for('name' => 'a').should be_an_instance_of Project
16
+ default.project_for('name' => 'b', 'grouped' => true).should be_an_instance_of GroupedProject
17
+ default.project_for('name' => 'c', 'pairwise' => true).should be_pairwise
18
+ default.project_for('name' => 'd', 'prioritized' => true).should be_prioritized
19
+ default.project_for('name' => 'e', 'pairwise' => true, 'prioritized' => true).should be_pairwise
20
+ default.project_for('name' => 'e', 'pairwise' => true, 'prioritized' => true).should be_prioritized
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe User do
5
+ let(:user){ User.new 1, project_name: 'random' }
6
+
7
+ it 'should store seen ids' do
8
+ user.seen.should be_a DiffSet::RandomSet
9
+ end
10
+
11
+ it 'should have a default ttl of 15 minutes' do
12
+ user.ttl.should == 900 # seconds
13
+ end
14
+
15
+ it 'should allow custom ttl' do
16
+ User.new(2, project_name: 'random', ttl: 123).ttl.should == 123
17
+ end
18
+
19
+ it 'should reset the ttl timer on activity' do
20
+ user.bare_object.should_receive(:restart_ttl_timer).at_least :once
21
+ user.seen
22
+ end
23
+
24
+ it 'should terminate on ttl expiry' do
25
+ async_project = double
26
+ Project[user.project_name].should_receive(:async).and_return async_project
27
+ async_project.should_receive(:remove_user).with user.id
28
+ user.ttl_expired!
29
+ user.ttl_timer.should be_nil
30
+ end
31
+ end
32
+ end