2

I have a STI model where the subclasses of each use different validations. The Rails defaults will run the original type's validations at runtime, so I am trying to use "becomes" to force the validations of the new type.

My code looks like this:

payment_processor = PaymentProcessor.where(:account_id => self.id).first_or_initialize

new_gateway = "PaymentProcessors::#{gateway_type.classify}".constantize
payment_processor = payment_processor.becomes(new_gateway)
payment_processor.type = new_gateway

payment_processor.assign_attributes(attributes)
payment_processor.save!

However, it won't save because the MySQl generated during save is looking for the new type. So, for example, if my initial gateway_type is "AuthorizeNet" and I'm changing to "PayPal", the MySQL is:

UPDATE `payment_processors` SET `type` = 'PaymentProcessors::PayPal', `updated_at` = '2015-11-07 11:53:53' WHERE `payment_processors`.`type` IN ('PaymentProcessors::PayPal') AND `payment_processors`.`id` = 232

But it should be looking for the original type, Auth.net like this:

UPDATE `payment_processors` SET `type` = 'PaymentProcessors::PayPal', `updated_at` = '2015-11-07 11:53:53' WHERE `payment_processors`.`type` IN ('PaymentProcessors::AuthorizeNet') AND `payment_processors`.`id` = 232

Any ideas on how to skip the "where clause" to just update by the payment_processor ID?

Dawn Green
  • 483
  • 4
  • 16

1 Answers1

0

I learned that STI fails on validation when attempting a type change. (Read more http://blog.arkency.com/2013/07/sti). So, I solved the problem by always creating a new payment_processor but restoring the old if validation fails. It's inelegant, but it works.

Copy the original payment_processor then delete it:

original = self.payment_processor.destroy
self.payment_processor.destroy

Instead of passing the new type, we delete it from the params, but use the new_gateway_type to create the new gateway.

new_gateway_type = params[:payment_processor][:gateway_type]
params[:payment_processor].delete(:gateway_type)

For example, if the new gateway is 'Paypal', we'll constantize the new_gateway_type to create a new object, then update the params:

payment_processor = "PaymentProcessors::#{new_gateway_type}".constantize.new(:account_id => self.id)
payment_processor.update_attributes(params[:payment_processor)

Finally, we save the new gateway. If the validation fails for this new_gateway, we restore the old by constantizing the object model name, and passing in the new attributes except for type and id:

begin
  payment_processor.save!
rescue ActiveRecord::RecordInvalid => invalid
  original.type.constantize.new(original.attributes.except("id", "type")).save
end
Dawn Green
  • 483
  • 4
  • 16