be_taskable 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +178 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/be_taskable.gemspec +100 -0
- data/docs/task_refresh_activity_diagram.delineato +0 -0
- data/docs/taskable_complete_flow.delineato +0 -0
- data/lib/be_taskable/task.rb +173 -0
- data/lib/be_taskable/task_assignment.rb +60 -0
- data/lib/be_taskable/task_resolver.rb +47 -0
- data/lib/be_taskable/task_runner.rb +87 -0
- data/lib/be_taskable/taskable.rb +157 -0
- data/lib/be_taskable/tasker.rb +21 -0
- data/lib/be_taskable.rb +28 -0
- data/lib/generators/be_taskable/migration_generator.rb +44 -0
- data/lib/generators/be_taskable/resolver_generator.rb +28 -0
- data/lib/generators/be_taskable/templates/active_record/migration.rb +35 -0
- data/lib/generators/be_taskable/templates/resolver.rb.tpl +67 -0
- data/readme.md +292 -0
- data/spec/be_taskable/integration/end_to_end_spec.rb +79 -0
- data/spec/be_taskable/integration/irrelevance_spec.rb +70 -0
- data/spec/be_taskable/unit/be_taskable_spec.rb +17 -0
- data/spec/be_taskable/unit/task_assignment_spec.rb +185 -0
- data/spec/be_taskable/unit/task_runner_spec.rb +200 -0
- data/spec/be_taskable/unit/task_spec.rb +490 -0
- data/spec/be_taskable/unit/taskable_spec.rb +309 -0
- data/spec/be_taskable/unit/tasker_spec.rb +24 -0
- data/spec/database.yml +19 -0
- data/spec/models.rb +38 -0
- data/spec/schema.rb +37 -0
- data/spec/spec_helper.rb +96 -0
- metadata +245 -0
data/readme.md
ADDED
@@ -0,0 +1,292 @@
|
|
1
|
+
BeTaskable
|
2
|
+
==========
|
3
|
+
|
4
|
+
BeTaskable is a small framework for creating and maintaining tasks / chores / assignments. Meaning something that someone has to do.
|
5
|
+
|
6
|
+
Concepts
|
7
|
+
--------
|
8
|
+
|
9
|
+
### Taskable
|
10
|
+
|
11
|
+
Any object that needs action, e.g. a document. The taskable can have different actions e.g. publish, authorise
|
12
|
+
|
13
|
+
### Assignee
|
14
|
+
|
15
|
+
Object that has to do the task. E.g. user
|
16
|
+
|
17
|
+
### Task
|
18
|
+
|
19
|
+
An object representing an action that needs to be done for a particular taskable.
|
20
|
+
e.g. Publish document #29
|
21
|
+
|
22
|
+
A task can have many assignees (See Task Assignments).
|
23
|
+
|
24
|
+
### Task Assignment
|
25
|
+
|
26
|
+
An object linking a task and a assignee.
|
27
|
+
e.g. User #80 can publish document #29
|
28
|
+
|
29
|
+
### Resolver
|
30
|
+
|
31
|
+
An object linked to a taskable that has the business logic for your particular application.
|
32
|
+
|
33
|
+
Usage
|
34
|
+
-----
|
35
|
+
|
36
|
+
### Taskable Model
|
37
|
+
|
38
|
+
Make a model taskable
|
39
|
+
|
40
|
+
class Taskable < ActiveRecord::Base
|
41
|
+
be_taskable
|
42
|
+
end
|
43
|
+
|
44
|
+
### Task Resolver
|
45
|
+
|
46
|
+
A resolver is a class that should provide the business logic for each particular task.
|
47
|
+
The resolver name is composed of the following parts:
|
48
|
+
|
49
|
+
- Name of the taskable model e.g. Document
|
50
|
+
- + the name of the action e.g. Publish
|
51
|
+
- + 'TaskResolver'
|
52
|
+
|
53
|
+
class DocumentPublishTaskResolver < BeTaskable::TaskResolver
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
A resolver object should provide the following methods:
|
58
|
+
|
59
|
+
class DocumentPublishTaskResolver < BeTaskable::TaskResolver
|
60
|
+
|
61
|
+
def consensus?(task)
|
62
|
+
# This method should decide if the task is completed based on the assigments
|
63
|
+
# return true or false
|
64
|
+
|
65
|
+
# Some possible scenarios are:
|
66
|
+
|
67
|
+
# any assignment is completed then return true
|
68
|
+
# task.any_assignment_done?
|
69
|
+
|
70
|
+
# the majority of assignments are completed then return true
|
71
|
+
# task.majority_of_assignments_done?
|
72
|
+
|
73
|
+
# all task are completed then return true
|
74
|
+
# task.all_assignments_done?
|
75
|
+
|
76
|
+
# use task.assignments to calculate consensus manually
|
77
|
+
end
|
78
|
+
|
79
|
+
def is_task_relevant?(task)
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def assignees_for_task(task)
|
84
|
+
# Return a list of assignees for this particular task
|
85
|
+
end
|
86
|
+
|
87
|
+
def due_date_for_assignment(assignment)
|
88
|
+
# called each time an assignment is created
|
89
|
+
# return here when the assignment should be completed by
|
90
|
+
# e.g. DateTime.now + two.weeks
|
91
|
+
# return nil = no due date
|
92
|
+
end
|
93
|
+
|
94
|
+
def visible_date_for_assignment(assignment)
|
95
|
+
# this sets the visible_at property on the assignment
|
96
|
+
# this is useful if you don't want an assignment to be visible until some time in the future
|
97
|
+
end
|
98
|
+
|
99
|
+
def label_for_task(task)
|
100
|
+
# return a label (name or description) for the task (if you need to show it on the ui)
|
101
|
+
# get the taskable by calling task.taskable
|
102
|
+
end
|
103
|
+
|
104
|
+
def label_for_assigment(assignment)
|
105
|
+
# return a label for the assignment
|
106
|
+
# get the taskable by calling assignment.taskable
|
107
|
+
end
|
108
|
+
|
109
|
+
def url_for_assignment(assignment)
|
110
|
+
# return a url where to go for the assigment
|
111
|
+
# get the taskable by calling assignment.taskable
|
112
|
+
end
|
113
|
+
|
114
|
+
# hooks
|
115
|
+
def on_creation(task)
|
116
|
+
# called when a task is created
|
117
|
+
end
|
118
|
+
|
119
|
+
def on_completion(task)
|
120
|
+
# will be called when a task is completed
|
121
|
+
end
|
122
|
+
|
123
|
+
def on_expiration(task)
|
124
|
+
# will be called when a task is expired
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
Create a resolver class for each taskable/action combination.
|
130
|
+
|
131
|
+
### Creating a task
|
132
|
+
|
133
|
+
Given a taskable model, create a new task like this:
|
134
|
+
|
135
|
+
task = document.create_task_for_action('publish')
|
136
|
+
|
137
|
+
This creates the task and the assignments. You don't assign assignees to a task manually, they are assigned by the resolver.
|
138
|
+
|
139
|
+
Also there is a `create_or_refresh_task_for_action` method. This will reuse an existing task if present and is __not completed__ and __not expired__.
|
140
|
+
|
141
|
+
### Completing a task
|
142
|
+
|
143
|
+
After completing an action you will usually have the taskable in hand. Using the taskable you can find the task like so:
|
144
|
+
|
145
|
+
task = taskable.last_task_for_action('publish') # will give you the last task
|
146
|
+
task.complete_by(assignee)
|
147
|
+
|
148
|
+
You may complete the task by using:
|
149
|
+
|
150
|
+
taskable.complete_task_for('publish', assignee)
|
151
|
+
|
152
|
+
When a task is completed several things will happen:
|
153
|
+
|
154
|
+
- Task will find the assignment for that particular assignee
|
155
|
+
- It will set the assignment as completed
|
156
|
+
- It will call the .consensus? method in the task resolver
|
157
|
+
- If consensus? returns true then it will set all the assignment to completed and the task as completed
|
158
|
+
|
159
|
+
You can check if a task is completed by doing:
|
160
|
+
|
161
|
+
task.completed?
|
162
|
+
|
163
|
+
Other options:
|
164
|
+
|
165
|
+
task.complete!
|
166
|
+
# completes the task regardless for all assignees. Marks all the assignments as completed.
|
167
|
+
|
168
|
+
taskable.complete_task_for_action('publish')
|
169
|
+
# same as task.complete!
|
170
|
+
|
171
|
+
Task.refresh
|
172
|
+
------------
|
173
|
+
|
174
|
+
When task.refresh is called the following will happen:
|
175
|
+
|
176
|
+
- Mark all the current task assignments as 'unconfirmed'
|
177
|
+
- Find the list of assignees
|
178
|
+
- Find or create an assignment for each assignee
|
179
|
+
- Set those assignment to confirmed
|
180
|
+
- Delete all the assignments that are still left as 'unconfirmed'
|
181
|
+
|
182
|
+
This means that if the business rules change in your resolver or the assignees change (e.g. you have more users) then `task.refresh` will create and deleted assignments as needed.
|
183
|
+
`task.refresh` has no effect if the task is already completed. Also it won't delete assignments that are already completed.
|
184
|
+
|
185
|
+
Task.tally
|
186
|
+
----------
|
187
|
+
|
188
|
+
This checks if the task can be considered done, it uses the `consensus?` method in the resolver to decide this. If the task is done then all assignments will be marked as completed.
|
189
|
+
|
190
|
+
Task.audit
|
191
|
+
--------
|
192
|
+
|
193
|
+
Calls `task.refresh` and `task.tally` immediatelly.
|
194
|
+
|
195
|
+
This is useful for an audit of process that runs everyday to check the validity of the assignments in your application, e.g.
|
196
|
+
|
197
|
+
BeTaskable::Task.find_each do |task|
|
198
|
+
task.audit
|
199
|
+
end
|
200
|
+
|
201
|
+
Task.expire
|
202
|
+
-----------
|
203
|
+
|
204
|
+
This sets the task as no longer valid, it expires all the assignments and calls the on_expiration method on the resolver
|
205
|
+
|
206
|
+
task.expire
|
207
|
+
|
208
|
+
Label and url
|
209
|
+
-------------
|
210
|
+
|
211
|
+
When task.run is called task.label, assignment.label and assignment.url are generated (using the resolver) and stored in the database. They are re-generated each time task.run is called.
|
212
|
+
When you call task.label, assignment.label and assignment.url they will be retrieved from the cached attribute stored in the database.
|
213
|
+
If you want to use the no-cached version use task.label!, assignment.label! and assignment.url! these will ask the resolver directly.
|
214
|
+
|
215
|
+
Who did the task?
|
216
|
+
-----------------
|
217
|
+
|
218
|
+
To find out who did a particular task do the following:
|
219
|
+
|
220
|
+
assignments = tasks.enacted_assignments # this are the assignments that were actually completed by their assignees
|
221
|
+
assignees = assignments.map(&:assignee)
|
222
|
+
|
223
|
+
Task Assignment Scopes
|
224
|
+
---------------------
|
225
|
+
|
226
|
+
The following scopes are available for task assignments:
|
227
|
+
|
228
|
+
- completed
|
229
|
+
- uncompled
|
230
|
+
- visible
|
231
|
+
- expired
|
232
|
+
- unexpired
|
233
|
+
- overdue
|
234
|
+
- not_overdue
|
235
|
+
- current: which are uncompled + visible + unexpired + not_overdue
|
236
|
+
|
237
|
+
Assignee
|
238
|
+
---------
|
239
|
+
This is the object doing a task. e.g. User
|
240
|
+
|
241
|
+
Mixin in `be_tasker` into your model to access the BeTaskable methods:
|
242
|
+
|
243
|
+
class User < ActiveRecord::Base
|
244
|
+
be_tasker
|
245
|
+
end
|
246
|
+
|
247
|
+
user.task_assignments #=> array with all assignments
|
248
|
+
user.task_assignments.current #=> array of current assignments
|
249
|
+
|
250
|
+
Testing (rspec)
|
251
|
+
-------
|
252
|
+
|
253
|
+
To stub a resolver do the following:
|
254
|
+
|
255
|
+
resolver = DocumentAuthorizeTaskResolver.new
|
256
|
+
|
257
|
+
# stub the taskable class
|
258
|
+
Document.stub(:_task_resolver_for_action).and_return(resolver)
|
259
|
+
|
260
|
+
# now you can stub the resolver
|
261
|
+
resolver.stub(:assignees_for_task).and_return([user1, user2])
|
262
|
+
|
263
|
+
document = Document.create
|
264
|
+
document.create_task_for_action('authorize') # this will use the stubbed resolver
|
265
|
+
|
266
|
+
Generators
|
267
|
+
----------
|
268
|
+
|
269
|
+
A handy generator is provided for creating the necessary tables
|
270
|
+
|
271
|
+
rails g be_taskable:migration
|
272
|
+
|
273
|
+
Also a genator for task resolvers is provided:
|
274
|
+
|
275
|
+
rails g be_taskable:resolver document publish
|
276
|
+
|
277
|
+
|
278
|
+
Testing this Gem
|
279
|
+
----------------
|
280
|
+
|
281
|
+
bundle
|
282
|
+
rspec
|
283
|
+
|
284
|
+
License
|
285
|
+
-------
|
286
|
+
|
287
|
+
MIT
|
288
|
+
|
289
|
+
|
290
|
+
|
291
|
+
|
292
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe 'End to End' do
|
4
|
+
|
5
|
+
let(:user1) { User.create }
|
6
|
+
let(:user2) { User.create }
|
7
|
+
let(:user3) { User.create }
|
8
|
+
let(:assignees) { [user1, user2] }
|
9
|
+
let(:taskable) { Taskable.create }
|
10
|
+
let(:resolver) { TaskableReviewTaskResolver.new }
|
11
|
+
|
12
|
+
before do
|
13
|
+
user1
|
14
|
+
user2
|
15
|
+
user3
|
16
|
+
|
17
|
+
resolver.stub(:assignees_for_task).and_return(assignees)
|
18
|
+
|
19
|
+
TaskableReviewTaskResolver.stub(:new).and_return(resolver)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Create a task
|
23
|
+
# make sure assignees are assigned
|
24
|
+
# complete an assignment
|
25
|
+
# complete another assignment (that triggers consensus)
|
26
|
+
# task should be completed
|
27
|
+
it 'works' do
|
28
|
+
# resolver should receive on_creation when a task is created
|
29
|
+
resolver.should_receive(:on_creation)
|
30
|
+
|
31
|
+
task = taskable.create_task_for_action('review')
|
32
|
+
|
33
|
+
# there should be two assigments now
|
34
|
+
expect(BeTaskable::TaskAssignment.count).to eq(2)
|
35
|
+
|
36
|
+
# add more assignees
|
37
|
+
assignees.push(user3)
|
38
|
+
|
39
|
+
# task should have a label provided by the resolver
|
40
|
+
expect(task.label).to eq("Task label #{task.id}")
|
41
|
+
|
42
|
+
task.refresh
|
43
|
+
|
44
|
+
# there should be 3 assigments now
|
45
|
+
expect(BeTaskable::TaskAssignment.count).to eq(3)
|
46
|
+
|
47
|
+
# assignment should have a label provided by the resolver
|
48
|
+
assignment = BeTaskable::TaskAssignment.first
|
49
|
+
expect(assignment.label).to eq("Assignment label #{assignment.id}")
|
50
|
+
|
51
|
+
# assignment should have a url provided by the resolver
|
52
|
+
expect(assignment.url).to eq("Assignment url #{assignment.id}")
|
53
|
+
|
54
|
+
# assignment should have due date as provided by the resolver
|
55
|
+
expect(assignment.complete_by).to be_within(1.minute).of(DateTime.now + 10.days)
|
56
|
+
|
57
|
+
# complete the task for one of the assignees
|
58
|
+
taskable.complete_task_for('review', user1)
|
59
|
+
|
60
|
+
# the task should still be uncompleted
|
61
|
+
expect(task).not_to be_completed
|
62
|
+
|
63
|
+
# change the consensus
|
64
|
+
resolver.stub(:consensus?).and_return true
|
65
|
+
|
66
|
+
# the resolver shoud receive on_completion
|
67
|
+
resolver.should_receive(:on_completion)
|
68
|
+
|
69
|
+
# complete another task
|
70
|
+
taskable.complete_task_for('review', user2)
|
71
|
+
|
72
|
+
# task should be completed
|
73
|
+
task.reload
|
74
|
+
# puts task.state
|
75
|
+
# puts task.assignments.inspect
|
76
|
+
expect(task).to be_completed
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Irrelevance" do
|
4
|
+
|
5
|
+
let(:user1) { User.create }
|
6
|
+
let(:user2) { User.create }
|
7
|
+
let(:assignees) { [user1, user2] }
|
8
|
+
let(:taskable) { Taskable.create }
|
9
|
+
let(:resolver) { TaskableReviewTaskResolver.new }
|
10
|
+
let(:task) { taskable.create_task_for_action('review') }
|
11
|
+
|
12
|
+
before do
|
13
|
+
user1
|
14
|
+
user2
|
15
|
+
|
16
|
+
resolver.stub(:assignees_for_task).and_return(assignees)
|
17
|
+
TaskableReviewTaskResolver.stub(:new).and_return(resolver)
|
18
|
+
|
19
|
+
task
|
20
|
+
end
|
21
|
+
|
22
|
+
steps "irrelevant" do
|
23
|
+
|
24
|
+
it "creates the assigments when relevant" do
|
25
|
+
expect(task).to be_open
|
26
|
+
|
27
|
+
# there should be two assigments now
|
28
|
+
expect(BeTaskable::TaskAssignment.count).to eq(2)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "deletes the assignments when irrelevant" do
|
32
|
+
# expect(BeTaskable::TaskAssignment.count).to eq(2)
|
33
|
+
|
34
|
+
# make the task irrelevant
|
35
|
+
resolver.stub(:is_task_relevant?).and_return(false)
|
36
|
+
|
37
|
+
task.refresh
|
38
|
+
|
39
|
+
expect(task).to be_irrelevant
|
40
|
+
expect(BeTaskable::TaskAssignment.count).to eq(0)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "recreates the assignments when relevant again" do
|
44
|
+
|
45
|
+
# make task relevant again
|
46
|
+
resolver.unstub(:is_task_relevant?)
|
47
|
+
|
48
|
+
task.refresh
|
49
|
+
|
50
|
+
expect(task).to be_open
|
51
|
+
# there should be two assigments now
|
52
|
+
expect(BeTaskable::TaskAssignment.count).to eq(2)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "doesnt delete already completed assignments" do
|
56
|
+
expect(BeTaskable::TaskAssignment.count).to eq(2)
|
57
|
+
|
58
|
+
resolver.stub(:is_task_relevant?).and_return(false)
|
59
|
+
resolver.stub(:consensus?).and_return(false)
|
60
|
+
|
61
|
+
BeTaskable::TaskAssignment.first.complete
|
62
|
+
|
63
|
+
task.refresh
|
64
|
+
|
65
|
+
expect(BeTaskable::TaskAssignment.count).to eq(1)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe 'BeTaskable' do
|
4
|
+
|
5
|
+
describe "#be_taskable" do
|
6
|
+
|
7
|
+
it "should provide a class method be_taskable" do
|
8
|
+
expect(ActiveRecord::Base).to respond_to('be_taskable')
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should provide a class method be_tasker" do
|
12
|
+
expect(ActiveRecord::Base).to respond_to('be_tasker')
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe 'BeTaskable::TaskAssignment' do
|
4
|
+
|
5
|
+
let(:task) { double.as_null_object }
|
6
|
+
let(:resolver) { double.as_null_object }
|
7
|
+
let(:assignment) { BeTaskable::TaskAssignment.new }
|
8
|
+
|
9
|
+
before do
|
10
|
+
assignment.stub(:task).and_return(task)
|
11
|
+
assignment.stub(:resolver).and_return(resolver)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#completed" do
|
15
|
+
it "finds the completed" do
|
16
|
+
a1 = BeTaskable::TaskAssignment.create(completed_at: DateTime.now)
|
17
|
+
a2 = BeTaskable::TaskAssignment.create()
|
18
|
+
res = BeTaskable::TaskAssignment.completed.all
|
19
|
+
expect(res).to eq([a1])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#uncompleted" do
|
24
|
+
it "finds the uncompleted" do
|
25
|
+
a1 = BeTaskable::TaskAssignment.create(completed_at: DateTime.now)
|
26
|
+
a2 = BeTaskable::TaskAssignment.create()
|
27
|
+
res = BeTaskable::TaskAssignment.uncompleted.all
|
28
|
+
expect(res).to eq([a2])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#overdue" do
|
33
|
+
it "finds the overdue" do
|
34
|
+
a1 = BeTaskable::TaskAssignment.create(complete_by: DateTime.now - 1.day)
|
35
|
+
a2 = BeTaskable::TaskAssignment.create(complete_by: DateTime.now + 1.day)
|
36
|
+
a3 = BeTaskable::TaskAssignment.create()
|
37
|
+
res = BeTaskable::TaskAssignment.overdue.all
|
38
|
+
expect(res).to eq([a1])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#not_overdue" do
|
43
|
+
it "finds the not_overdue" do
|
44
|
+
a1 = BeTaskable::TaskAssignment.create(complete_by: DateTime.now - 1.day)
|
45
|
+
a2 = BeTaskable::TaskAssignment.create(complete_by: DateTime.now + 1.day)
|
46
|
+
a3 = BeTaskable::TaskAssignment.create()
|
47
|
+
res = BeTaskable::TaskAssignment.not_overdue.all
|
48
|
+
expect(res).to eq([a2, a3])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#visible" do
|
53
|
+
it "finds the visible" do
|
54
|
+
a1 = BeTaskable::TaskAssignment.create(visible_at: DateTime.now - 1.day)
|
55
|
+
a2 = BeTaskable::TaskAssignment.create()
|
56
|
+
a3 = BeTaskable::TaskAssignment.create(visible_at: DateTime.now + 1.day)
|
57
|
+
res = BeTaskable::TaskAssignment.visible.all
|
58
|
+
expect(res).to eq([a1, a2])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#expired" do
|
63
|
+
it "finds the expired" do
|
64
|
+
a1 = BeTaskable::TaskAssignment.create(expired_at: DateTime.now - 1.day)
|
65
|
+
a2 = BeTaskable::TaskAssignment.create()
|
66
|
+
res = BeTaskable::TaskAssignment.expired.all
|
67
|
+
expect(res).to eq([a1])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#unexpired" do
|
72
|
+
it "finds the unexpired" do
|
73
|
+
a1 = BeTaskable::TaskAssignment.create(expired_at: DateTime.now - 1.day)
|
74
|
+
a2 = BeTaskable::TaskAssignment.create()
|
75
|
+
res = BeTaskable::TaskAssignment.unexpired.all
|
76
|
+
expect(res).to eq([a2])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#current" do
|
81
|
+
|
82
|
+
let(:a1) { BeTaskable::TaskAssignment.create() }
|
83
|
+
let(:a2) { BeTaskable::TaskAssignment.create(expired_at: DateTime.now - 1.day) }
|
84
|
+
let(:a3) { BeTaskable::TaskAssignment.create(complete_by: DateTime.now - 1.day) }
|
85
|
+
let(:a4) { BeTaskable::TaskAssignment.create(complete_by: DateTime.now + 1.day) }
|
86
|
+
let(:a5) { BeTaskable::TaskAssignment.create(completed_at: DateTime.now - 1.day) }
|
87
|
+
let(:a6) { BeTaskable::TaskAssignment.create(visible_at: DateTime.now + 1.day) }
|
88
|
+
|
89
|
+
before do
|
90
|
+
a1;a2;a3;a4;a5;a6
|
91
|
+
end
|
92
|
+
|
93
|
+
it "finds them" do
|
94
|
+
res = BeTaskable::TaskAssignment.current.all
|
95
|
+
expect(res).to eq([a1, a4])
|
96
|
+
end
|
97
|
+
|
98
|
+
it "doesnt find expired" do
|
99
|
+
res = BeTaskable::TaskAssignment.current.all
|
100
|
+
expect(res).not_to include(a2)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "doesnt find overdue" do
|
104
|
+
res = BeTaskable::TaskAssignment.current.all
|
105
|
+
expect(res).not_to include(a3)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "doesnt find completed" do
|
109
|
+
res = BeTaskable::TaskAssignment.current.all
|
110
|
+
expect(res).not_to include(a5)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "doesnt find invisibles" do
|
114
|
+
res = BeTaskable::TaskAssignment.current.all
|
115
|
+
expect(res).not_to include(a6)
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
describe ".taskable" do
|
121
|
+
it "ask the task" do
|
122
|
+
task.should_receive(:taskable)
|
123
|
+
assignment.taskable
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe ".resolver" do
|
128
|
+
it 'asks the task' do
|
129
|
+
assignment.unstub(:resolver)
|
130
|
+
task.should_receive(:resolver)
|
131
|
+
assignment.resolver
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe ".complete" do
|
136
|
+
it 'sets the completed_at' do
|
137
|
+
assignment.complete
|
138
|
+
expect(assignment.completed_at).to be_within(1.minute).of(DateTime.now)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "calls on_assignment_completed on task" do
|
142
|
+
task.should_receive(:on_assignment_completed).with(assignment)
|
143
|
+
assignment.complete
|
144
|
+
end
|
145
|
+
|
146
|
+
it "returns true" do
|
147
|
+
res = assignment.complete
|
148
|
+
expect(res).to be_true
|
149
|
+
end
|
150
|
+
|
151
|
+
it "returns false if assignment has already been completed" do
|
152
|
+
assignment.update_attribute(:completed_at, DateTime.now)
|
153
|
+
res = assignment.complete
|
154
|
+
expect(res).to be_false
|
155
|
+
end
|
156
|
+
|
157
|
+
it "sets an errors array if it returns false" do
|
158
|
+
assignment.update_attribute(:completed_at, DateTime.now)
|
159
|
+
res = assignment.complete
|
160
|
+
expect(res).to be_false
|
161
|
+
#expect(res.errors.size).not_to be_empty
|
162
|
+
end
|
163
|
+
|
164
|
+
it "sets enacted" do
|
165
|
+
res = assignment.complete
|
166
|
+
# assignment.reload
|
167
|
+
expect(assignment).to be_enacted
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe ".label!" do
|
172
|
+
it "asks the resolver" do
|
173
|
+
resolver.should_receive(:label_for_assignment)
|
174
|
+
assignment.label!
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe ".url!" do
|
179
|
+
it "asks the resolver" do
|
180
|
+
resolver.should_receive(:url_for_assignment)
|
181
|
+
assignment.url!
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|