0

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!

3
  • 1
    I believe that two simple test cases should answer your question. If you are not using a test suite already - I very much recommend doing so. It will make things much easier, especially with these kinds of cases. Regarding your question - Have you thought about a case where user updates his answer? If it is not possible, then you should be ok. If it is - a dirty solution would be to put a constraint on the validation to check if created_at is bigger than your last record before validation was put in place. Commented Feb 9, 2019 at 12:55
  • Are these old records allowed to have more than 50 words when updating them after implementing the new validation? Or can they only have more than 50 words as long as the records aren't updated? Commented Feb 9, 2019 at 14:40
  • I envisage allowing old records to be updated with > 50 words to avoid client confusion Commented Feb 9, 2019 at 15:27

3 Answers 3

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for this. I wonder if I could run the validation as follows: always on creation of a record and only on updates to a record which already has no greater than 50 words. In other words, the only time the validation does not run is if the record is updated and, before the update, already had > 50 words.
That's a reasonable approach, keep in mind that this will eventually become legacy code and a new developer may be surprised by this validation on update that allows records already with more than 50 words to be valid. That's when you leave a breadcrumb either on the commit message or a code comment "~ 50 records had more than 50 words when this validation was first introduced, let them be valid" or something similar explaining why this was done.
0

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.

2 Comments

It is conceivable that an old record will be edited. In this case, should it already have > 50 words, I would like for the update to allow > 50 words (ie. no validation). In all other cases (new record, updates to records with <= 50 words) the validation should be run
The only way to achieve this is by following the advise in my second paragraph, If you change the validation to only on update, you could basically avoid the validation rule by saving and editing to > 50 words, like Danilo explained in his answer.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.