belongs_to causing endless loop on first call to save!

Hi,

I have a situation I’m hoping someone out there may be able to shed some light on. I have a Rails app (2.1.0 on Ruby 1.8.7) with a wizard-based sign up process, that has recently been changed from storing incremental data in the database to having a medium sized object graph living in the user session until the user completes the entire sign up process (this is a business requirement now). After the user commits all relevant data, I’m now calling save! on the top-level object to save all the rows at once. However, when the call to save! happens–and because I have two objects that have a belongs_to association, each pointing to other in addition to the normal bidirectional association–I end up in an endless validation loop. Funny thing is, when I was incrementally saving the data, this issue never arose–another way to put it, those relationships never caused a problem when saving/retrieving already-existing rows.

In the model tier, I have the following classes defined:

class PrimaryAccount < Account
has_many :payment_methods, :foreign_key => “account_id” #using single table inheritance, which is why foreign key is not “primary_account_id”
belongs_to :current_payment_method, :class_name => “PaymentMethod”, :foreign_key => “current_payment_method_id” #points to same table, so I can have many payment_methods, but also mark as being the current one to bill

class PaymentMethod < ActiveRecord::Base
belongs_to :account #normal bidirectional association

class CreditCard < PaymentMethod

In the controller tier, I have a session object that–on the final step–loads an Account model object based on the current session state, and then calls save! on that model:

class PrimaryAccountUser < User

attr_reader :credit_card

def initialize(service_id)
@credit_card = {} #hash for storing credit card attributes
end

def persist_to_database
add_session_data_to_account(PrimaryAccount.new)
account.save!
@id = account.id
return account
end

private

def add_session_data_to_account(account) #adds the impl-specific things to the account

if(!@credit_card.blank?)
  card = CreditCard.new do |cc|
    cc.account_type = @credit_card[:type]
    cc.account_name = @credit_card[:name]
    cc.account_number = @credit_card[:number]
    cc.cvv = @credit_card[:cvv]
    cc.account_expiration = @credit_card[:expiration]
    cc.address = @credit_card[:address]
    cc.city = @credit_card[:city]
    cc.state = @credit_card[:state]
    cc.zip = @credit_card[:zip]
    cc.country = @credit_card[:country]
    cc.phone = @credit_card[:phone]
  end

  #strange...this won't populate the account_id on save!
  account.payment_methods << card

  #again, if I don't do this, then the payment_method won't have a populated account_id on save!
  card.account = account
 
  #doing this causes the endless validation loop
  account.current_payment_method = card

end
   
return account

end

Am I crazy for thinking this ought to work?

Thanks in advance,

Ryan Frith
ralafapa@gmail.com

It just seems way too overengineered to me. When you add a card to the system, just add the card

In the controller, for example:

def create
card = Card.new(params[:card]
card.account_id = session[:user_id] # or current_user.id or wherever you store the id of the currently logged in user.

if account.save
   ...
else
  ...
end

end

No need to do all that account << card stuff because the web is stateless. When it’s saved, you’re going to redirect them, a new request will be made by your user, and the records get queried again. Plus you save a ton of overhead.

just my .02.

Or maybe I’m oversimplifying your situation, but I just like to keep things as simple as possible.

Maybe you didn't see the fine print--I'm saving the entire object
graph at once; there is no session[:user].id or current_user. I
greatly simplified the code for the sake of brevity, but all the
objects get assembled in the final sign-up step from state stored in
the session over many screens (wizard) and then all the objects in the
graph get persisted from the call to the top-level object's save!
method. This was a design decision to not save session state to DB in
between requests for the sign up process. Anyways, the issue may be
related to this one:

  http://dev.rubyonrails.org/ticket/1796

Anyone else have any ideas?

Thanks,

Ryan