Talks

Validate Me! - Demystifying Rails Validators

Validate Me! - Demystifying Rails Validators

by Rachael Wright Munn

In the presentation titled "Validate Me! - Demystifying Rails Validators," Rachael Wright Munn explores the intricacies of validation within Rails applications. The session targets engineers of varying skill levels at the Blue Ridge Ruby 2024 conference, focusing on how Rails validators work and how to create custom validators effectively. Rachael begins with a brief introduction of her qualifications, including the addition of the ComparisonValidator to Rails, before diving into the main topic.

Key Points Discussed:
- Nature of Validators: Rachael emphasizes the role of validators in ensuring that specific attributes within a Rails model meet certain criteria, using examples such as the presence validator and the comparison validator.
- Structure of Validations: Validators in Rails inherit from Active Model, which maintains consistency across the framework. Each validator implements a method called validate_each, which handles error reporting when conditions are unmet.
- Active Model vs Active Record: Rachael distinguishes between Active Model, which deals with Ruby objects and validates them without recognizing the database, and Active Record, which manages database interactions.
- Concurrency Issues: An important point discussed was the limitations of validators in protecting the database, particularly with the uniqueness validator, which can lead to concurrency issues without proper database constraints.
- Creating Custom Validators: Rachael provides detailed guidance on developing custom validators, outlining where they can reside (models, lib directory, or as a gem) and the process required to add them to Rails. She highlights the importance of following naming conventions, inheriting from Active Model validators, and implementing the validate_each method.

In conclusion, Rachael encourages engineers to innovate with their custom validators, emphasizing the long-term commitment required to contribute to Rails, as evidenced by her own year-long journey to integrate the ComparisonValidator. She expresses gratitude to the conference organizers and offers the audience the opportunity to ask questions after her talk, reinforcing her open, engaging presentation style.

