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!