Consider the following scenario:
You have an account model
You have an external service which manages subscriptions (such as CheddarGetter).
You do not want to create a customer on CG unless the data entered passed your own validations, and likewise you don't want to save the customer down to your own database unless CG accepts the customer record and payment details.
However, you want any validation errors from either side to be made available to the user.
So, how would you go about this? Validating either side is simple, but getting both sides working together seems difficult.
I have found a way of achieving this.
Local validations are carried out as normal.
External validations are carried out in a before_create callback:
def save_customer_on_cheddargetter
begin
external_api_stuff
rescue => error
errors.add :base, error.message
return false
end
true
end
As long as the callback returns false for an invalid record, and adds errors to base, the user sees one validation, and also blocks saves to the database should the API return an invalid record.
Related
I'm trying to manage a HABTM relationship with a uniqueness constraint.
ie. I want my User to
has_and_belongs_to_many :tokens
But I don't want the same token to be associated with a given user more than once.
I put a unique index on the join table
add_index users_tokens [:user_id, :token_id], unique: true
which correctly results in a ActiveRecord::RecordNotUnique exception being thrown if the code tries to add the same token to a given user more than once.
In my code I was hoping to just silently catch/swallow this exception, something like this:
begin
user << token
rescue ActiveRecord::RecordNotUnique
# nothing to do here since the user already has the token
end
However, I'm running into a problem where the RecordNotUnique exception gets thrown much later in my code, when my user object gets modified for something else.
So some code calls something like
...
# The following line throws ActiveRecord::RecordNotUnique
# for user_tokens, even though
# we are not doing anything with tokens here:
user.update_counters
It's as if the association remembers that it's 'dirty' or unsaved, and then tries to save the record that didn't get saved earlier, and ends up throwing the exception.
Any ideas where to look to see if the association actually thinks it's dirty, and/or how to reset its 'dirty' state when I catch the exception?
ActiveRecord maintains in the application layer an object representation of the records in the database including relationships to other objects, and endevours to keep the application layer data representation in sync with the database. When you assign the token to the user like this:
user.tokens << token
first ActiveRecord looks for any application-level validations that would prevent the assignment, finding none it links the token to the user in the application layer, then it goes on to issue the DB request necessary to also make this connection in the DB layer. The DB has a constrant that prevents it, so an error is raised. You rescue from the error and continue, but the application level connection of the two objects is still in place. The next time that you make any edit to that same user object through ActiveRecord it will again try to bring the DB into sync with how the object is represented in the application, and since the connection to the token is still there it will make another attempt to insert this connection in the DB, but this time there is no rescue for the error that arises.
So when you do rescue from the database error you must also undo the application level change like this:
begin
user.toekns << token
rescue ActiveRecord::RecordNotUnique
user.tokens.delete(token)
end
I am not sure if I understand totally active record validation role.
Of course, if a user inputs data (like an email or a country), I can and should validate its existence, its uniqueness or its inclusion in a list of countries
But for example, if I have methods in the backend that change an attribute page_clicked or click_date or even the column update_at, that I "control" i.e 'is not generated by a user's input', should I use active record validations ?
I'm asking this because on a very 'hot database' (need speed for millions of frequent updates), I wonder if checking on each update that updated_at is a datetime, and that if a clicked column is true/false and nothing esle is really necessary as the user is not the one inputting/controlling these data but I am through Rails custom methods I wrote
Thanks
I don't think there is a general satisfying answer to your question. It's up to you to enforce validation or not.
Remember that you don't have to use ActiveRecord for validation, you can also use your DBMS to ensure that:
a value will never be NULL (one of the most annoying errors)
a value has the correct TYPE
a FOREIGN KEY always points to an existing row in another table
and depending on your DBMS, a lot more is possible
If you need high INSERT speed and want to go with raw SQL INSERTS, putting some validation in your database can prevent nasty application errors later.
Validations should guard your database and its job should be to stop saving the records that are considered invalid by your application.
There is no hard rule on what is valid record you have to decide it your self by adding the validations. If the record wont pass the validation step it is simply not going to be saved to the database.
From Active Record Callbacks:
3.1 Creating an Object
before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
after_commit/after_rollback
3.2 Updating an Object
before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
after_commit/after_rollback
You can see that validation hooks run at the beginning of the object life cycle.
So in your case instead of asking your self a question:
Should I use active record validations if the record is not generated by a user's input.
You should ask your self:
Is this record invalid without page_clicked or click_date(aka them being nil)
UPDATE
If you consider record to be invalid but worrying about speed problems with running validations I would do the validations to make sure that all the records in the database are valid and try to find the way to optimise the speed somewhere else. Plus not 100% sure but time spend on saving invalid records and filtering them later on will be probably much longer then validating in the first place.
When performance is really a priority and that I am sure that we developers / the server are the only ones who can manipulate specific attributes of a Model, I will
Make sure that I create a separate method / wrapper method for this specific action.
In this specific method, I call .save (validate: false) instead of the usual .save
I still write validations for the said attributes for developers' reference to prevent future development errors, and in case a new developer comes in and accidentally save an invalid record, precisely just because there's no validation to safeguard it.
Or, I will use .update_column instead of .save (validate: false) to perform a direct DB call, skipping Model validations and callbacks (If you also do not want callbacks to be called).
Note that .update_column is different from .update.
So a situation came up at work and I wanted to discuss it here because we could not get to an agreement between us:
We have two models, Order and Passport, which are related in a way that an Order has_one passport and a passport has_many orders. Whenever an order is completed, its associated passport must be 'locked', that is, turned into read-only (that information was already used to clear customs, so it can't be changed afterwards). We want to enforce that rule in the Passport model and we've thought of the following options:
Creating a validation. CONS: There will be records yielding valid? => false when technically the record is fine (although it can't be saved). For example, if other records have a validates_associated :passport on them, that could be a problem.
Overriding the readonly? method. CONS: This will raise an exception when trying to update that record, although you would expect that calling a save method won't ever raise one.
Creating a before_save callback. This has two flavors: either raise an exception (which is pretty much like the readonly? option) or add an #error and return false to stop the callback chain. CONS: Adding validation errors from outside a proper validation can be considered a bad practice. Also, you might find yourself calling valid? and getting true and then call save and get false.
This situation made us think a lot about the relationship between validations and Rails. What exactly does it mean for a record to be valid?? Does it imply that the save will work?
I would like to listen to your opinions to learn about this scenario. Maybe the best approach is neither one of the three! Thanks!
What about marking this record as read-only by using readonly! instance method? See the API
You could do it in a constructor, like:
class Passport < ActiveRecord::Base
def initialize(*args)
super(*args)
readonly! if orders.count>0 # or similar
end
end
I think there is an extra alternative. What you describe dictates that the Passport model can have some different states. I would consider using a state machine to describe the relevant orders status for the passport.
eg:
open
pending
locked
other_update_actions ...
With that in mind, all relevant order actions will trigger an event to the passport model and its state.
If it is possible to integrate the update actions to certain events then you could handle the readonly part in a more elegant way (incompatible state transition).
As an extra check you can always keep an ugly validator as a last resort to prevent the model from being updated without the state machine.
you can check the aasm gem for this
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.
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.
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...
Given a model that has validations in the model_name.rb file, how can I access those validations manually? I'd like to cook up my own form validation system that would work alongside the built-in Rails tricks and I want to keep everything as DRY as possible. My main problem is that I need to make server-side validations before any of the form values hit the database (I'm using a multistep form).
Basically I'm wondering if there is a method like
User.validations.each do |v|
puts v.constraint.to_s + " " + v.message
end
Is there anything similar to this?
Thanks in advance.
My main problem is that I need to make
server-side validations before any of
the form values hit the database (I'm
using a multistep form).
If your model is not valid according to the validations in its class file, then its data won't get saved to the database (unless you pass false to the save method to suppress validations).
You can ask a model if it's valid at any point by invoking its valid?/invalid? methods.
The ActiveRecord object exposes the errors method after valid? is called, which gives you messages about which validations are violated. You could check valid? and then check to see if any of the fields on the part of the form you are on are invalid.
you could do something like this for a form with fields field1 and field2.
unless x.valid?
[:field1,:field2].each do |field|
yes_there_was_an_error if x.errors[field]
end
end
Your best bet is to use a state machine and store the data in the database between the various steps in the form.
You can do e.g. validates_presence_of :username, :if => proc {|u| u.signup_step >= 2 }, where signup_step is an integer column in the database.
So, even though you say you don't want to store it in the database between the forms, I think you should. If you do this, you can use regular validations and models, without nasty hacks. And honestly, I doubt doing it this way is a problem.