cellect-server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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