cellect-server 2.1.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2329c741f3e1c93571991a828999c115ffd4d052
4
- data.tar.gz: 55c3766d563da3f902dde9b2a18557d3d99d26b7
3
+ metadata.gz: d2e026d8345a63a009cc6fe11553fc8bfc87fbd0
4
+ data.tar.gz: 101d9b968ddb2445505ffbd4aff4b3503c608721
5
5
  SHA512:
6
- metadata.gz: bc5f12c17737323843881ce4bd8e53b730ee5a896feef7909a849fed5ab85b1bebda409b4e0cc5a683359da303cda75491cfa0a3773a0636e299643c76ee5abb
7
- data.tar.gz: 3cb690279afa355a07034cf45519d3f7faa58f399b663fc6f197622f02fe60844d25bb3f3fe01aba4ae09dedff16a0a4e5fc4c14bc3667a61904bfa4fd832920
6
+ metadata.gz: 650931de9ff9aed416f1388fbb8b31bb67b87b6c5d3890d8116548eda0aba018b5de847a075385df682c2eb6235a29091d5e8f38b1dc6f8a25c9df7b95024a67
7
+ data.tar.gz: 110a3703be03e56a8300c1c79aea3e39b753c97e97249a12dad8fbcba3fa79a60fa7793ca7ea91afa75dab8eccb63e7c20417cdf696114dc32e5dc915dcb9c07
data/.travis.yml CHANGED
@@ -13,3 +13,10 @@ rvm:
13
13
  script: bundle exec rake spec
14
14
  services:
15
15
  - redis-server
16
+
17
+ addons:
18
+ code_climate:
19
+ repo_token: $CODECLIMATE_REPO_TOKEN
20
+
21
+ after_success:
22
+ - bundle exec codeclimate-test-reporter
data/cellect.gemspec CHANGED
@@ -29,7 +29,8 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency 'puma', '~> 2.8'
30
30
  spec.add_development_dependency 'pg', '~> 0.17'
31
31
  spec.add_development_dependency 'connection_pool', '~> 2.0'
32
- spec.add_development_dependency 'codeclimate-test-reporter'
32
+ spec.add_development_dependency "simplecov"
33
+ spec.add_development_dependency "codeclimate-test-reporter", "~> 1.0.0"
33
34
 
34
35
  spec.add_runtime_dependency 'cellect-server', Cellect::VERSION
35
36
  spec.add_runtime_dependency 'cellect-client', Cellect::VERSION
@@ -10,6 +10,8 @@ module Cellect
10
10
  require 'cellect/server/workflow'
11
11
  require 'cellect/server/grouped_workflow'
12
12
  require 'cellect/server/user'
13
+ require 'cellect/server/loader'
14
+ require 'cellect/server/grouped_loader'
13
15
  require 'cellect/server/api'
14
16
 
15
17
  class << self
@@ -10,7 +10,7 @@ module Cellect
10
10
  require 'cellect/server/api/users'
11
11
 
12
12
  # GET /stats
13
- #
13
+ #
14
14
  # Provides system load information
15
15
  get :stats do
16
16
  instance = Attention.instance
@@ -32,7 +32,7 @@ module Cellect
32
32
  resources :workflows do
33
33
 
34
34
  # GET /workflows
35
- #
35
+ #
36
36
  # Returns a list of available workflows
37
37
  get do
38
38
  Cellect::Server.adapter.workflow_list
@@ -44,7 +44,7 @@ module Cellect
44
44
  mount Users
45
45
 
46
46
  # GET /workflows/:workflow_id/status
47
- #
47
+ #
48
48
  # Returns the workflow's status
49
49
  get :status do
50
50
  return four_oh_four unless workflow
@@ -52,15 +52,15 @@ module Cellect
52
52
  end
53
53
 
54
54
  # POST /workflows/:workflow_id/reload
55
- #
55
+ #
56
56
  # Reloads the workflow from the adapter
57
57
  post :reload do
58
58
  return four_oh_four unless workflow
59
- workflow.async.load_data
59
+ workflow.reload_data
60
60
  end
61
61
 
62
62
  # DELETE /workflows/:workflow_id
63
- #
63
+ #
64
64
  # Not implemented
65
65
  delete do
66
66
  # delete a workflow (maybe?)
@@ -0,0 +1,13 @@
1
+ module Cellect
2
+ module Server
3
+ class GroupedLoader < Loader
4
+
5
+ def run_load!(set)
6
+ Cellect::Server.adapter.load_data_for(workflow.name) do |hash|
7
+ set[hash['group_id']] ||= workflow.set_klass.new
8
+ set[hash['group_id']].add hash['id'], hash['priority']
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,29 +1,20 @@
1
1
  module Cellect
