I have a strange problem with my rails application. My application accept duplicate POST requests within one second
This duplicate request, containing the same data, strangely able to bypass the uniqueness validation of my model. This results in creation of two rows of data with exact same contents.
What really baffled me is that it only happened once a day, starting yesterday, I am not sure what caused this. ( The system are already live, and being used by my clients, this method call is used 200-300 times a day, and I cannot reproduce this at all )
So here's the situation with my code snippent and link to the full code, with chronological order
A Users want to create a new transaction, will call this method on the controller
def new
@penjualan = Penjualan.new
@penjualan.kode_transaksi = "J"+ DateTime.now.strftime("%d%m%Y%H%M%S")+@active_user.id.to_s
@customers = Customer.all(:limit => cookies[:limit], :order=>:kode_kustomer )
@barangs = Barang.all(:limit => cookies[:limit] )
respond_to do |format|
format.html # new.html.erb
format.json { render json: @penjualan }
end
end
full controller on http://pastebin.com/Lmp7hncn line 648
On the 'new' view, I have disabled the button with :disable_with, so that user cannot click on submit button twice, preventing user initiated double POST request
.row
.span4
= f.submit 'Proses', :class=>"btn btn-large btn-primary", :disable_with => "Processing..."
full view on http://pastebin.com/7b9W68RY line 97
The Submitted request will call the 'create' method on controller, the same controller as #1, This method is called twice on 1 second difference. Even more strange is that this request bypass the uniqueness validation that I defined on the model, where it is supposed to fail the second request for having the same kode_transaksi as the first request
I have a uniqueness constraints on my model (Penjualan) attributes (kode_transaksi)
class Penjualan < ActiveRecord::Base
attr_accessible :customer_id, :jatuh_tempo, :kode_transaksi, :no_sj, :tanggal_bayar, :tanggal_transaksi, :total,:total_diskon, :ongkos, :user_id, :status_pembayaran, :is_returned, :kartu_kredit, :kartu_debit
has_many :detil_penjualans
attr_accessible :cash_total, :kembali
belongs_to :user
belongs_to :customer
validates :kode_transaksi, :uniqueness =>{:message=>"Transaksi Sudah Terjadi"}
scoped_search :on => [:kode_transaksi, :tanggal_transaksi, :status_pembayaran, :tanggal_bayar, :jatuh_tempo, :total ]
scoped_search :in => :customer, :on => [:nama_kustomer, :kode_kustomer]
scoped_search :in => :user, :on => [:username]
end
My Production log with snippet of the case
Started POST "/penjualans" for 192.168.1.104 at 2012-11-24 12:15:40 +0900
Processing by PenjualansController#create as HTML
Parameters: {.... too long, see below ....}
Started POST "/penjualans" for 192.168.1.104 at 2012-11-24 12:15:41 +0900
Processing by PenjualansController#create as HTML
Parameters: {..... too long, see below ....}
Redirected to url/penjualans/17403
Completed 302 Found in 378ms (ActiveRecord: 246.0ms)
Redirected to url/penjualans/17404
Completed 302 Found in 367ms (ActiveRecord: 233.8ms)
Snippet of the logs http://pastebin.com/3tpua9gi
I am really baffled with this behaviour and I'm at my wits end. Any help will be much appreciated.
To quickly fix the problem I'd suggest you add a unique constraint to the database besides the model.
The rails docs suggest that uniqueness validation should be accompanied by a unique constraint in the database to prevent issues with two connections inserting the same unique value at the same time.
Other than that, is there maybe a problem with a user double-clicking the form in rapid succession? Maybe the disabling of the form does not work correctly and is therefore allowing users to click twice?
Is it every day at the same time or only at specific times?
The issue is caused by the way in which the model-based uniqueness constraints are implemented in Rails. Basically, they work by asking the database if there are any existing rows for the given the uniqueness constraint and refusing the create the object if that is the case.
However, given the commonly used transactional isolation levels (typically repeatable-read) you can have overlapping transactions which both successfully check the constraint and then insert their objects without knowing from each other.
That is because to achieve actual uniqueness, you have to define your constraint in the database using UNIQUE
indexes. This is much more important than defining the constraints in your model as only the database is able to ensure actual uniqueness by checking the constraint as a row is actually inserted/updated during multi-threaded operation.
About the only reason why you still want to additionally define the constraint in Ruby is that its error messages are much more friendly and you can thus handle the common case.
If the database constraint is hit rather than the Rails constraint, you'll just get false
back when calling save
without much information what went wrong except for a failed database constraint. The upside is however that you are guaranteed to still have a consistent database afterwards.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With