00:00:11.840 Rachael has been a software engineer since 2012, a three-time team lead, and enjoys live streaming her open-source contributions on Twitch.
00:00:13.639 She added a ComparisonValidator to Rails, built the Jekyll-Twitch gem, and is currently working on an app for making friends at conferences. So that's pretty appropriate for something to use right now.
00:00:29.240 Anywho, here she is!
00:00:33.940 Hello Blue Ridge Ruby! I am here to discuss validation, or more specifically, validators in Rails.
00:00:42.360 From the title, you probably guessed that I would be talking about validators. However, I'd like to focus more on demystifying Rails validators. I know I'm speaking to a group of 60+ engineers with varying experience levels, and most of you have probably written something like this before—where we validate two attributes and the presence validator is ensuring that those two attributes are present.
00:00:50.920 If they are not, it adds an error to the actual object stating that "start_at" or "end_at" is blank. I would even bet that some of you have done this where we're validating one attribute with multiple validators.
00:01:07.600 So here we have the presence validator, but also the format validator, joined by a uniqueness validator. A select group of you have probably worked with validation helpers, such as validates presence of—which is again the presence validator in a different form—and we also have the comparison validator. This is my absolute favorite validator because as Joe mentioned, I added it to Rails.
00:02:03.479 When I added this validation helper, I actually added it in the same file as the validator, specifically "comparison.rb". Even though it resides in "comparison.rb", it lives inside the Active Model validations helper methods module.
00:02:11.440 To clarify, this method starts at Active Model, which contains the validations module. Within this, we have the comparison validator itself along with its helper methods. The function "validates_comparison_of" simply calls "validates_with" for the comparison validator and merges in any attributes that were passed to that method call.
00:02:36.960 You can see this when looking at the Rails documentation. In the active model validations helper methods, the helper methods are sourced from each of the individual validator files. If this documentation looks a little unfamiliar to you, you should probably use api.rubyonrails.org, which is the official source of Rails documentation instead of API doc.
00:03:05.040 So we know that validation helpers are in the same module but sourced from their separate files. Now, let's take a look at one of those separate files. I know you're sitting here thinking, "But CH, you're the creator of the comparison validator. Why would you use the presence validator?" The answer is simply that it fit on the slide.
00:03:55.959 Here we have the validation helper that uses the validates with presence validator, followed by an entire section that is the documentation for the validation helper. At the very top of the file, we have the presence validator itself.
00:04:27.720 Now, if we take a closer look at the presence validator, it becomes clear how these validations are added within Rails. Each validator inherits from Active Model, which establishes consistency with every single validator that's part of the standard library.
00:05:00.000 Next, we implement a method called `validate_each`, which accepts the record, the attribute name, and the attribute value. When we implement that method, we will add an error to the record for the attribute name if the value is blank.
00:05:29.000 This pattern is consistent across all validators created within Rails. We need to have a record, add an error for the attribute name, or nothing if we'd like it to apply to the entire record. The distinction is significant because it illustrates that the validators don't protect your database; they only validate the objects that the framework deals with.
00:06:13.240 So Rails is composed of framework components, particularly Rails 7, made up of 12 gems with different responsibilities that all require the same version. At present, we will focus on Active Model and Active Record. Active Support handles a lot of the magic in Rails.
00:07:02.920 For example, things like 12 days ago, those are all handled by Active Support. If you've ever been on Stack Overflow and asked how to accomplish something in Ruby, someone will likely reply that it's actually an Active Support feature.
00:07:45.599 Active Model focuses on Ruby objects with attributes while Active Record is specifically about Active Models that are connected to a database. So attributes and validations live in Active Model, while Active Record handles querying and everything related to databases.
00:08:08.160 If you take a look at the runtime dependencies of Active Record, you will see that it depends on Active Model, which means it has access to everything within it. Conversely, Active Model does not depend on Active Record, reinforcing that reducers do not interact with the database.
00:08:52.880 Most validators that we talk about, with the exception of a few, live in Active Model. This means they do not know that your database exists. The exceptions include validators like `absence`, `acceptance`, `comparison`, `length`, `numerical`, and `presence`, which are aware of the database. They utilize information from your database column to inform the parsing of values.
00:09:36.839 Some validators can look at your whole database table to check for uniqueness. However, it's worth noting that this uniqueness validator is prone to concurrency issues.
00:10:00.960 As previously discussed, Active Model doesn't recognize that a database exists. It's focused on examining a Ruby object at a fixed moment in time and checking its attributes.
00:10:25.240 The concurrency challenges arise because if two queries are being run simultaneously without proper database constraints, the uniqueness cannot be guaranteed. Therefore, if you have only a uniqueness validator, it may be prudent to consider establishing a unique index on that column.
00:11:02.799 This brings us to the first main reason that validations do not offer database protection. Additionally, validations do not check existing data; they only work on new data unless a new validation constraint is added afterward.
00:11:43.680 In practicality, a new constraint means when that individual attempts to update that record next, they’ll run against the latest validation.
00:12:05.799 So now that we have extensively discussed the theory behind validations, let’s move on to practical aspects like building your own custom validator. Custom validators allow you to validate anything or anyone.
00:12:58.000 The first thing to determine is where your custom validator should live. You can create it within your models, which is standard and perfectly valid. Alternatively, you can put your custom validators in the lib directory or even in a gem.
00:13:29.000 If you place it inside a gem, it'll be easier to import and reuse across different applications. Lastly, you can also build your custom validator right inside Rails. I’ve organized the options from fastest to the slowest method to integrate a validator.
00:14:18.000 To add your validator to Rails, you’ll first create it along with tests, documentation, and a changelog (which I forgot when adding the comparison validator). Next, you need to convince a member of Rails Corp or a Rails committer that your validator should be included in the standard library.
00:14:56.920 Once you've completed the exhaustive review process for your validator, tests, documentation, and changelog, you’ll wait for the next Rails release for it to be integrated.
00:15:40.079 Finally, you would upgrade your application to use your new validator.
00:15:42.440 It's important to set realistic expectations about this process. As I mentioned, it took me over a year to add the comparison validator.
00:16:15.560 Nevertheless, I want you to have an enjoyable experience building custom validators.
00:16:40.000 To effectively name your validator, follow the naming convention of something validator. After you've inherited from Active Model validator, implement the method `validate_each`, which includes record, attribute name, and value, and follow the earlier-mapped pattern for validations.
00:17:09.839 Next, optionally include a validation helper, which facilitates the addition of validations throughout your application. Hence, you would have effectively created a custom validator!
00:17:57.799 Thank you for listening and engaging with me! Right after this talk, I will post on my site with slides, and you can find the link to my content.
00:18:32.960 I would like to give special thanks to those who helped me with this presentation, especially Matthew D for reviewing the technical correctness of my talk and the other contributors who assisted me.
00:19:03.120 Thanks are also due to Mark Laer and Joe Pack of Blue Ridge Ruby for organizing this excellent conference and giving me the opportunity to speak.
00:20:01.120 And of course, my heartfelt gratitude goes to all of you for being such an attentive audience and for indulging me. If you have any questions, feel free to ask, as I have no immediate lunch plans!