2
2
  module Server
3
3
  class GroupedWorkflow < Workflow
4
- attr_accessor :groups
5
4
 
6
5
  # Sets up the new workflow
7
6
  def initialize(name, pairwise: false, prioritized: false)
8
- self.groups = { }
7
+ self.subjects = { }
9
8
  super
10
9
  end
11
10
 
12
- # Load subjects from the adapter into their groups
13
- def load_data
14
- self.state = :initializing
15
- self.groups = { }
16
- klass = set_klass
17
- Cellect::Server.adapter.load_data_for(name).each do |hash|
18
- self.groups[hash['group_id']] ||= klass.new
19
- self.groups[hash['group_id']].add hash['id'], hash['priority']
20
- end
21
- self.state = :ready
11
+ def groups
12
+ subjects
22
13
  end
23
14
 
24
- # Returns a group by id or samples one randomly
15
+ # Returns a group by id or samples one randomly with a fall back to a new group
25
16
  def group(group_id = nil)
26
- groups[group_id] || groups.values.sample
17
+ subjects[group_id] || subjects.values.sample || fetch_or_setup_group(group_id)
27
18
  end
28
19
 
29
20
  # Get unseen subjects from a group for a user
@@ -32,7 +23,7 @@ module Cellect
32
23
  end
33
24
 
34
25
  # Get a sample of subjects from a group for a user
35
- #
26
+ #
36
27
  # Accepts a hash in the form:
37
28
  # {
38
29
  # user_id: 123,
@@ -43,12 +34,12 @@ module Cellect
43
34
  if opts[:user_id]
44
35
  unseen_for opts[:user_id], group_id: opts[:group_id], limit: opts[:limit]
45
36
  else
46
- group(opts[:group_id]).sample opts[:limit]
37
+ group(opts[:group_id]).sample opts[:limit]
47
38
  end
48
39
  end
49
40
 
50
41
  # Adds or updates a subject in a group
51
- #
42
+ #
52
43
  # Accepts a hash in the form:
53
44
  # {
54
45
  # subject_id: 1,
@@ -56,28 +47,32 @@ module Cellect
56
47
  # priority: 0.5 # (if the workflow is prioritized)
57
48
  # }
58
49
  def add(opts = { })
50
+ add_group = fetch_or_setup_group(opts[:group_id])
51
+
59
52
  if prioritized?
60
- groups[opts[:group_id]].add opts[:subject_id], opts[:priority]
53
+ add_group.add opts[:subject_id], opts[:priority]
61
54
  else
62
- groups[opts[:group_id]].add opts[:subject_id]
55
+ add_group.add opts[:subject_id]
63
56
  end
64
57
  end
65
58
 
66
59
  # Removes a subject from a group
67
- #
60
+ #
68
61
  # Accepts a hash in the form:
69
62
  # {
70
63
  # group_id: 1,
71
64
  # subject_id: 2
72
65
  # }
73
66
  def remove(opts = { })
74
- groups[opts[:group_id]].remove opts[:subject_id]
67
+ if group = subjects[opts[:group_id]]
68
+ group.remove opts[:subject_id]
69
+ end
75
70
  end
76
71
 
77
72
  # General information about this workflow
78
73
  def status
79
74
  # Get the number of subjects in each group
80
- group_counts = Hash[*groups.collect{ |id, set| [id, set.size] }.flatten]
75
+ group_counts = Hash[*subjects.collect{ |id, set| [id, set.size] }.flatten]
81
76
 
