Skip to content
This repository has been archived by the owner on Sep 13, 2018. It is now read-only.

sporto/be_taskable

Repository files navigation

BeTaskable

Build Status

BeTaskable is a small framework for creating and maintaining tasks / chores / assignments. Meaning something that someone has to do.

Concepts

Taskable

Any object that needs action, e.g. a document. The taskable can have different actions e.g. publish, authorise

Assignee

Object that has to do the task. E.g. user

Task

An object representing an action that needs to be done for a particular taskable. e.g. Publish document #29

A task can have many assignees (See Task Assignments).

Task Assignment

An object linking a task and a assignee. e.g. User #80 can publish document #29

Resolver

An object linked to a taskable that has the business logic for your particular application.

Usage

Taskable Model

Make a model taskable

class Taskable < ActiveRecord::Base
	be_taskable
end

Task Resolver

A resolver is a class that should provide the business logic for each particular task. The resolver name is composed of the following parts:

  • Name of the taskable model e.g. Document
    • the name of the action e.g. Publish
    • 'TaskResolver'

      class DocumentPublishTaskResolver < BeTaskable::TaskResolver

      end

A resolver object should provide the following methods:

class DocumentPublishTaskResolver < BeTaskable::TaskResolver
	
	def consensus?(task)
		# This method should decide if the task is completed based on the assigments
		# return true or false

		# Some possible scenarios are:

		# any assignment is completed then return true
		# task.any_assignment_done?

		# the majority of assignments are completed then return true
		# task.majority_of_assignments_done?

		# all task are completed then return true
		# task.all_assignments_done?

		# use task.assignments to calculate consensus manually
	end

	def is_task_relevant?(task)
		true
	end

	def assignees_for_task(task)
		# Return a list of assignees for this particular task
	end

	def due_date_for_assignment(assignment)
		# called each time an assignment is created
		# return here when the assignment should be completed by
		# e.g. DateTime.now + two.weeks
		# return nil = no due date
	end

	def visible_date_for_assignment(assignment)
		# this sets the visible_at property on the assignment
		# this is useful if you don't want an assignment to be visible until some time in the future
	end

	def label_for_task(task)
		# return a label (name or description) for the task (if you need to show it on the ui)
		# get the taskable by calling task.taskable
	end

	def label_for_assigment(assignment)
		# return a label for the assignment
		# get the taskable by calling assignment.taskable
	end

	def url_for_assignment(assignment)
		# return a url where to go for the assigment
		# get the taskable by calling assignment.taskable
	end

	# hooks
	def on_creation(task)
		# called when a task is created
	end

	def on_completion(task)
		# will be called when a task is completed
	end

	def on_expiration(task)
		# will be called when a task is expired
	end

end

Create a resolver class for each taskable/action combination.

Creating a task

Given a taskable model, create a new task like this:

task = document.create_task_for_action('publish')

This creates the task and the assignments. You don't assign assignees to a task manually, they are assigned by the resolver.

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.

Completing a task

After completing an action you will usually have the taskable in hand. Using the taskable you can find the task like so:

task = taskable.last_task_for_action('publish') # will give you the last task
task.complete_by(assignee)

You may complete the task by using:

taskable.complete_task_for('publish', assignee)

When a task is completed several things will happen:

  • Task will find the assignment for that particular assignee
  • It will set the assignment as completed
  • It will call the .consensus? method in the task resolver
  • If consensus? returns true then it will set all the assignment to completed and the task as completed

You can check if a task is completed by doing:

task.completed?

Other options:

task.complete!
# completes the task regardless for all assignees. Marks all the assignments as completed.

taskable.complete_task_for_action('publish')
# same as task.complete!

Task.refresh

When task.refresh is called the following will happen:

  • Mark all the current task assignments as 'unconfirmed'
  • Find the list of assignees
  • Find or create an assignment for each assignee
  • Set those assignment to confirmed
  • Delete all the assignments that are still left as 'unconfirmed'

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. task.refresh has no effect if the task is already completed. Also it won't delete assignments that are already completed.

Task.tally

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.

Task.audit

Calls task.refresh and task.tally immediatelly.

This is useful for an audit of process that runs everyday to check the validity of the assignments in your application, e.g.

BeTaskable::Task.find_each do |task|
	task.audit
end

Task.expire

This sets the task as no longer valid, it expires all the assignments and calls the on_expiration method on the resolver

task.expire

Label and url

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. When you call task.label, assignment.label and assignment.url they will be retrieved from the cached attribute stored in the database. If you want to use the no-cached version use task.label!, assignment.label! and assignment.url! these will ask the resolver directly.

Who did the task?

To find out who did a particular task do the following:

assignments = tasks.enacted_assignments # this are the assignments that were actually completed by their assignees
assignees = assignments.map(&:assignee)

Task Assignment Scopes

The following scopes are available for task assignments:

  • completed
  • uncompled
  • visible
  • expired
  • unexpired
  • overdue
  • not_overdue
  • current: which are uncompled + visible + unexpired + not_overdue

Assignee

This is the object doing a task. e.g. User

Mixin in be_tasker into your model to access the BeTaskable methods:

class User < ActiveRecord::Base
	be_tasker
end

user.task_assignments #=> array with all assignments
user.task_assignments.current #=> array of current assignments

Testing (rspec)

To stub a resolver do the following:

resolver = DocumentAuthorizeTaskResolver.new

# stub the taskable class
Document.stub(:_task_resolver_for_action).and_return(resolver)

# now you can stub the resolver
resolver.stub(:assignees_for_task).and_return([user1, user2])

document = Document.create
document.create_task_for_action('authorize') # this will use the stubbed resolver

Generators

A handy generator is provided for creating the necessary tables

rails g be_taskable:migration

Also a genator for task resolvers is provided:

rails g be_taskable:resolver document publish

Testing this Gem

bundle
rspec

Deploying

rake version:bump:minor
rake version:bump:patch
rake release

License

MIT

About

Be Taskable - A Ruby Gem for managing user tasks

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages