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
2
3
4
5
6
7
8
class GreenbackTransaction < ActiveRecord::Base
end

class AccountRechargeTransaction < GreenbackTransaction
end

class AccountPayoutTransaction < GreenbackTransaction
end

I defined my observer like this:

1
2
3
4
5
class GreenbackTransactionObserver < ActiveRecord::Observer
  def after_create(txn)
    # Code to reject the transaction if it is suspicious
  end
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:

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

1
2
3
4
5
6
7
class AccountRechargeTransactionObserver < ActiveRecord::Observer
  observe AccountRechargeTransaction

  def after_create(txn)
    # Code to reject the transaction if it is suspicious
  end
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

A picture of me

I am François Beausoleil, a Ruby on Rails coder. During the day, I work on XLsuite. At night, I am interested many things. Read my biography

Tags

(3) (1) (0) (2) (1) (1) (2) (2) (1) (2) (1) (2) (1) (2) (1) (1) (1) (1) (2) (14) (1) (1) (1) (1) (2) (1) (1) (2) (0) (1) (2) (1) (3) (1) (1) (1) (1) (1) (1) (0) (3) (2) (1) (2) (2) (1) (3) (2) (8) (8) (9) (12) (1) (1) (3) (1) (1) (1) (1) (1) (1) (2) (2) (2) (1) (1) (3) (1) (3) (1) (0) (23) (1) (1) (0) (1) (1) (1) (23) (25) (1) (1) (13) (1) (1) (2) (3) (1) (1) (4) (1) (2) (3) (0) (1) (7) (3) (1) (5) (5) (2) (2) (2) (4) (6) (7) (1) (0) (1) (1) (2) (2) (1) (4) (12) (2) (1) (2) (4) (1) (1) (1) (2) (8) (2) (3) (2) (2) (1) (3) (1) (1)

Links

Projects I work on

Categories

Archives