Lost in Transaction
November 4, 2010 I am working on an issue tracking system at work. I built in an API so other systems can create events, but I wanted to make sure to minimize duplicate events, and opted to just add a comment in case the same issue was opened twice (or more.)I started with this test
context "don't allow duplicate issues" do
setup do
@issue = Issue.create(:name => "something", :start_at => "1/1/2010")
end
should "not create a duplicate issue" do
assert_no_difference('Issue.count') do
another_issue = Issue.create(:name => "something", :start_at => "1/1/2010")
end
end
should "create a new issue if the duplicate issue is closed" do
@issue.update_attributes(:resolution => "something")
assert_difference('Issue.count') do
another_issue = Issue.create(:name => "something", :start_at => "1/1/2010")
end
end
should "add a comment on existing issue" do
assert_difference('@issue.comments.count') do
another_issue = Issue.create(:name => "something", :start_at => "1/1/2010")
end
end
end
Then wrote this code:
class Issue < ActiveRecord::Base
has_many :comments
validate :check_for_open_duplicate, :on => :create
scope :open, where(:resolution => nil)
protected
def check_for_open_duplicate
open_issue = Issue.open.where(:name => name, :description => description).first
if open_issue
open_issue.comments.create(:body => "Duplicate attempted to open")
errors.add(:duplicate, "Duplicate Issue")
end
end
end
But it kept failing, which was weird to me, because it seemed right.. it wasn't until I tried it from the development environment and checked out the logs that I saw what was happening..
SQL (0.1ms) BEGIN
Issue Load (0.1ms) SELECT `issues`.* FROM `issues` WHERE (`issues`.`resolution` IS NULL) AND (`issues`.`description` = '') AND (`issues`.`name` = 'something') ORDER BY start_at DESC LIMIT 1
SQL (0.2ms) SELECT COUNT(*) AS count_id FROM `comments`
SQL (18.6ms) INSERT INTO `comments` (`body`, `commentable_id`, `commentable_type`, `created_at`, `updated_at`, `user_id`) VALUES ('Duplicate attempted to open', 3370, 'Issue', '2010-11-03 14:36:12', '2010-11-03 14:36:12', 1)
SQL (47.5ms) ROLLBACK
The whole request was being wrapped in a transaction and being rolled back.. A little google searching later, I found after_rollback, and wrote a new callback
class Issue < ActiveRecord::Base
attr_accessor :duplicate_issue
has_many :comments
validate :check_for_open_duplicate, :on => :create
after_rollback :add_duplicate_comment
scope :open, where(:resolution => nil)
protected
def check_for_open_duplicate
open_issue = Issue.open.where(:name => name, :description => description).first
if open_issue
@duplicate_issue = open_issue
errors.add(:duplicate, "Duplicate Issue")
end
end
def add_duplicate_comment
@duplicate_issue.comments.create(:body => "Duplicate attempted to open") if @duplicate_issue
end
end
And now everything is working as intended. So if you run into a similar situation, I hope that this will help out.