cellect-server 2.1.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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