be_taskable 0.5.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.
- 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
|