Problem
With high volume Rails applications, entities with unique constraints are expensive and error prone to create/update. ActsAsInsertOrUpdate helps solve that problem (if you're using MySQL), by leveraging the "INSERT ... ON DUPLICATE KEY UPDATE" functionality.
Scenario
Lets say you have a Person, and Entity, and a Rating. Each user can rate each entity only once, and if they re-rate the entity, it should update the value.
class Entity < ActiveRecord::Base has_many :ratings end class Person < ActiveRecord::Base has_many :ratings end class Rating < ActiveRecord::Base belongs_to :Person belongs_to :Entity end
Here is the table that back's Rating. Notice the Unique Key constraint on (entity_id, person_id).
CREATE TABLE `ratings` ( `id` int(11) NOT NULL auto_increment, `rating` tinyint(4) default '0', `person_id` int(11) default NULL, `entity_id` int(11) default NULL, `created_at` datetime default NULL, `updated_at` datetime default NULL, PRIMARY KEY (`id`), UNIQUE KEY `index_ratings_on_entity_id_and_person_id` (`entity_id`,`person_id`), )
Previously, the logic would be something like:
- 1) Check if a rating exists for the User + Entity
- 2) If so, update
- 3) If not, insert
- 4) rescue the insert in case there is a unqiue constraint error
- 5) retrieve the record (and/or update with the new rating)
If the table is MyISAM, Steps 1-5 aren't transactionally safe. If you're using InnoDB, and experience heavy volumes of traffic, you're prone to Deadlock's. This is even more of a concern is the unique entity is shared across multiple users, as seen with a recent client of ours.
Solution:
class Rating < ActiveRecord::Base belongs_to :Person belongs_to :Entity acts_as_insert_or_update :field_to_update => "rating" end
Now Steps 1-5 above become, just one. Rating.create(..)
In the background, ActsAsInsertOrUpdate overwrites the implementation of ActionRecord:Base#create, to leverage an often unsed feature of MySQL called INSERT ... ON DUPLICATE KEY UPDATE. As configured above, if a duplicate record is found for the unique constraint, the rating field will be updated with the new value.
Caution
This is a brute force hack on ActiveRecord::Base#create. Use at your own risk.
Code
Waiting for a rubyforge account. Will post more info soon.
