Active Record has a nice feature that enables the programmer to separate concerns. In the application I am building, I need to reject some transactions based on business rules. For example, if an attacker attemps to empty an account by requesting multiple payouts, or attempting to load more than a specific amount of money in the user’s account.

In this application, I am using STI (Single Table Inheritance) to represent the different types of transaction. The hierarchy looks like this:

1 class GreenbackTransaction < ActiveRecord::Base
2 end
3 
4 class AccountRechargeTransaction < GreenbackTransaction
5 end
6 
7 class AccountPayoutTransaction < GreenbackTransaction
8 end

I defined my observer like this:

app/models/greenback_transaction_observer.rb
1 class GreenbackTransactionObserver < ActiveRecord::Observer
2   def after_create(txn)
3     # Code to reject the transaction if it is suspicious
4   end
5 end

Using the console, everything was working fine. So, off I went to write a test for it (I know, it should have been the other way around, but I haven’t used observers much, and I wanted to see what was going on).

My test is defined like this:

test/unit/greenback_transaction_observer_test.rb
 1 class GreenbackTransactionObserverTest < Test::Unit::TestCase
 2   fixtures :greenback_transactions, :users, :accounts, :affected_accounts
 3   
 4   def setup
 5     @sam = users(:sam)
 6     Setting.recharge_amount_threshold = 150.to_money
 7   end
 8   
 9   def test_suspicious_transaction_rejected
10     assert_nothing_raised do
11       AccountRechargeTransaction.new(:account => @sam.account, :amount => 100.to_money)
12     end
13     
14     assert_raise(TransactionFailureException) do
15       AccountRechargeTransaction.new(:account => @sam.account, :amount => 100.to_money)
16     end
17   end
18 end

To my complete surprise, this didn’t work. After much investigation, I found that the observer was not loaded for GreenbackTransaction. After some fooling around, adding logging statements in Rails core, I finally stumbled upon the solution:

app/models/account_recharge_transaction_observer.rb
1 class AccountRechargeTransactionObserver < ActiveRecord::Observer
2   observe AccountRechargeTransaction
3 
4   def after_create(txn)
5     # Code to reject the transaction if it is suspicious
6   end
7 end

The problem was the observer was registered on GreenbackTransaction, and it seems the observers aren’t inherited in subclasses. This is important and bears repeating: if you use STI and observers, observe your subclasses !.

Search

Your Host

A picture of me

I am François Beausoleil, a Ruby on Rails and Scala developer. During the day, I work on Seevibes, a platform to measure social interactions related to TV shows. At night, I am interested many things. Read my biography.

Top Tags

Books I read and recommend

Links

Projects I work on

Projects I worked on