History of Events with rails
Another common task in a Web app is to create a register of events. Sometimes you want to email your users about something that happened, others you just want to show somewhere a table with the timeline of recent events. For example, let’s take the example of a project managing application (like the well-known Basecamp of 37Signals).
This comes from the last app in which I worked. I reckon there are two ways to embrace this problem. One involves adding or using the rails default field updated_at of every table in the database. Of course some other handy fields might be added as well, such as updated_by and updating_description. Showing a register of events in this case means taking every model in the database, ordering it by the updating date and limiting the results to a given constant. All of those should be added by the controller to a hash or an array and would later on be globally ordered by the value of the update field, only to be delivered to the views and finally displayed. Nonetheless, this method could get stuff complex over time, specially when using a large set of models, and it gets events dependent to subjects.
Another approach is using a self-contained concept of event. Everything done in the system is recorded and added to a register, to which we can access whenever we please and display it. As usual, someone has had this problem before and created a useful plugin for us, in this case it’s called timeline_fu. I added though a very small modification to serve my purposes.
Timeline_fu solves all the logic of recording events in the database. Besides, the events are smartly and efficiently stored (using polymorphic associations) so that we can access subjects, [secondary subjects] and actors directly from the event itself. A event is constituted by the following fields:
| Field | Description |
|---|---|
| event_type | A string to describe the kind of event. Examples: destroyed_message, new_task, modified_project |
| subject_type | The name of the subject. Examples: Message, Task, Project |
| actor_type | The type of object responsible to trigger the event-action. Example: User |
| secondary_subject | If necessary this can be filled with another subject, or can be left empty. In my example, this field was perfect to put the Project I was describing the events for |
| subject_id, actor_id, secondary_subject_id | This fields are automatically filled with the associated id |
| created_at, updated_at | This fields (of obvious utility) are automatically filled too. |
| subject_description | This field is a personal addition of mine to the plugin, and is used to freeze the name of the main subject (I will explain why later). Example: Christmas message, Cut the wood task, Zero6 Project. |
How do we install the plugin?
script/plugin install git://github.com/bernat/timeline_fu.git
script/generate timeline_fu && rake db:migrate
How do we register the events?
In the models, using the method fires, here come some examples:
For Task, I register the subject (the task itself), the actor (which is usually the current_user and is stored in the updater field by the controller in the actions create, update and destroy), the project is saved in the secondary_subject and the subject_description is the task name/description. I’m interested in registering the events of creating, destroying and updating tasks.
class Task < ActiveRecord::Base
belongs_to :project
belongs_to :updater, :class_name => "User"
validates_presence_of :description
fires :new_task, :on => :create, :actor => :updater, :secondary_subject => :get_project, :subject_description => :description
fires :destroyed_task, :on => :destroy, :actor => :updater, :secondary_subject => :get_project, :subject_description => :description
fires :modified_task, :on => :update, :actor => :updater, :secondary_subject => :get_project, :subject_descriptio => :description
end
Same thing for a message. The subject is the message, the actor is the sender in case of creating a new message and the updater in case of destroying it. The subject_description is the subject of the message.
class Message < ActiveRecord::Base belongs_to :sender, :class_name => "User"
belongs_to :project
belongs_to :updater, :class_name => "User"
validates_presence_of :sender
validates_presence_of :subject
validates_presence_of :body
validates_presence_of :project
default_scope :order => 'created_at desc'
fires :new_message, :on => :create, :actor => :sender, :secondary_subject => :get_project, :subject_description => :subject
fires :destroyed_message, :on => :destroy, :actor => :updater, :secondary_subject => :get_project, :subject_description => :subject
def get_project
self.project
end
end
And that’s all that needs to be done. The database is magically filled from now on with everything that happens. One of the places I decided to show the timeline of events is the general overview or dashboard, where all projects and its 15 last events are displayed. This is the overview_controller:
class OverviewController < ApplicationController
MAX_EVENTS = 15 # how many events of a project are displayed
def index
@projects = current_user.admin? ? Project.all : current_user.projects
@events = []
@projects.each do |project|
@events[project.id] = TimelineEvent.find(
:all,
:conditions => ["secondary_subject_type = ? AND secondary_subject_id = ?", "Project", project.id],
:order => "updated_at DESC",
:limit => MAX_EVENTS)
end
end
end
As you can see, I store the events in an array which index is the project.id. After in the views I will print all events for each position of the array (that is, for each project) taking particular care to link the subject if this is not nil and showing its name (for example, subject.name) or, in case it is nil, showing the subject_description which I purposely added to the plugin. This way the register will always be consistent no matter subjects of events are deleted by the time. Of course if there is the possibility to remove users from the database a similar modification should be done for users.
The milestone “Phase III meeting” has been deleted but its creation and deletion is still displayed in the register from the subject_description attribute. The icon of the event depends on the subject_type, the name and link come from the association of subject (in the case of a message, subject.subject and link_to event.subject.subject, [project, event.subject], the action in gray comes from the event_type, the name comes from the actor.name (that is User.name) and the date is the updated_at attribute.
Tags: basecampproject managing exampleprojects overviewregisterregister of eventsruby on railstimeline eventstimeline_fu
No Comments »






