Acts as Placed On Timeline
Ruby forge project
|
Acts as placed on timeline API
|
DateTimeRange object API
AAPOT is a plugin/gem (download) that adds temporal awareness to ActiveRecord objects. The best way to explain what AAPOT does is by example. So let’s start with a simple one.
Say we want to create an address book application. We want all the default functionality but we also want to be able to see all the addresses where a person has ever lived.
class Person < ActiveRecord::Base
has_many: addresses
end
class Address < ActiveRecord::Base
belongs_to: person
end
ActiveRecord enables us to find all the addresses that belong to a person by just calling Person.find(:first).addresses. In our case this would include both current and previous (or even future) addresses. If we wanted just the addresses in the past we would have to pass some conditions.
Using AAPOT we have a clean and simple way of doing this automatically.
class Address < ActiveRecord::Base
acts_as_placed_on_timeline
belongs_to: person
end
If we redo our example from before with AAPOT, Person.find(:first).addresses would yield all the current addresses. If we wanted the past addresses too we could do: Person.find(:first).addresses.find(:all, :at => DateTimeRange.past) or even Person.find(:first).addresses.find_whenever, when we still needed all the addresses.
So what do we use this for?
AAPOT has many uses. It’s primary goal is to simplify modeling objects that have a validity in time (for example addresses, contracts, reservations or tasks). But there are some other ways to use AAPOT like basic versioning.
Why should I use this?
We override a lot of ActiveRecord functions to add an :at option. This simplifies a lot of the queries you have to do. Where previously you would have to do: Address.find(:all, :conditions => ['valid_from > ? AND valid_to < ?', start, stop]) you can now simply do: Address.find(:all, :at => [start,stop]). This may seem like a small syntactic thing but in our experience timeline queries introduce a lot of stupid mistakes/bugs. Having a generic way to talk to these object will save you time and aggravation.
When you get to the more advanced features of AAPOT you will love the way it just ‘works’, trust us!
What do I need to get it working?
Just install the plugin or gem and enable it for your model.
svn checkout http://ar-timeline.rubyforge.org/svn/trunk/vendor/plugins/acts_as_placed_on_timeline
class Address < ActiveRecord::Base
acts_as_placed_on_timeline #enables the timeline plugin
end
As the plugin required some fields in the database you will have to modify your default migration.
create_table :addresses, :placed_on_timeline => true do |t|
t.string :street
end
If you already have a create migration you can just add the fields and indexes manually.
add_column :addresses, :timeline_id, :integer
add_column :addresses, :valid_from, :date, :default => DateTimeRange::BEGINNING_OF_TIME, :null => false
add_column :addresses, :valid_to, :date, :default => DateTimeRange::END_OF_TIME, :null => false
add_index :addresses, [:timeline_id,:valid_from,:valid_to]
add_index :addresses, [:valid_from,:valid_to]
Advanced features, what do those do?
We’ve been working with temporal objects for quite a while now and we’ve been able to extract the most common features everybody will need when using temporal objects.
One of these features is making sure your database stays consistent. Again we explain by example.
class Person < ActiveRecord::Base
acts_as_placed_one_timeline
has_many: addresses
end
class Address < ActiveRecord::Base
acts_as_placed_one_timeline
belongs_to: person
end
We use the same example as above, but we expand it. We would like to version the Person model, so we can have a record of each change we made to it.
When we make a change to a person, a new object is created and the old object is ‘closed’. AAPOT is clever enough to see that if we did this the new object would not have any addresses (the new object has a different ID, which is stored inside the address).
So after each save the person’s addresses would be lost. That’s just stupid. So AAPOT looks at the associations of Person (in this case only has_many :addresses) and rebuild those accordingly.
In the example above it would automatically create new versions of the Address objects since they are AAPOT objects. When AAPOT objects have associations with normal objects, these are automatically cloned and relinked.
Where can I find out more?
Ruby forge project
Acts as placed on timeline API
DateTimeRange object API
