Sam J
Sam J

Reputation: 75

Adding a validation to an existing database model

I am a junior developer (Ruby on Rails 5) currently implementing a new validation on an existing model. The validation only passes if the attribute in question is no greater than 50 words.

  validates_length_of :reason, maximum: 50, too_long: 'Please reduce to 50 words or less',
                  tokenizer: ->(str) { str.split(/\s+/) }

Unfortunately there are already 54 records in our database which violate this validation. So, I'm searching for a solution to ensure the validation would never get run for these pre-existing records.

My research so far has yielded the on: :create option which seems promising, but I wanted some experienced feedback on whether I might run into problems/bugs down the line.

Will this validation option give me exactly the behaviour I want?

Thanks folks!

Upvotes: 0

Views: 225

Answers (3)

3limin4t0r
3limin4t0r

Reputation: 21110

I would do something like this:

validates_length_of :reason, maximum: 50,
                             tokenizer: ->(str) { str.split(/\s+/) },
                             too_long: 'Please reduce to 50 words or less',
                             unless: -> { reason_was && reason_was.split(/\s+/).size > 50 }

This executes the validation unless reason attribute was previously present and was greater than 50. Meaning that new records never have a reason word count greater than 50, because they don't have a previous reason value. And old records can only have reason word count larger than 50 if it previously was larger, otherwise the maximum of 50 is applied.

This makes use of the methods generated methods provided by ActiveModel::Dirty.

Upvotes: 0

bo-oz
bo-oz

Reputation: 2872

The new validation is only run against Create or Update actions. This means that existing records in the database will stay as they are. In other words, as long as they aren't updated, you won't run into any issues.

Is this what you want? Or do you like to take allow the older records > 50 words even when updating? If you really really need to, you could create a new attribute, called for instance legacy, which you set to true for any existing records. You could skip validation for any legacy record. But I think it's a bit against the point.

Upvotes: 0

Danilo Cabello
Danilo Cabello

Reputation: 2963

Yes you are on the right track, you have a few options when it comes to dealing with existing data.

You should add the validation to on: create because you don't want people (or jobs) that are changing other unrelated fields in a separate process to have an error because the reason is invalid and too long.

However if you only add on: create it means that someone can create a reason with less than 10 words, then update the record on the update UI to 60 words and violate the validation. Consider also adding the same validation with if: :reason_changed?, this way you prevent updates breaking the validation rules imposed on creation.

Another reasonable solution you may use in this case is perform a data migration on the existing records, for each record that violates the validation, you trim the words down to 49 add a ... at the end and save it. That would lose information however it would mean you can apply this validation always 100% of the time. Sometimes fixing the data is a great option to write less code.

Whatever you choose make sure you have tests to increase your confidence that the code is doing what you believe is supposed to be doing.

Upvotes: 1

Related Questions