Which rails ActiveRecord callback to sync with service (stripe) when creating a new record and still properly use errors?

StackOverflow https://stackoverflow.com/questions/11372477

Question

I have a User and a StripeCustomer model. Every User embeds one and accepts_nested_attributes_for StripeCustomer.

When creating a new user, I always create a corresponding StripeCustomer and if you provide either a CC or a coupon code, I create a subscription.

In my StripeCustomer:

attr_accessible :coupon_id, :stripe_card_token

What I'd like to do is, if the coupon is invalid, do:

errors.add :coupon_id, "bad coupon id"

So that normal rails controller patters like:

if @stripe_customer.save
    ....
else
    ....
end

will just work. And be able to use normal rails field_with_errors stuff for handling a bad coupon.

So the question is, at which active record callback should I call Stripe::Customer.create and save the stripe_customer_token?

I had it on before_create, because I want it done only if you are really going to persist the record. But this does strange things with valid? and worse, if you are going to create it via a User, the save of User and StripeCustomer actually succeeds even if you do errors.add in the before_create callback! I think the issue is that the save will only fail if you add errors and return false at before_validation.

That last part I'm not sure if it is a mongoid issue or not.

I could move it to before_validation :on => :create but then it would create a new Stripe::Customer even if I just called valid? which I don't want.

Anyway, I'm generically curious about what the best practices are with any model that is backed by or linked to a record on a remote service and how to handle errors.

Was it helpful?

Solution

Ok here is what I did, I split the calls to stripe into 2 callbacks, one at before_validation and one before_create (or before_update).

In the before_validation, I do whatever I can to check the uncontrolled inputs (directly from user) are valid. In the stripe case that just means the coupon code so I check with stripe that it is valid and add errors to :coupon_code as needed.

Actually creating/updating customers with stripe, I wait to do until before_create/before_update (I use two instead of just doing before_save because I handle these two cases differently). If there is an error then, I just don't handle the exception instead of trying to add to errors after validation which (a) doesn't really make any sense and (b) sort of works but fails to prevent saves on nested models (in mongoid anyway, which is very bad and strange).

This way I know by the time I get to persisting, that all the attributes are sound. Something could of course still fail but I've minimized my risk substantially. Now I can also do things like call valid? without worrying about creating records with stripe I didn't want.

In retrospect this seems pretty obvious.

OTHER TIPS

I'm not sure I totally understand the scenario. you wrote:

Every User embeds one and accepts_nested_attributes_for StripeUser

Did you mean StripeCustomer?

So you have a User that has a Customer that holds the coupon info?

If so, I think it should be enough to accept nested attributed for the customer in the user, put the validation in the customer code and that's it. See here

Let me know if I got your question wrong...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top