cellect-server 0.0.1 → 0.0.2
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/lib/cellect/server/adapters/default.rb +15 -15
- data/lib/cellect/server/adapters/postgres.rb +6 -6
- data/lib/cellect/server/api/helpers.rb +2 -2
- data/lib/cellect/server/api/sets.rb +3 -3
- data/lib/cellect/server/api/users.rb +2 -2
- data/lib/cellect/server/api.rb +6 -6
- data/lib/cellect/server/{grouped_project.rb → grouped_workflow.rb} +1 -1
- data/lib/cellect/server/node_set.rb +3 -2
- data/lib/cellect/server/user.rb +8 -8
- data/lib/cellect/server/{project.rb → workflow.rb} +6 -6
- data/lib/cellect/server.rb +4 -4
- data/lib/cellect/version.rb +1 -1
- data/spec/fixtures/{project_data → workflow_data}/grouped_pairwise_priority.json +0 -0
- data/spec/fixtures/{project_data → workflow_data}/grouped_pairwise_random.json +0 -0
- data/spec/fixtures/{project_data → workflow_data}/grouped_priority.json +0 -0
- data/spec/fixtures/{project_data → workflow_data}/grouped_random.json +0 -0
- data/spec/fixtures/{project_data → workflow_data}/pairwise_priority.json +0 -0
- data/spec/fixtures/{project_data → workflow_data}/pairwise_random.json +0 -0
- data/spec/fixtures/{project_data → workflow_data}/priority.json +0 -0
- data/spec/fixtures/{project_data → workflow_data}/random.json +0 -0
- data/spec/server/api/add_seen_spec.rb +8 -8
- data/spec/server/api/add_spec.rb +14 -14
- data/spec/server/api/remove_spec.rb +9 -9
- data/spec/server/api/sample_spec.rb +8 -8
- data/spec/server/api/user_load_spec.rb +7 -7
- data/spec/server/grouped_workflow_spec.rb +76 -0
- data/spec/server/server_spec.rb +9 -9
- data/spec/server/user_spec.rb +5 -5
- data/spec/server/workflow_spec.rb +62 -0
- data/spec/support/{shared_examples_for_project.rb → shared_examples_for_workflow.rb} +3 -3
- data/spec/support/spec_adapter.rb +6 -6
- metadata +26 -26
- data/spec/server/grouped_project_spec.rb +0 -76
- data/spec/server/project_spec.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd1ff2b4c25231cf18b03a8a558077a22915881c
|
4
|
+
data.tar.gz: 926b9cc3da11fcb74cf25994377d566ef2a9c1d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80c13d8c484811d77c62afbe567b966df465a4aac0f0f7e885711a2ed21e1e4afbdfab51d654b0242048c4b01ee70bded2b2de39da3846f61c160f93c21b3171
|
7
|
+
data.tar.gz: 9506e44c1991d879f7282fd9129222e49e5dca854619b4dbf06123c17fa2679a219e28ed9ff74cca3cabe88ea2d3942df62b0cdbf13117c8e580dd5ebe97448e
|
@@ -2,7 +2,7 @@ module Cellect
|
|
2
2
|
module Server
|
3
3
|
module Adapters
|
4
4
|
class Default
|
5
|
-
# Return a list of
|
5
|
+
# Return a list of workflows to load in the form:
|
6
6
|
# [{
|
7
7
|
# 'id' => 123,
|
8
8
|
# 'name' => 'foo',
|
@@ -10,49 +10,49 @@ module Cellect
|
|
10
10
|
# 'pairwise' => false,
|
11
11
|
# 'grouped' => false
|
12
12
|
# }, ...]
|
13
|
-
def
|
13
|
+
def workflow_list
|
14
14
|
raise NotImplementedError
|
15
15
|
end
|
16
16
|
|
17
|
-
# Load the data for a
|
18
|
-
# Accepts a
|
17
|
+
# Load the data for a workflow, this method:
|
18
|
+
# Accepts a workflow
|
19
19
|
# Returns an array of hashes in the form:
|
20
20
|
# {
|
21
21
|
# 'id' => 123,
|
22
22
|
# 'priority' => 0.123,
|
23
23
|
# 'group_id' => 456
|
24
24
|
# }
|
25
|
-
def load_data_for(
|
25
|
+
def load_data_for(workflow_name)
|
26
26
|
raise NotImplementedError
|
27
27
|
end
|
28
28
|
|
29
29
|
# Load seen ids for a user, this method:
|
30
|
-
# Accepts a
|
30
|
+
# Accepts a workflow_name, and a user id
|
31
31
|
# Returns an array in the form:
|
32
32
|
# [1, 2, 3]
|
33
|
-
def load_user(
|
33
|
+
def load_user(workflow_name, id)
|
34
34
|
raise NotImplementedError
|
35
35
|
end
|
36
36
|
|
37
|
-
def
|
38
|
-
|
37
|
+
def load_workflows
|
38
|
+
workflow_list.each{ |workflow_info| load_workflow workflow_info }
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
41
|
+
def load_workflow(args)
|
42
42
|
info = if args.is_a?(Hash)
|
43
43
|
args
|
44
44
|
elsif args.is_a?(String)
|
45
|
-
|
45
|
+
workflow_list.select{ |h| h['name'] == args }.first
|
46
46
|
else
|
47
47
|
raise ArgumentError
|
48
48
|
end
|
49
49
|
|
50
|
-
|
50
|
+
workflow_for info
|
51
51
|
end
|
52
52
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
53
|
+
def workflow_for(opts = { })
|
54
|
+
workflow_klass = opts.fetch('grouped', false) ? GroupedWorkflow : Workflow
|
55
|
+
workflow_klass[opts['name'], pairwise: opts['pairwise'], prioritized: opts['prioritized']]
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
@@ -11,9 +11,9 @@ module Cellect
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
14
|
+
def workflow_list
|
15
15
|
with_pg do |pg|
|
16
|
-
pg.exec('select * from
|
16
|
+
pg.exec('select * from workflows').collect do |row|
|
17
17
|
{
|
18
18
|
'id' => row['id'].to_i,
|
19
19
|
'name' => row['id'],
|
@@ -25,9 +25,9 @@ module Cellect
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def load_data_for(
|
28
|
+
def load_data_for(workflow_name)
|
29
29
|
with_pg do |pg|
|
30
|
-
pg.exec("select id, priority, group_id from
|
30
|
+
pg.exec("select id, priority, group_id from workflow_#{ workflow_name }_subjects").collect do |row|
|
31
31
|
{
|
32
32
|
'id' => row['id'].to_i,
|
33
33
|
'priority' => row['priority'].to_f,
|
@@ -37,9 +37,9 @@ module Cellect
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
def load_user(
|
40
|
+
def load_user(workflow_name, id)
|
41
41
|
with_pg do |pg|
|
42
|
-
pg.exec("select subject_id from
|
42
|
+
pg.exec("select subject_id from workflow_#{ workflow_name }_classifications where user_id=#{ id }").collect do |row|
|
43
43
|
row['subject_id'].to_i
|
44
44
|
end
|
45
45
|
end
|
@@ -3,16 +3,16 @@ module Cellect
|
|
3
3
|
class API
|
4
4
|
class Sets < Grape::API
|
5
5
|
get do
|
6
|
-
|
6
|
+
workflow.sample selector_params
|
7
7
|
end
|
8
8
|
|
9
9
|
put :add do
|
10
|
-
|
10
|
+
workflow.add update_params
|
11
11
|
nil
|
12
12
|
end
|
13
13
|
|
14
14
|
put :remove do
|
15
|
-
|
15
|
+
workflow.remove update_params
|
16
16
|
nil
|
17
17
|
end
|
18
18
|
end
|
@@ -9,7 +9,7 @@ module Cellect
|
|
9
9
|
subject_id = param_to_int :subject_id
|
10
10
|
|
11
11
|
if user_id && user_id > 0 && subject_id && subject_id > 0
|
12
|
-
|
12
|
+
workflow.async.add_seen_for user_id, subject_id
|
13
13
|
end
|
14
14
|
|
15
15
|
nil
|
@@ -19,7 +19,7 @@ module Cellect
|
|
19
19
|
user_id = param_to_int :user_id
|
20
20
|
|
21
21
|
if user_id && user_id > 0
|
22
|
-
|
22
|
+
workflow.async.user user_id
|
23
23
|
end
|
24
24
|
|
25
25
|
nil
|
data/lib/cellect/server/api.rb
CHANGED
@@ -20,26 +20,26 @@ module Cellect
|
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
23
|
-
resources :
|
23
|
+
resources :workflows do
|
24
24
|
get do
|
25
|
-
Cellect::Server.adapter.
|
25
|
+
Cellect::Server.adapter.workflow_list
|
26
26
|
end
|
27
27
|
|
28
|
-
segment '/:
|
28
|
+
segment '/:workflow_id' do
|
29
29
|
helpers Helpers
|
30
30
|
mount Sets
|
31
31
|
mount Users
|
32
32
|
|
33
33
|
get :status do
|
34
|
-
|
34
|
+
workflow.status
|
35
35
|
end
|
36
36
|
|
37
37
|
post :reload do
|
38
|
-
|
38
|
+
workflow.async.load_data
|
39
39
|
end
|
40
40
|
|
41
41
|
delete do
|
42
|
-
# delete a
|
42
|
+
# delete a workflow (maybe?)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -10,8 +10,9 @@ module Cellect
|
|
10
10
|
|
11
11
|
def setup
|
12
12
|
zk.mkdir_p '/nodes'
|
13
|
-
|
14
|
-
|
13
|
+
address = Socket.ip_address_list.find{ |address| address.ipv4? && !address.ipv4_loopback? }
|
14
|
+
raise "Cannot identify IP address" unless address
|
15
|
+
path = zk.create '/nodes/node', data: address.ip_address, mode: :ephemeral_sequential
|
15
16
|
self.id = path.sub /^\/nodes\//, ''
|
16
17
|
end
|
17
18
|
end
|
data/lib/cellect/server/user.rb
CHANGED
@@ -4,23 +4,23 @@ module Cellect
|
|
4
4
|
include Celluloid
|
5
5
|
include Celluloid::Logger
|
6
6
|
|
7
|
-
trap_exit :
|
7
|
+
trap_exit :workflow_crashed
|
8
8
|
finalizer :cancel_ttl_timer
|
9
9
|
|
10
|
-
attr_accessor :id, :
|
10
|
+
attr_accessor :id, :workflow_name, :seen, :state
|
11
11
|
attr_accessor :ttl, :ttl_timer
|
12
12
|
|
13
|
-
def initialize(id,
|
13
|
+
def initialize(id, workflow_name: nil, ttl: nil)
|
14
14
|
self.id = id
|
15
|
-
self.
|
15
|
+
self.workflow_name = workflow_name
|
16
16
|
self.seen = DiffSet::RandomSet.new
|
17
|
-
monitor
|
17
|
+
monitor Workflow[workflow_name]
|
18
18
|
@ttl = ttl
|
19
19
|
load_data
|
20
20
|
end
|
21
21
|
|
22
22
|
def load_data
|
23
|
-
data = Cellect::Server.adapter.load_user(
|
23
|
+
data = Cellect::Server.adapter.load_user(workflow_name, id) || []
|
24
24
|
data.each do |subject_id|
|
25
25
|
@seen.add subject_id
|
26
26
|
end
|
@@ -50,14 +50,14 @@ module Cellect
|
|
50
50
|
def ttl_expired!
|
51
51
|
debug "User #{ id } TTL expired"
|
52
52
|
cancel_ttl_timer
|
53
|
-
|
53
|
+
Workflow[workflow_name].async.remove_user(id)
|
54
54
|
end
|
55
55
|
|
56
56
|
def ttl
|
57
57
|
@ttl || 60 * 15 # 15 minutes
|
58
58
|
end
|
59
59
|
|
60
|
-
def
|
60
|
+
def workflow_crashed(actor, reason)
|
61
61
|
cancel_ttl_timer
|
62
62
|
terminate
|
63
63
|
end
|
@@ -1,25 +1,25 @@
|
|
1
1
|
module Cellect
|
2
2
|
module Server
|
3
|
-
class
|
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
|
def self.[](name, pairwise: false, prioritized: false)
|
10
|
-
key = "
|
10
|
+
key = "workflow_#{ name }".to_sym
|
11
11
|
Actor[key] ||= supervise name, pairwise: pairwise, prioritized: prioritized
|
12
12
|
Actor[key].actors.first
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.names
|
16
16
|
actor_names = Celluloid.actor_system.registry.names.collect &:to_s
|
17
|
-
|
18
|
-
|
17
|
+
workflow_actors = actor_names.select{ |key| key =~ /^workflow_/ }
|
18
|
+
workflow_actors.collect{ |name| name.sub(/^workflow_/, '').to_sym }
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.all
|
22
|
-
names.collect{ |name|
|
22
|
+
names.collect{ |name| Workflow[name] }
|
23
23
|
end
|
24
24
|
|
25
25
|
def initialize(name, pairwise: false, prioritized: false)
|
@@ -41,7 +41,7 @@ module Cellect
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def user(id)
|
44
|
-
self.users[id] ||= User.supervise id,
|
44
|
+
self.users[id] ||= User.supervise id, workflow_name: name
|
45
45
|
users[id].actors.first
|
46
46
|
end
|
47
47
|
|
data/lib/cellect/server.rb
CHANGED
@@ -6,8 +6,8 @@ module Cellect
|
|
6
6
|
module Server
|
7
7
|
require 'cellect/server/node_set'
|
8
8
|
require 'cellect/server/adapters'
|
9
|
-
require 'cellect/server/
|
10
|
-
require 'cellect/server/
|
9
|
+
require 'cellect/server/workflow'
|
10
|
+
require 'cellect/server/grouped_workflow'
|
11
11
|
require 'cellect/server/user'
|
12
12
|
require 'cellect/server/api'
|
13
13
|
|
@@ -16,8 +16,8 @@ module Cellect
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.ready?
|
19
|
-
|
20
|
-
return false unless
|
19
|
+
Workflow.all.each do |workflow|
|
20
|
+
return false unless workflow.ready?
|
21
21
|
end
|
22
22
|
|
23
23
|
true
|
data/lib/cellect/version.rb
CHANGED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -7,16 +7,16 @@ module Cellect::Server
|
|
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
|
-
let(:
|
11
|
-
let(:
|
12
|
-
let(:user){
|
13
|
-
before(:each){ pass_until
|
10
|
+
let(:workflow_type){ [grouping, set_type].compact.join '_' }
|
11
|
+
let(:workflow){ Workflow[workflow_type] }
|
12
|
+
let(:user){ workflow.user 123 }
|
13
|
+
before(:each){ pass_until workflow, is: :ready }
|
14
14
|
|
15
15
|
it 'should add seen subjects' do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
put "/
|
16
|
+
async_workflow = double
|
17
|
+
workflow.should_receive(:async).and_return async_workflow
|
18
|
+
async_workflow.should_receive(:add_seen_for).with 123, 123
|
19
|
+
put "/workflows/#{ workflow_type }/users/123/add_seen", subject_id: 123
|
20
20
|
last_response.status.should == 200
|
21
21
|
end
|
22
22
|
end
|
data/spec/server/api/add_spec.rb
CHANGED
@@ -7,30 +7,30 @@ module Cellect::Server
|
|
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
|
-
let(:
|
11
|
-
let(:
|
12
|
-
let(:user){
|
13
|
-
before(:each){ pass_until
|
10
|
+
let(:workflow_type){ [grouping, set_type].compact.join '_' }
|
11
|
+
let(:workflow){ Workflow[workflow_type] }
|
12
|
+
let(:user){ workflow.user 123 }
|
13
|
+
before(:each){ pass_until workflow, is: :ready }
|
14
14
|
|
15
15
|
let(:opts) do
|
16
16
|
{ subject_id: 123 }.tap do |h|
|
17
|
-
h[:priority] = 456.0 if
|
18
|
-
h[:group_id] = 1 if
|
17
|
+
h[:priority] = 456.0 if workflow.prioritized?
|
18
|
+
h[:group_id] = 1 if workflow.grouped?
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'should add subjects' do
|
23
|
-
if
|
24
|
-
|
25
|
-
elsif
|
26
|
-
|
27
|
-
elsif
|
28
|
-
|
23
|
+
if workflow.grouped? && workflow.prioritized?
|
24
|
+
workflow.should_receive(:add).with subject_id: 123, group_id: 1, priority: 456.0
|
25
|
+
elsif workflow.grouped?
|
26
|
+
workflow.should_receive(:add).with subject_id: 123, group_id: 1, priority: nil
|
27
|
+
elsif workflow.prioritized?
|
28
|
+
workflow.should_receive(:add).with subject_id: 123, group_id: nil, priority: 456.0
|
29
29
|
else
|
30
|
-
|
30
|
+
workflow.should_receive(:add).with subject_id: 123, group_id: nil, priority: nil
|
31
31
|
end
|
32
32
|
|
33
|
-
put "/
|
33
|
+
put "/workflows/#{ workflow_type }/add", opts
|
34
34
|
last_response.status.should == 200
|
35
35
|
end
|
36
36
|
end
|
@@ -7,25 +7,25 @@ module Cellect::Server
|
|
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
|
-
let(:
|
11
|
-
let(:
|
12
|
-
let(:user){
|
13
|
-
before(:each){ pass_until
|
10
|
+
let(:workflow_type){ [grouping, set_type].compact.join '_' }
|
11
|
+
let(:workflow){ Workflow[workflow_type] }
|
12
|
+
let(:user){ workflow.user 123 }
|
13
|
+
before(:each){ pass_until workflow, is: :ready }
|
14
14
|
|
15
15
|
let(:opts) do
|
16
16
|
{ subject_id: 123 }.tap do |h|
|
17
|
-
h[:group_id] = 1 if
|
17
|
+
h[:group_id] = 1 if workflow.grouped?
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
it 'should remove subjects' do
|
22
|
-
if
|
23
|
-
|
22
|
+
if workflow.grouped?
|
23
|
+
workflow.should_receive(:remove).with subject_id: 123, group_id: 1, priority: nil
|
24
24
|
else
|
25
|
-
|
25
|
+
workflow.should_receive(:remove).with subject_id: 123, group_id: nil, priority: nil
|
26
26
|
end
|
27
27
|
|
28
|
-
put "/
|
28
|
+
put "/workflows/#{ workflow_type }/remove", opts
|
29
29
|
last_response.status.should == 200
|
30
30
|
end
|
31
31
|
end
|
@@ -7,14 +7,14 @@ module Cellect::Server
|
|
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
|
-
let(:
|
11
|
-
let(:
|
12
|
-
let(:user){
|
13
|
-
before(:each){ pass_until
|
10
|
+
let(:workflow_type){ [grouping, set_type].compact.join '_' }
|
11
|
+
let(:workflow){ Workflow[workflow_type] }
|
12
|
+
let(:user){ workflow.user 123 }
|
13
|
+
before(:each){ pass_until workflow, is: :ready }
|
14
14
|
|
15
15
|
it 'should sample without a user, limit, or group' do
|
16
|
-
|
17
|
-
get "/
|
16
|
+
workflow.should_receive(:sample).with(limit: 5, user_id: nil, group_id: nil).and_call_original
|
17
|
+
get "/workflows/#{ workflow_type }"
|
18
18
|
last_response.status.should == 200
|
19
19
|
json.should be_a Array
|
20
20
|
end
|
@@ -22,8 +22,8 @@ module Cellect::Server
|
|
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
|
25
|
-
|
26
|
-
get "/
|
25
|
+
workflow.should_receive(:sample).with(limit: 3, user_id: 123, group_id: group_id).and_call_original
|
26
|
+
get "/workflows/#{ workflow_type }?limit=3&user_id=123#{ grouping ? '&group_id=1' : '' }"
|
27
27
|
last_response.status.should == 200
|
28
28
|
json.should be_a Array
|
29
29
|
end
|
@@ -7,15 +7,15 @@ module Cellect::Server
|
|
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
|
-
let(:
|
11
|
-
let(:
|
12
|
-
before(:each){ pass_until
|
10
|
+
let(:workflow_type){ [grouping, set_type].compact.join '_' }
|
11
|
+
let(:workflow){ Workflow[workflow_type] }
|
12
|
+
before(:each){ pass_until workflow, is: :ready }
|
13
13
|
|
14
14
|
it 'should load users' do
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
post "/
|
15
|
+
async_workflow = double
|
16
|
+
workflow.should_receive(:async).and_return async_workflow
|
17
|
+
async_workflow.should_receive(:user).with 123
|
18
|
+
post "/workflows/#{ workflow_type }/users/123/load"
|
19
19
|
last_response.status.should == 201
|
20
20
|
end
|
21
21
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Cellect::Server
|
4
|
+
describe GroupedWorkflow do
|
5
|
+
SET_TYPES.collect{ |type| "grouped_#{ type }" }.each do |workflow_type|
|
6
|
+
context workflow_type do
|
7
|
+
it_behaves_like 'workflow', :workflow
|
8
|
+
let(:workflow){ GroupedWorkflow[workflow_type] }
|
9
|
+
let(:user){ workflow.user 123 }
|
10
|
+
let(:set_klass){ workflow.prioritized? ? DiffSet::PrioritySet : DiffSet::RandomSet }
|
11
|
+
before(:each){ pass_until workflow, is: :ready }
|
12
|
+
|
13
|
+
it 'should provide unseen from a random group for users' do
|
14
|
+
workflow.groups = { }
|
15
|
+
workflow.groups[1] = set_klass.new
|
16
|
+
workflow.groups[1].should_receive(:subtract).with user.seen, 3
|
17
|
+
workflow.unseen_for 123, limit: 3
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should provide unseen from a specific group for users' do
|
21
|
+
3.times{ |i| workflow.groups[i] = set_klass.new }
|
22
|
+
workflow.group(1).should_receive(:subtract).with user.seen, 3
|
23
|
+
workflow.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
|
+
workflow.groups = { }
|
28
|
+
workflow.groups[1] = set_klass.new
|
29
|
+
workflow.group(1).should_receive(:sample).with 3
|
30
|
+
workflow.sample limit: 3
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should sample subjects from a specific group without a user' do
|
34
|
+
3.times{ |i| workflow.groups[i] = set_klass.new }
|
35
|
+
workflow.group(1).should_receive(:sample).with 3
|
36
|
+
workflow.sample group_id: 1, limit: 3
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should sample subjects from a random group for a user' do
|
40
|
+
workflow.groups = { }
|
41
|
+
workflow.groups[1] = set_klass.new
|
42
|
+
workflow.groups[1].should_receive(:subtract).with user.seen, 3
|
43
|
+
workflow.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| workflow.groups[i] = set_klass.new }
|
48
|
+
workflow.group(1).should_receive(:subtract).with user.seen, 3
|
49
|
+
workflow.sample user_id: 123, group_id: 1, limit: 3
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should add subjects' do
|
53
|
+
workflow.groups[1] = set_klass.new
|
54
|
+
|
55
|
+
if workflow.prioritized?
|
56
|
+
workflow.groups[1].should_receive(:add).with 123, 456
|
57
|
+
workflow.add subject_id: 123, group_id: 1, priority: 456
|
58
|
+
else
|
59
|
+
workflow.groups[1].should_receive(:add).with 123
|
60
|
+
workflow.add subject_id: 123, group_id: 1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should remove subjects' do
|
65
|
+
workflow.groups[1] = set_klass.new
|
66
|
+
workflow.groups[1].should_receive(:remove).with 123
|
67
|
+
workflow.remove subject_id: 123, group_id: 1
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should be grouped' do
|
71
|
+
workflow.should be_grouped
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/spec/server/server_spec.rb
CHANGED
@@ -6,18 +6,18 @@ module Cellect::Server
|
|
6
6
|
let(:default){ Cellect::Server::Adapters::Default.new }
|
7
7
|
|
8
8
|
it 'should raise a NotImplementedError when using the default adapter' do
|
9
|
-
expect{ default.
|
10
|
-
expect{ default.load_data_for(
|
9
|
+
expect{ default.workflow_list }.to raise_error NotImplementedError
|
10
|
+
expect{ default.load_data_for(Workflow.new('test')) }.to raise_error NotImplementedError
|
11
11
|
expect{ default.load_user 'random', 123 }.to raise_error NotImplementedError
|
12
12
|
end
|
13
13
|
|
14
|
-
it 'should return a
|
15
|
-
default.
|
16
|
-
default.
|
17
|
-
default.
|
18
|
-
default.
|
19
|
-
default.
|
20
|
-
default.
|
14
|
+
it 'should return a workflow given a set of options' do
|
15
|
+
default.workflow_for('name' => 'a').should be_an_instance_of Workflow
|
16
|
+
default.workflow_for('name' => 'b', 'grouped' => true).should be_an_instance_of GroupedWorkflow
|
17
|
+
default.workflow_for('name' => 'c', 'pairwise' => true).should be_pairwise
|
18
|
+
default.workflow_for('name' => 'd', 'prioritized' => true).should be_prioritized
|
19
|
+
default.workflow_for('name' => 'e', 'pairwise' => true, 'prioritized' => true).should be_pairwise
|
20
|
+
default.workflow_for('name' => 'e', 'pairwise' => true, 'prioritized' => true).should be_prioritized
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
data/spec/server/user_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Cellect::Server
|
4
4
|
describe User do
|
5
|
-
let(:user){ User.new 1,
|
5
|
+
let(:user){ User.new 1, workflow_name: 'random' }
|
6
6
|
|
7
7
|
it 'should store seen ids' do
|
8
8
|
user.seen.should be_a DiffSet::RandomSet
|
@@ -13,7 +13,7 @@ module Cellect::Server
|
|
13
13
|
end
|
14
14
|
|
15
15
|
it 'should allow custom ttl' do
|
16
|
-
User.new(2,
|
16
|
+
User.new(2, workflow_name: 'random', ttl: 123).ttl.should == 123
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'should reset the ttl timer on activity' do
|
@@ -22,9 +22,9 @@ module Cellect::Server
|
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'should terminate on ttl expiry' do
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
async_workflow = double
|
26
|
+
Workflow[user.workflow_name].should_receive(:async).and_return async_workflow
|
27
|
+
async_workflow.should_receive(:remove_user).with user.id
|
28
28
|
user.ttl_expired!
|
29
29
|
user.ttl_timer.should be_nil
|
30
30
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Cellect::Server
|
4
|
+
describe Workflow do
|
5
|
+
SET_TYPES.each do |workflow_type|
|
6
|
+
context workflow_type do
|
7
|
+
it_behaves_like 'workflow', :workflow
|
8
|
+
let(:workflow){ Workflow[workflow_type] }
|
9
|
+
let(:user){ workflow.user 123 }
|
10
|
+
before(:each){ pass_until workflow, is: :ready }
|
11
|
+
|
12
|
+
it 'should provide unseen for users' do
|
13
|
+
workflow.subjects.should_receive(:subtract).with user.seen, 3
|
14
|
+
workflow.unseen_for 123, limit: 3
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should sample subjects without a user' do
|
18
|
+
workflow.subjects.should_receive(:sample).with 3
|
19
|
+
workflow.sample limit: 3
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should sample subjects with a user' do
|
23
|
+
workflow.subjects.should_receive(:subtract).with user.seen, 3
|
24
|
+
workflow.sample user_id: 123, limit: 3
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should add subjects' do
|
28
|
+
if workflow.prioritized?
|
29
|
+
workflow.subjects.should_receive(:add).with 123, 456
|
30
|
+
workflow.add subject_id: 123, priority: 456
|
31
|
+
else
|
32
|
+
workflow.subjects.should_receive(:add).with 123
|
33
|
+
workflow.add subject_id: 123
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should remove subjects' do
|
38
|
+
workflow.subjects.should_receive(:add).with 123
|
39
|
+
workflow.add subject_id: 123
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should be notified of a user ttl expiry' do
|
43
|
+
async_workflow = double
|
44
|
+
workflow.should_receive(:async).and_return async_workflow
|
45
|
+
async_workflow.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
|
+
workflow.remove_user id
|
52
|
+
workflow.users.should_not have_key id
|
53
|
+
expect{ user.id }.to raise_error
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should not be grouped' do
|
57
|
+
workflow.should_not be_grouped
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
|
-
shared_examples_for '
|
1
|
+
shared_examples_for 'workflow' do |name|
|
2
2
|
let(:obj){ send name }
|
3
3
|
|
4
4
|
before(:each) do
|
5
|
-
Cellect::Server.adapter.
|
5
|
+
Cellect::Server.adapter.load_workflow obj.name
|
6
6
|
end
|
7
7
|
|
8
8
|
it 'should add singleton instances to the registry' do
|
9
|
-
obj.class[:foo].should be_a_kind_of Cellect::Server::
|
9
|
+
obj.class[:foo].should be_a_kind_of Cellect::Server::Workflow
|
10
10
|
obj.class[:foo].object_id.should == obj.class[:foo].object_id
|
11
11
|
end
|
12
12
|
|
@@ -1,17 +1,17 @@
|
|
1
1
|
require 'oj'
|
2
2
|
|
3
3
|
class SpecAdapter < Cellect::Server::Adapters::Default
|
4
|
-
def
|
4
|
+
def workflow_list
|
5
5
|
fixtures.values
|
6
6
|
end
|
7
7
|
|
8
|
-
def load_data_for(
|
9
|
-
fixtures.fetch(
|
8
|
+
def load_data_for(workflow_name)
|
9
|
+
fixtures.fetch(workflow_name, { }).fetch 'entries', []
|
10
10
|
end
|
11
11
|
|
12
12
|
def fixtures
|
13
13
|
@fixtures ||= { }.tap do |fixtures|
|
14
|
-
Dir["#{ _fixture_path }/
|
14
|
+
Dir["#{ _fixture_path }/workflow_data/*.json"].collect do |f|
|
15
15
|
name = File.basename(f).sub /\.json$/, ''
|
16
16
|
data = Oj.strict_load File.read f
|
17
17
|
fixtures[name] = data
|
@@ -30,9 +30,9 @@ class SpecAdapter < Cellect::Server::Adapters::Default
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
def load_user(
|
33
|
+
def load_user(workflow_name, id)
|
34
34
|
user = user_fixtures[id]
|
35
|
-
user ? user[
|
35
|
+
user ? user[workflow_name] : user_fixtures['new_user'][workflow_name]
|
36
36
|
end
|
37
37
|
|
38
38
|
protected
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cellect-server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Parrish
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -245,39 +245,39 @@ files:
|
|
245
245
|
- lib/cellect/server/api/helpers.rb
|
246
246
|
- lib/cellect/server/api/sets.rb
|
247
247
|
- lib/cellect/server/api/users.rb
|
248
|
-
- lib/cellect/server/
|
248
|
+
- lib/cellect/server/grouped_workflow.rb
|
249
249
|
- lib/cellect/server/node_set.rb
|
250
|
-
- lib/cellect/server/project.rb
|
251
250
|
- lib/cellect/server/user.rb
|
251
|
+
- lib/cellect/server/workflow.rb
|
252
252
|
- lib/cellect/version.rb
|
253
253
|
- log/.gitkeep
|
254
|
-
- spec/fixtures/project_data/grouped_pairwise_priority.json
|
255
|
-
- spec/fixtures/project_data/grouped_pairwise_random.json
|
256
|
-
- spec/fixtures/project_data/grouped_priority.json
|
257
|
-
- spec/fixtures/project_data/grouped_random.json
|
258
|
-
- spec/fixtures/project_data/pairwise_priority.json
|
259
|
-
- spec/fixtures/project_data/pairwise_random.json
|
260
|
-
- spec/fixtures/project_data/priority.json
|
261
|
-
- spec/fixtures/project_data/random.json
|
262
254
|
- spec/fixtures/user_data/complete_user.json
|
263
255
|
- spec/fixtures/user_data/new_user.json
|
264
256
|
- spec/fixtures/user_data/partial_user.json
|
257
|
+
- spec/fixtures/workflow_data/grouped_pairwise_priority.json
|
258
|
+
- spec/fixtures/workflow_data/grouped_pairwise_random.json
|
259
|
+
- spec/fixtures/workflow_data/grouped_priority.json
|
260
|
+
- spec/fixtures/workflow_data/grouped_random.json
|
261
|
+
- spec/fixtures/workflow_data/pairwise_priority.json
|
262
|
+
- spec/fixtures/workflow_data/pairwise_random.json
|
263
|
+
- spec/fixtures/workflow_data/priority.json
|
264
|
+
- spec/fixtures/workflow_data/random.json
|
265
265
|
- spec/server/api/add_seen_spec.rb
|
266
266
|
- spec/server/api/add_spec.rb
|
267
267
|
- spec/server/api/remove_spec.rb
|
268
268
|
- spec/server/api/sample_spec.rb
|
269
269
|
- spec/server/api/user_load_spec.rb
|
270
|
-
- spec/server/
|
270
|
+
- spec/server/grouped_workflow_spec.rb
|
271
271
|
- spec/server/node_set_spec.rb
|
272
|
-
- spec/server/project_spec.rb
|
273
272
|
- spec/server/server_spec.rb
|
274
273
|
- spec/server/user_spec.rb
|
274
|
+
- spec/server/workflow_spec.rb
|
275
275
|
- spec/spec_helper.rb
|
276
276
|
- spec/support/cellect_helper.rb
|
277
277
|
- spec/support/shared_api_context.rb
|
278
278
|
- spec/support/shared_examples_for_node_set.rb
|
279
|
-
- spec/support/shared_examples_for_project.rb
|
280
279
|
- spec/support/shared_examples_for_set.rb
|
280
|
+
- spec/support/shared_examples_for_workflow.rb
|
281
281
|
- spec/support/spec_adapter.rb
|
282
282
|
- spec/support/zk_setup.rb
|
283
283
|
- tmp/.gitkeep
|
@@ -306,32 +306,32 @@ signing_key:
|
|
306
306
|
specification_version: 4
|
307
307
|
summary: ''
|
308
308
|
test_files:
|
309
|
-
- spec/fixtures/project_data/grouped_pairwise_priority.json
|
310
|
-
- spec/fixtures/project_data/grouped_pairwise_random.json
|
311
|
-
- spec/fixtures/project_data/grouped_priority.json
|
312
|
-
- spec/fixtures/project_data/grouped_random.json
|
313
|
-
- spec/fixtures/project_data/pairwise_priority.json
|
314
|
-
- spec/fixtures/project_data/pairwise_random.json
|
315
|
-
- spec/fixtures/project_data/priority.json
|
316
|
-
- spec/fixtures/project_data/random.json
|
317
309
|
- spec/fixtures/user_data/complete_user.json
|
318
310
|
- spec/fixtures/user_data/new_user.json
|
319
311
|
- spec/fixtures/user_data/partial_user.json
|
312
|
+
- spec/fixtures/workflow_data/grouped_pairwise_priority.json
|
313
|
+
- spec/fixtures/workflow_data/grouped_pairwise_random.json
|
314
|
+
- spec/fixtures/workflow_data/grouped_priority.json
|
315
|
+
- spec/fixtures/workflow_data/grouped_random.json
|
316
|
+
- spec/fixtures/workflow_data/pairwise_priority.json
|
317
|
+
- spec/fixtures/workflow_data/pairwise_random.json
|
318
|
+
- spec/fixtures/workflow_data/priority.json
|
319
|
+
- spec/fixtures/workflow_data/random.json
|
320
320
|
- spec/server/api/add_seen_spec.rb
|
321
321
|
- spec/server/api/add_spec.rb
|
322
322
|
- spec/server/api/remove_spec.rb
|
323
323
|
- spec/server/api/sample_spec.rb
|
324
324
|
- spec/server/api/user_load_spec.rb
|
325
|
-
- spec/server/
|
325
|
+
- spec/server/grouped_workflow_spec.rb
|
326
326
|
- spec/server/node_set_spec.rb
|
327
|
-
- spec/server/project_spec.rb
|
328
327
|
- spec/server/server_spec.rb
|
329
328
|
- spec/server/user_spec.rb
|
329
|
+
- spec/server/workflow_spec.rb
|
330
330
|
- spec/spec_helper.rb
|
331
331
|
- spec/support/cellect_helper.rb
|
332
332
|
- spec/support/shared_api_context.rb
|
333
333
|
- spec/support/shared_examples_for_node_set.rb
|
334
|
-
- spec/support/shared_examples_for_project.rb
|
335
334
|
- spec/support/shared_examples_for_set.rb
|
335
|
+
- spec/support/shared_examples_for_workflow.rb
|
336
336
|
- spec/support/spec_adapter.rb
|
337
337
|
- spec/support/zk_setup.rb
|
@@ -1,76 +0,0 @@
|
|
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
|
data/spec/server/project_spec.rb
DELETED
@@ -1,62 +0,0 @@
|
|
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
|