82
77
  super.merge({
83
78
  grouped: true,
@@ -89,6 +84,16 @@ module Cellect
89
84
  def grouped?
90
85
  true
91
86
  end
87
+
88
+ private
89
+
90
+ def data_loader
91
+ GroupedLoader.new(self)
92
+ end
93
+
94
+ def fetch_or_setup_group(group_id)
95
+ subjects[group_id] ||= set_klass.new
96
+ end
92
97
  end
93
98
  end
94
99
  end
@@ -0,0 +1,37 @@
1
+ module Cellect
2
+ module Server
3
+ class Loader
4
+ include Celluloid
5
+
6
+ attr_reader :workflow
7
+
8
+ def initialize(workflow)
9
+ @workflow = workflow
10
+ end
11
+
12
+ def load_data
13
+ run_load!(workflow.subjects)
14
+ mark_workflow_as_loaded
15
+ end
16
+
17
+ def reload_data(set)
18
+ run_load!(set)
19
+ workflow.subjects = set
20
+ mark_workflow_as_loaded
21
+ end
22
+
23
+ private
24
+
25
+ def mark_workflow_as_loaded
26
+ workflow.set_reload_at_time
27
+ workflow.state = :ready
28
+ end
29
+
30
+ def run_load!(set)
31
+ Cellect::Server.adapter.load_data_for(workflow.name) do |hash|
32
+ set.add hash['id'], hash['priority']
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -18,11 +18,13 @@ module Cellect
18
18
  self.seen = DiffSet::RandomSet.new
19
19
  monitor Workflow[workflow_name]
20
20
  @ttl = ttl
21
- load_data
21
+ self.state = :initializing
22
22
  end
23
23
 
24
24
  # Load the seen subjects for a user and restarts the TTL
25
25
  def load_data
26
+ return if self.state == :ready
27
+ self.state = :loading
26
28
  data = Cellect::Server.adapter.load_user(workflow_name, id) || []
27
29
  data.each do |subject_id|
28
30
  @seen.add subject_id
@@ -8,8 +8,11 @@ module Cellect
8
8
  end
9
9
  self.workflow_names = { }
10
10
 
11
- attr_accessor :name, :users, :subjects, :state
12
- attr_accessor :pairwise, :prioritized
11
+ attr_accessor :name, :users, :subjects, :state, :pairwise,
12
+ :prioritized, :can_reload_at
13
+
14
+ SKIP_RELOAD_STATES = [ :reloading, :loading, :initializing ].freeze
15
+ RELOAD_TIMEOUT = ENV.fetch('RELOAD_TIMEOUT', 600).to_i.freeze
13
16
 
14
17
  # Look up and/or load a workflow
15
18
  def self.[](name)
@@ -41,23 +44,32 @@ module Cellect
41
44
  self.users = { }
42
45
  self.pairwise = !!pairwise
43
46
  self.prioritized = !!prioritized
44
- self.subjects = set_klass.new
47
+ self.subjects ||= set_klass.new
48
+ self.state = :initializing
45
49
  end
46
50
 
47
51
  # Loads subjects from the adapter
48
52
  def load_data
49
- self.state = :initializing
50
- self.subjects = set_klass.new
51
- Cellect::Server.adapter.load_data_for(name).each do |hash|
52
- subjects.add hash['id'], hash['priority']
53
+ return if [:loading, :ready ].include? state
54
+ self.state = :loading
55
+ data_loader.async.load_data
56
+ end
57
+
58
+ # Reloads subjects from the adapter
59
+ def reload_data
60
+ if can_reload_data?
61
+ self.state = :reloading
62
+ reload_set = subjects.class.new
63
+ data_loader.async.reload_data(reload_set)
53
64
  end
54
- self.state = :ready
55
65
  end
56
66
 
57
67
  # Look up and/or load a user
58
68
  def user(id)
59
69
  self.users[id] ||= User.supervise id, workflow_name: name
60
- users[id].actors.first
70
+ user = self.users[id].actors.first
71
+ user.load_data
72
+ user
61
73
  end
62
74
 
63
75
  # Get unseen subjects for a user
@@ -79,7 +91,7 @@ module Cellect
79
91
  end
80
92
 
81
93
  # Get a sample of subjects for a user
82
- #
94
+ #
83
95
  # Accepts a hash in the form:
84
96
  # {
85
97
  # user_id: 123,
@@ -94,7 +106,7 @@ module Cellect
94
106
  end
95
107
 
96
108
  # Adds or updates a subject
97
- #
109
+ #
98
110
  # Accepts a hash in the form:
99
111
  # {
100
112
  # subject_id: 1,
@@ -109,7 +121,7 @@ module Cellect
109
121
  end
110
122
 
111
123
  # Removes a subject
112
- #
124
+ #
113
125
  # Accepts a hash in the form:
114
126
  # {
115
127
  # subject_id: 1
@@ -145,7 +157,7 @@ module Cellect
145
157
 
146
158
  # Looks up the set class
147
159
  def set_klass
148
- SET_KLASS[[prioritized, pairwise]]
160
+ @set_klass ||= SET_KLASS[[prioritized, pairwise]]
149
161
  end
150
162
 
151
163
  # General information about this workflow
@@ -160,6 +172,24 @@ module Cellect
160
172
  users: users.length
161
173
  }
162
174
  end
175
+
176
+ def can_reload_data?
177
+ if SKIP_RELOAD_STATES.include?(self.state)
178
+ false
179
+ elsif can_reload_at.nil?
180
+ true
181
+ else
182
+ can_reload_at <= Time.now
183
+ end
184
+ end
185
+
186
+ def set_reload_at_time(time_stamp=Time.now + RELOAD_TIMEOUT)
187
+ self.can_reload_at = time_stamp
188
+ end
189
+
190
+ def data_loader
191
+ Loader.new(self)
192
+ end
163
193
  end
164
194
  end
165
195
  end
@@ -1,3 +1,3 @@
1
1
  module Cellect
2
- VERSION = '2.1.1'
2
+ VERSION = '3.0.0'
3
3
  end
@@ -0,0 +1,24 @@
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(:workflow_type){ [grouping, set_type].compact.join '_' }
11
+ let(:workflow){ Workflow[workflow_type] }
12
+ before(:each){ pass_until_state_of workflow, is: :ready }
13
+
14
+ it 'should call reload_data' do
15
+ expect(workflow).to receive(:reload_data)
16
+ post "/workflows/#{ workflow_type }/reload"
17
+ expect(last_response.status).to eq 201
18
+ expect(json).to be_nil
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe Loader do
5
+ SET_TYPES.collect{ |type| "grouped_#{ type }" }.each do |workflow_type|
6
+ it_behaves_like "loader" do
7
+ let(:workflow) { GroupedWorkflow.new(workflow_type) }
8
+ let(:loader) { GroupedLoader.new(workflow) }
9
+ let(:subjects) { {} }
10
+ let(:group_counts) do
11
+ fixtures.map { |f| f["group_id"] }.uniq.count
12
+ end
13
+
14
+ describe "#load_data" do
15
+ it "should setup the groups" do
16
+ expect(workflow.subjects)
17
+ .to receive(:[]=)
18
+ .exactly(group_counts)
19
+ .and_call_original
20
+ loader.load_data
21
+ end
22
+
23
+ it "should add data to the workflow subjects" do
24
+ set = workflow.set_klass.new
25
+ allow(workflow.set_klass)
26
+ .to receive(:new)
27
+ .and_return(set)
28
+ expect(set)
29
+ .to receive(:add)
30
+ .exactly(fixture_count)
31
+ loader.load_data
32
+ end
33
+ end
34
+
35
+ describe "#reload_data" do
36
+ it "should add data to the workflow subjects" do
37
+ expect(subjects)
38
+ .to receive(:[]=)
39
+ .exactly(group_counts)
40
+ .and_call_original
41
+ loader.reload_data(subjects)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -4,67 +4,136 @@ module Cellect::Server
4
4
  describe GroupedWorkflow do
5
5
  SET_TYPES.collect{ |type| "grouped_#{ type }" }.each do |workflow_type|
6
6
  context workflow_type do
7
- it_behaves_like 'workflow', :workflow
8
- let(:workflow){ GroupedWorkflow[workflow_type] }
7
+ let(:workflow){ GroupedWorkflow.new(workflow_type) }
9
8
  let(:user){ workflow.user 123 }
10
9
  let(:set_klass){ workflow.prioritized? ? DiffSet::PrioritySet : DiffSet::RandomSet }
11
- before(:each){ pass_until_state_of workflow, is: :ready }
12
10
 
13
- it 'should provide unseen from a random group for users' do
14
- workflow.groups = { }
15
- workflow.groups[1] = set_klass.new
16
- expect(workflow.groups[1]).to receive(:subtract).with user.seen, 3
17
- workflow.unseen_for 123, limit: 3
11
+ it_behaves_like 'workflow', :workflow do
12
+ let(:obj) { workflow }
18
13
  end
19
14
 
20
- it 'should provide unseen from a specific group for users' do
21
- 3.times{ |i| workflow.groups[i] = set_klass.new }
22
- expect(workflow.group(1)).to receive(:subtract).with user.seen, 3
23
- workflow.unseen_for 123, group_id: 1, limit: 3
24
- end
15
+ describe "#group" do
16
+ context "no group is pre-defined" do
17
+ it "should be an empty set" do
18
+ expect(workflow.subjects).to be_empty
19
+ end
25
20
 
26
- it 'should sample subjects from a random group without a user' do
27
- workflow.groups = { }
28
- workflow.groups[1] = set_klass.new
29
- expect(workflow.group(1)).to receive(:sample).with 3
30
- workflow.sample limit: 3
31
- end
21
+ it "should setup a group" do
22
+ expect(workflow.group(1)).to be_instance_of(workflow.set_klass)
23
+ end
24
+
25
+ it "should setup a group if no group_id param" do
26
+ expect(workflow.group).to be_instance_of(workflow.set_klass)
27
+ end
28
+ end
32
29
 
33
- it 'should sample subjects from a specific group without a user' do
34
- 3.times{ |i| workflow.groups[i] = set_klass.new }
35
- expect(workflow.group(1)).to receive(:sample).with 3
36
- workflow.sample group_id: 1, limit: 3
30
+ context "with a pre-defined group" do
31
+ let(:set) { workflow.set_klass.new }
32
+ before do
33
+ workflow.subjects = { 1 => set }
34
+ end
35
+
36
+ it 'should retrieve a pre-defined group' do
37
+ expect(workflow.group(1)).to eq(set)
38
+ end
39
+
40
+ it 'should randomly select if no group param' do
41
+ expect(workflow.group).to eq(set)
42
+ end
43
+ end
37
44
  end
38
45
 
39
- it 'should sample subjects from a random group for a user' do
40
- workflow.groups = { }
41
- workflow.groups[1] = set_klass.new
42
- expect(workflow.groups[1]).to receive(:subtract).with user.seen, 3
43
- workflow.sample user_id: 123, limit: 3
46
+ describe "#unseen_for" do
47
+ it 'should not fail if a group is not loaded' do
48
+ workflow.subjects = { }
49
+ expect { workflow.unseen_for 123, limit: 3 }.not_to raise_error
50
+ end
51
+
52
+ it 'should provide unseen from a random group for users' do
53
+ workflow.subjects = { }
54
+ workflow.groups[1] = set_klass.new
55
+ expect(workflow.groups[1]).to receive(:subtract).with user.seen, 3
56
+ workflow.unseen_for 123, limit: 3
57
+ end
58
+
59
+ it 'should provide unseen from a specific group for users' do
60
+ 3.times{ |i| workflow.groups[i] = set_klass.new }
61
+ expect(workflow.group(1)).to receive(:subtract).with user.seen, 3
62
+ workflow.unseen_for 123, group_id: 1, limit: 3
63
+ end
44
64
  end
45
65
 
46
- it 'should sample subjects from a specific group for a user' do
47
- 3.times{ |i| workflow.groups[i] = set_klass.new }
48
- expect(workflow.group(1)).to receive(:subtract).with user.seen, 3
49
- workflow.sample user_id: 123, group_id: 1, limit: 3
66
+ describe "#sample" do
67
+ it 'should not fail if a group is not loaded' do
68
+ workflow.subjects = { }
69
+ expect { workflow.sample limit: 3 }.not_to raise_error
70
+ end
71
+
72
+ it 'should sample subjects from a random group without a user' do
73
+ workflow.subjects = { }
74
+ workflow.groups[1] = set_klass.new
75
+ expect(workflow.group(1)).to receive(:sample).with 3
76
+ workflow.sample limit: 3
77
+ end
78
+
79
+ it 'should sample subjects from a specific group without a user' do
80
+ 3.times{ |i| workflow.groups[i] = set_klass.new }
81
+ expect(workflow.group(1)).to receive(:sample).with 3
82
+ workflow.sample group_id: 1, limit: 3
83
+ end
84
+
85
+ it 'should sample subjects from a random group for a user' do
86
+ workflow.subjects = { }
87
+ workflow.groups[1] = set_klass.new
88
+ expect(workflow.groups[1]).to receive(:subtract).with user.seen, 3
89
+ workflow.sample user_id: 123, limit: 3
90
+ end
91
+
92
+ it 'should sample subjects from a specific group for a user' do
93
+ 3.times{ |i| workflow.groups[i] = set_klass.new }
94
+ expect(workflow.group(1)).to receive(:subtract).with user.seen, 3
95
+ workflow.sample user_id: 123, group_id: 1, limit: 3
96
+ end
50
97
  end
51
98
 
52
- it 'should add subjects' do
53
- workflow.groups[1] = set_klass.new
99
+ describe "#add" do
100
+ let(:opts) do
101
+ opts = { subject_id: 123, group_id: 1 }
102
+ opts[:priority] = 456 if workflow.prioritized?
103
+ opts
104
+ end
105
+
106
+ it 'should add a data to a new group if a group is not loaded' do
107
+ workflow.subjects = { }
108
+ workflow.add opts
109
+ group_data = workflow.subjects[1]
110
+ expect(group_data.to_a).to_not eq(opts[:subject_id])
111
+ end
112
+
113
+ it 'should add subjects' do
114
+ workflow.groups[1] = set_klass.new
54
115
 
55
- if workflow.prioritized?
56
- expect(workflow.groups[1]).to receive(:add).with 123, 456
57
- workflow.add subject_id: 123, group_id: 1, priority: 456
58
- else
59
- expect(workflow.groups[1]).to receive(:add).with 123
60
- workflow.add subject_id: 123, group_id: 1
116
+ if workflow.prioritized?
117
+ expect(workflow.groups[1]).to receive(:add).with 123, 456
118
+ workflow.add opts
119
+ else
120
+ expect(workflow.groups[1]).to receive(:add).with 123
121
+ workflow.add opts
122
+ end
61
123
  end
62
124
  end
63
125
 
64
- it 'should remove subjects' do
65
- workflow.groups[1] = set_klass.new
66
- expect(workflow.groups[1]).to receive(:remove).with 123
67
- workflow.remove subject_id: 123, group_id: 1
126
+ describe "#remove" do
127
+ it 'should not fail if a group is not loaded' do
128
+ workflow.subjects = { }
129
+ expect { workflow.remove subject_id: 123, group_id: 1 }.not_to raise_error
130
+ end
131
+
132
+ it 'should remove subjects' do
133
+ workflow.groups[1] = set_klass.new
134
+ expect(workflow.groups[1]).to receive(:remove).with 123
135
+ workflow.remove subject_id: 123, group_id: 1
136
+ end
68
137
  end
69
138
 
70
139
  it 'should be grouped' do
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ module Cellect::Server
4
+ describe Loader do
5
+ SET_TYPES.each do |workflow_type|
6
+ it_behaves_like "loader" do
7
+ let(:workflow) { Workflow.new(workflow_type) }
8
+ let(:loader) { Loader.new(workflow) }
9
+ let(:subjects) { workflow.set_klass.new }
10
+
11
+ describe "#load_data" do
12
+ it "should add data to the workflow subjects" do
13
+ expect(workflow.subjects)
14
+ .to receive(:add)
15
+ .exactly(fixture_count)
16
+ loader.load_data
17
+ end
18
+ end
19
+
20
+ describe "#reload_data" do
21
+ it "should add data to the workflow subjects" do
22
+ expect(subjects)
23
+ .to receive(:add)
24
+ .exactly(fixture_count)
25
+ loader.reload_data(subjects)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -28,5 +28,24 @@ module Cellect::Server
28
28
  user.ttl_expired!
29
29
  expect(user.ttl_timer).to be_nil
30
30
  end
31
+
32
+ describe '#load_data' do
33
+ it 'should request data from the adapater' do
34
+ expect(Cellect::Server.adapter)
35
+ .to receive(:load_user)
36
+ .with(user.workflow_name, user.id)
37
+ .and_return([])
38
+ user.load_data
39
+ end
40
+
41
+ it 'should add data to seens' do
42
+ expect { user.load_data }.to change { user.seen.size }
43
+ end
44
+
45
+ it 'should not add new subjects when already loaded' do
46
+ user.load_data
47
+ expect { user.load_data }.not_to change { user.seen.size }
48
+ end
49
+ end
31
50
  end
32
51
  end
@@ -9,10 +9,12 @@ module Cellect::Server
9
9
 
10
10
  SET_TYPES.each do |workflow_type|
11
11
  context workflow_type do
12
- it_behaves_like 'workflow', :workflow
13
- let(:workflow){ Workflow[workflow_type] }
14
- let(:user){ workflow.user 123 }
15
- before(:each){ pass_until_state_of workflow, is: :ready }
12
+ let(:workflow) { Workflow.new(workflow_type) }
13
+ let(:user) { workflow.user 123 }
14
+
15
+ it_behaves_like 'workflow', :workflow do
16
+ let(:obj) { workflow }
17
+ end
16
18
 
17
19
  it 'should provide unseen for users' do
18
20
  expect(workflow.subjects).to receive(:subtract).with user.seen, 3
@@ -46,7 +48,7 @@ module Cellect::Server
46
48
 
47
49
  it 'should be notified of a user ttl expiry' do
48
50
  async_workflow = double
49
- expect(workflow).to receive(:async).and_return async_workflow
51
+ expect(Workflow[workflow.name]).to receive(:async).and_return async_workflow
50
52
  expect(async_workflow).to receive(:remove_user).with user.id
51
53
  user.ttl_expired!
52
54
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,5 @@
1
- require 'codeclimate-test-reporter'
2
- CodeClimate::TestReporter.start
1
+ require "simplecov"
2
+ SimpleCov.start
3
3
 
4
4
  CELLECT_ROOT = File.expand_path File.join(File.dirname(__FILE__), '../')
5
5
 
@@ -0,0 +1,42 @@
1
+ shared_examples_for 'loader' do |name|
2
+ let(:fixtures) do
3
+ Cellect::Server
4
+ .adapter
5
+ .fixtures
6
+ .fetch(workflow.name, { })
7
+ .fetch('entries', [])
8
+ end
9
+ let(:fixture_count) { fixtures.count }
10
+
11
+ describe "#load_data" do
12
+ it "should use the server adapter to load data" do
13
+ expect(Cellect::Server.adapter)
14
+ .to receive(:load_data_for)
15
+ .with(workflow.name)
16
+ .and_call_original
17
+ loader.load_data
18
+ end
19
+
20
+ it "should mark the workflow as loaded" do
21
+ expect { loader.load_data }.to change { workflow.state }.to(:ready)
22
+ end
23
+ end
24
+
25
+ describe "#reload_data" do
26
+ it "should replace the existing subjects with the reloaded set" do
27
+ expect {
28
+ loader.reload_data(subjects)
29
+ }.to change {
30
+ workflow.subjects
31
+ }.to(subjects)
32
+ end
33
+
34
+ it "should mark the workflow as ready" do
35
+ expect {
36
+ loader.reload_data(subjects)
37
+ }.to change {
38
+ workflow.state
39
+ }.to(:ready)
40
+ end
41
+ end
42
+ end
@@ -1,10 +1,4 @@
1
1
  shared_examples_for 'workflow' do |name|
2
- let(:obj){ send name }
3
-
4
- before(:each) do
5
- Cellect::Server.adapter.load_workflow obj.name
6
- end
7
-
8
2
  it 'should add singleton instances to the registry' do
9
3
  expect(obj.class[obj.name]).to be_a_kind_of Cellect::Server::Workflow
10
4
  expect(obj.class[obj.name].object_id).to eq obj.class[obj.name].object_id
@@ -14,8 +8,12 @@ shared_examples_for 'workflow' do |name|
14
8
  expect(obj.name).to be_a String
15
9
  expect(obj.users).to be_a Hash
16
10
 
17
- set_klass = obj.prioritized? ? DiffSet::PrioritySet : DiffSet::RandomSet
18
- expect(obj.subjects).to be_a set_klass
11
+ if obj.grouped?
12
+ expect(obj.subjects).to eq({})
13
+ else
14
+ set_klass = obj.prioritized? ? DiffSet::PrioritySet : DiffSet::RandomSet
15
+ expect(obj.subjects).to be_a set_klass
16
+ end
19
17
  end
20
18
 
21
19
  it 'should provide a user lookup' do
@@ -23,4 +21,82 @@ shared_examples_for 'workflow' do |name|
23
21
  expect(obj.user(1).object_id).to eq obj.user(1).object_id
24
22
  expect(obj.users.keys).to include 1
25
23
  end
24
+
25
+ context "with a celluloid stubbed async loader" do
26
+ let(:loader) do
27
+ if obj.grouped?
28
+ Cellect::Server::GroupedLoader.new(obj)
29
+ else
30
+ Cellect::Server::Loader.new(obj)
31
+ end
32
+ end
33
+ let(:celluloid_target) { loader.wrapped_object }
34
+
35
+ before do
36
+ allow(obj.wrapped_object).to receive(:data_loader).and_return(loader)
37
+ allow(loader).to receive(:async).and_return(loader)
38
+ end
39
+
40
+ describe '#load_data' do
41
+ it 'should request data from the loader' do
42
+ expect(celluloid_target).to receive(:load_data)
43
+ obj.load_data
44
+ end
45
+
46
+ it 'should not attempt to reload subjects when in ready state' do
47
+ obj.state = :ready
48
+ expect(celluloid_target).not_to receive(:load_data)
49
+ obj.load_data
50
+ end
51
+ end
52
+
53
+ describe '#reload_data' do
54
+ context "able to reload" do
55
+ before do
56
+ obj.can_reload_at = Time.now - 1
57
+ obj.state = :ready
58
+ end
59
+
60
+ it 'should request data from the loader' do
61
+ expect(celluloid_target)
62
+ .to receive(:reload_data)
63
+ .with(instance_of(obj.subjects.class))
64
+ obj.reload_data
65
+ end
66
+
67
+ it 'should not reload subjects when state is in any kind of loading' do
68
+ %i(reloading loading).each do |state|
69
+ obj.state = state
70
+ expect(celluloid_target).not_to receive(:reload_data)
71
+ obj.reload_data
72
+ end
73
+ end
74
+ end
75
+
76
+ it 'should not allow reloading until after loading' do
77
+ expect(celluloid_target).not_to receive(:reload_data)
78
+ obj.reload_data
79
+ end
80
+
81
+ context "after initial data load" do
82
+ before do
83
+ obj.state = :ready
84
+ obj.set_reload_at_time
85
+ end
86
+
87
+ it 'should not allow reloading after first load' do
88
+ expect(celluloid_target).not_to receive(:reload_data)
89
+ obj.reload_data
90
+ end
91
+
92
+ context "when reload time gaurds has past" do
93
+ it 'should allow reloading after timer has reset' do
94
+ obj.can_reload_at = Time.now - 1
95
+ expect(celluloid_target).to receive(:reload_data)
96
+ obj.reload_data
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
26
102
  end
@@ -10,7 +10,12 @@ class SpecAdapter < Cellect::Server::Adapters::Default
10
10
  end
11
11
 
12
12
  def load_data_for(workflow_name)
13
- fixtures.fetch(workflow_name, { }).fetch 'entries', []
13
+ entries = fixtures.fetch(workflow_name, { }).fetch 'entries', []
14
+ if block_given?
15
+ entries.each { |entry| yield entry }
16
+ else
17
+ entries
18
+ end
14
19
  end
15
20
 
16
21
  def fixtures
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: 2.1.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Parrish
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-09 00:00:00.000000000 Z
11
+ date: 2017-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -266,7 +266,9 @@ files:
266
266
  - lib/cellect/server/api/helpers.rb
267
267
  - lib/cellect/server/api/sets.rb
268
268
  - lib/cellect/server/api/users.rb
269
+ - lib/cellect/server/grouped_loader.rb
269
270
  - lib/cellect/server/grouped_workflow.rb
271
+ - lib/cellect/server/loader.rb
270
272
  - lib/cellect/server/node_set.rb
271
273
  - lib/cellect/server/user.rb
272
274
  - lib/cellect/server/workflow.rb
@@ -274,10 +276,13 @@ files:
274
276
  - log/.gitkeep
275
277
  - spec/cellect/server/api/add_seen_spec.rb
276
278
  - spec/cellect/server/api/add_spec.rb
279
+ - spec/cellect/server/api/reload_spec.rb
277
280
  - spec/cellect/server/api/remove_spec.rb
278
281
  - spec/cellect/server/api/sample_spec.rb
279
282
  - spec/cellect/server/api/user_load_spec.rb
283
+ - spec/cellect/server/grouped_loader_spec.rb
280
284
  - spec/cellect/server/grouped_workflow_spec.rb
285
+ - spec/cellect/server/loader_spec.rb
281
286
  - spec/cellect/server/node_set_spec.rb
282
287
  - spec/cellect/server/server_spec.rb
283
288
  - spec/cellect/server/user_spec.rb
@@ -296,6 +301,7 @@ files:
296
301
  - spec/spec_helper.rb
297
302
  - spec/support/cellect_helper.rb
298
303
  - spec/support/shared_api_context.rb
304
+ - spec/support/shared_examples_for_loader.rb
299
305
  - spec/support/shared_examples_for_set.rb
300
306
  - spec/support/shared_examples_for_workflow.rb
301
307
  - spec/support/spec_adapter.rb
@@ -327,10 +333,13 @@ summary: ''
327
333
  test_files:
328
334
  - spec/cellect/server/api/add_seen_spec.rb
329
335
  - spec/cellect/server/api/add_spec.rb
336
+ - spec/cellect/server/api/reload_spec.rb
330
337
  - spec/cellect/server/api/remove_spec.rb
331
338
  - spec/cellect/server/api/sample_spec.rb
332
339
  - spec/cellect/server/api/user_load_spec.rb
340
+ - spec/cellect/server/grouped_loader_spec.rb
333
341
  - spec/cellect/server/grouped_workflow_spec.rb
342
+ - spec/cellect/server/loader_spec.rb
334
343
  - spec/cellect/server/node_set_spec.rb
335
344
  - spec/cellect/server/server_spec.rb
336
345
  - spec/cellect/server/user_spec.rb
@@ -349,6 +358,7 @@ test_files:
349
358
  - spec/spec_helper.rb
350
359
  - spec/support/cellect_helper.rb
351
360
  - spec/support/shared_api_context.rb
361
+ - spec/support/shared_examples_for_loader.rb
352
362
  - spec/support/shared_examples_for_set.rb
353
363
  - spec/support/shared_examples_for_workflow.rb
354
364
  - spec/support/spec_adapter.rb