00:00:20.640
as Drew mentioned, my name is Andy Andrea based in Durham, North Carolina. I've been working with Rails
00:00:26.000
professionally for a little over a decade at this point. And in that time, I've used the Rails validation library
00:00:32.800
quite often. Sometimes too often, sometimes not quite often enough. And a
00:00:38.239
couple years ago, uh, I started working on an application that has a pretty customized in-house data access layer.
00:00:44.480
It's an event source application where we have tons of events moving through the system as hashes, as value objects.
00:00:51.280
no active record, no real model layer in the traditional rail sense. And about a
00:00:56.399
year ago, I was tasked with coming up with a way to validate that our events make sense. They're not corrupted. They
00:01:02.800
have all the data, all the shape that they uh need in order for the system to function. And I was told that that
00:01:10.080
validation library should be a single class. We didn't want to have to make new classes for every type of event and
00:01:15.680
then register the validations on all of those classes. And so I thought, well, that isn't very Railsy, but it would be
00:01:23.439
great if I could figure out a way to use the built-in Rails validators because then we don't have to worry about
00:01:29.040
maintaining them. We don't have to worry about documenting them. We don't have to worry about reinventing the wheel and
00:01:34.079
just to get a fraction of the power that we can get from Rails. And so I decided to give it a shot. And it's actually
00:01:40.799
been in production for a little over a year now. So it worked kind of to my surprise. And today I will not be
00:01:47.600
showing you that code, but I will in fact be showing you something that I think is much better. So for anyone that
00:01:54.560
might have snuck in from my work, please don't fire me for that. Uh I am going to assume that everyone
00:02:01.920
has some experience with Rails validators in this talk, but I'm going to take a little bit to just establish
00:02:08.160
some some shared language. And in case anyone is wondering why my slide theme
00:02:13.520
changed from slide to slide, it is because I procrastinated and did not know that there was an official slide
00:02:18.800
theme. So, we're stuck with the first one from Google Slides. Uh, similarly, I'm kind of living life
00:02:24.800
on the edge because I did not change the batteries in my remote since Atlanta, which for those of you who don't know
00:02:30.239
was two years ago. So, we will see how this goes. But, so back to topic with our Rails
00:02:37.360
validators. We kind of have two sides to the same coin. Classes and instances. And at the class level, we have methods
00:02:44.480
that I'm going to say are used for registering validations. These are things like validates, validate,
00:02:50.400
validates with validates presence of, there are quite a quite a good number of these. Um, if we look at our Rails
00:02:56.959
models in Rails 8 and on the flip side, the instance level, we have still a good
00:03:04.480
number of methods, uh, 27, but only four are really relevant to what I think the
00:03:09.840
instance methods often typically do, which is running validations that have already been registered at the class
00:03:16.720
level. Those methods are valid, invalid, validate, invalidate, bang. So again, class level registering your
00:03:23.360
validations, instance level running the validations.
00:03:28.879
And as far as we're concerned, we're just going to be talking about active model today. We do have some validation
00:03:33.920
logic that comes from active record. Typically the stuff dealing with the database, uniqueness, associations, and
00:03:39.200
the like. But in active model, we've got the validations directory, the validations.rb file, the validator.rb.
00:03:47.440
Great thing about being a speaker is sometimes you just pronounce words like validator for no apparent reason. Um the
00:03:53.519
validator is basically where the base classes live. Validations directory is where a lot of the validators like the
00:03:59.680
presence validator, the numeric numericality validator and the like live. Validations.rb is kind of a lot of
00:04:04.959
the helper files bring it all together. And so I mentioned before that I really
00:04:11.599
like and in some cases have probably overused Rails validators. There have
00:04:16.720
been a couple of situations though where I've wished that they work a little bit differently and that makes sense. It's a
00:04:22.560
convention over configuration framework. So those things are I've sometimes
00:04:27.840
wished that I could use them with non-model classes. I've had a hash like in the example I gave earlier where I
00:04:34.160
wanted to validate the key value pairs rather than the columns and the row values uh associated with a particular
00:04:41.199
model. And I've also wanted in certain cases where I've had very complex
00:04:47.199
validation logic with tons of conditionals that says this validation should run or this validation shouldn't
00:04:53.040
run. I would have liked the ability to compose my validations on the fly rather than having them all registered at some
00:04:59.919
global class level in my application. Additionally, I've run into some
00:05:06.080
situations where I've maybe had a param that comes in in a controller that should affect whether or not some
00:05:12.560
validations run or don't run. And since validations are all at the model level, that means you have to get that data
00:05:18.639
into the model. That might mean making a new active model model. That might mean
00:05:24.080
adding some kind of a new attribute or adder accessor to your existing model and putting that in where it's sometimes
00:05:30.240
used but often times not that relevant. And really, there are ways to get around most of these. Most of them involve just
00:05:36.720
making a new class, but sometimes it would have been nice if we didn't necessarily have to go that approach.
00:05:42.720
Sometimes that might feel a little bit heavy-handed to us. And so the question is, do these things
00:05:49.840
matter? I would say no, not really. But since that's really bad marketing for this
00:05:56.320
talk, I'll say please don't leave just because I said that. And so in this talk, we are going to assume that these
00:06:02.639
things are at least worth considering and worth investigating. And so our goal by the end of this talk
00:06:09.520
is to have a validator class that can run Rails validators against any kind of
00:06:14.639
input, whether it's a hash, whether it's a model. We're going to do this without any monkey patching, without any kind of
00:06:20.080
type casting or coercion. We're also going to have it have its own independable or independent state at the
00:06:27.039
instance level. So nothing global, nothing dependent upon any other validations that might have been
00:06:32.240
registered in your system. And we're also not going to care about the shape or the structure of that data. If you
00:06:38.240
have hashes in array, it's going to work. If you have hashes that are deeply nested, it's going to work. If you've got a mixture of hashes and models,
00:06:45.440
it'll all work. So to get started on that, we're going to start off with a fairly simple proof
00:06:51.280
of concept where we're just going to say we want to run the Rails presence validator against a hash.
00:06:58.800
And so to get started, we can look at the source code for presence validator. And it's actually really simple. This
00:07:04.479
isn't the whole thing, but it's most of the logic in there. There's some extra comments, a little helper method, but
00:07:09.759
this is the entire validate each method. And this is really the only public method that we see on presence validator
00:07:15.360
when we look at the source. And it's pretty simple. It takes in a record which is typically going to be an active
00:07:20.800
record model in our typical use cases, but now is going to be a hash. Adder name that would typically be a column on
00:07:27.199
a table is now going to be a key in that hash. And the value is going to be the value associated with that key. And so
00:07:34.000
it's very simple. We add an error if the value is blank. So we're going to start off with something very, very naive.
00:07:40.800
create a simple validator class that takes in our object to validate our hash and the constructor. We've defined a
00:07:47.280
validates presence of method that takes in some keys. This would be like validates presence of first name, last
00:07:52.960
name, first name, last name are the keys and accept our normal options that we would normally pass into Rails. So we
00:07:58.960
can just instantiate our presence validator. We do have to pass it those keys uh as the arguments to the
00:08:04.639
constructor. iterate over the keys, grab the value, and then call that validate
00:08:09.840
each method that we saw. And like I said, this is very, very naive. It's very, very simple. And because of that,
00:08:16.240
you might be very pleasantly surprised to know that this doesn't actually work.
00:08:22.000
If we call this little proof of concept where we want to say that we're validating the presence of presents, maybe it's a Christmas themed app or
00:08:28.400
something, then it goes boom. But it doesn't really have too many issues.
00:08:34.399
There are only four errors that we have to solve before this runs. Rather than
00:08:39.440
going through them one by one, I'm just going to show them all off. So, first off, we get undefined method errors. If
00:08:45.440
we think back to that presence validator source code that was doing record.sadd,
00:08:50.560
our record is our hash. It doesn't have an errors method. This makes sense. That's something we'll have to fix. If
00:08:55.680
we fix that up, we'll see that we've now left the presence validator behind. And when we're building up the errors
00:09:01.839
themselves, we see no method for read attribute for validation.
00:09:07.360
Similarly, undefined method model name and human attribute name. So currently, all of these methods are
00:09:14.320
being called on our hash, our record. And so we need what we're passing into the presence validator to define all of
00:09:20.800
these methods. And like I said before, we don't want to do monkey patching. We don't want to add read attribute for
00:09:26.240
validations to every single hash in Ruby. So to fix that up, we can create another
00:09:32.959
little class that I'm going to call the wrapper class. And now you're going to see the word new a lot in the next
00:09:38.880
couple of slides. It's going to be weird, but just bear with me. So we can define a new method on wrapper. You can
00:09:44.480
call to get a new anonymous class. And then you can call new on that anonymous
00:09:50.720
class to get an instance of that class. And within this anonymous class, we're going to define our constructor. It's
00:09:55.920
going to take in that hash. We're going to set up the errors with an adder reader going along with the instance variable so that we have that errors
00:10:02.880
method defined. And then we're also going to define read attribute for validation that second myth missing
00:10:08.480
method to just grab the value associated with the attribute that we're trying to validate. So this is pretty simple. We
00:10:15.440
do have those two other errors that are still in there and we can get those by just extending some built-in uh Rails
00:10:21.279
modules naming and translation which also means that we're kind of on the path to get some internationalization in place which is always great.
00:10:28.800
And so then now if we go to our validator and we uh set it up so that
00:10:36.399
instead of passing in the hash to the validate each method as that first
00:10:41.519
record argument, we now wrap the hash in our new instance of that anonymous class
00:10:47.600
that wrapper. Then we can go back run our tests again
00:10:54.399
and it passes. And so now we have successfully validated the presence of
00:11:00.240
presence on a hash. And it's very simple. We just pass the hash in to the constructor. We call validates presence
00:11:06.320
of and it works. And this is pretty cool. Honestly, this didn't take as long as I expected it to. But I'm not going
00:11:13.600
to call our proof of concept done quite yet. And that's because we're not supporting all of the options that we
00:11:20.160
would normally have access to in our presence validation. So, first off, if
00:11:25.519
we look at allow nil, this holds true for allow blank, but I don't really know why you would ever use allow blank on a
00:11:30.640
presence validator. Um, we can check to see if rank is nil. And we're saying we're going to allow a nil rank, we
00:11:38.959
still get the error. Rank can't be blank. And so this means that something is going wrong. Something about allow
00:11:44.800
nil isn't being taken into account. So, if we go back to our presence validator, we saw that it inherited from each
00:11:50.880
validator. And if we go to each validator, we actually see the code that handles these
00:11:56.000
options directly in each validator's validate method. Uh underneath this a little bit, it calls validate each. So
00:12:02.720
that tells us, okay, we're just bypassing the code we need. So if we go back to our validator, no longer iterate
00:12:08.880
over our keys, just pass them into the constructor as we were before, and now call the validate method, passing in
00:12:15.120
again our wrapped hash. It works. Pretty simple.
00:12:20.560
But but but but we have one more thing left before I'm going to call our proof of concept done. And that is supporting
00:12:27.680
if noneless. So here we're saying validates the presence of speaker if false. So it's basically saying never uh
00:12:35.440
never run this. But we still get the error speaker can't be blank. So now to
00:12:41.440
fix this again, we go back to our presence validator. This is kind of where we start whenever we're debugging
00:12:46.560
this kind of stuff. I mentioned there was a bunch of comments that I wasn't showing on that first slide. So if we go
00:12:52.000
back, we actually see in the comments, it tells us directly where to go. Go to class methods validates.
00:12:58.240
So if we pull up this method in the Rails source code, and this is the method that we're probably really familiar with from our active record
00:13:04.959
models, if we're ever doing a validates first name, presence true, it's that
00:13:10.480
validates method that we're looking at right here. And so within validates that then calls validates with which we might
00:13:16.959
know from running custom validators. Validates with then calls validate. Validate then calls set call back. And
00:13:23.200
this is pretty cool. This is where we've actually left active model behind entirely. And now we're in active
00:13:28.959
support callbacks because at the end of the day we do have a lot of validation logic in Rails but a lot of the
00:13:34.959
validation paradigm is basically just two callbacks in a trench code. And so if we look at callbacks, we see
00:13:43.120
we've got our options here if and unless. And now earlier we're just calling the validate the validator
00:13:49.360
directly. We're completely bypassing that paradigm I mentioned earlier of registration and then running. And so
00:13:55.440
this tells us that we need to hook into that callback code to get these options working. And so if we make a little
00:14:02.480
change to our validator where we now just delegate a bunch of things away to
00:14:07.680
our wrapper and our wrapper class. And now we're delegating those class methods that we're used to from our active
00:14:13.760
record models to our validation wrapper class, that anonymous class that's being returned by wrapper. And then we're
00:14:20.880
delegating the instance variables to the instance of that class. And then meanwhile on the wrapper side of things,
00:14:28.079
we have a very small little update that we make which is just include active model validations. This is great. We've
00:14:34.560
got access to all of our validator methods and logic that we're used to at this point. And so then now if we go
00:14:41.519
back and check a simple little if and unless then we get the behavior that we'd
00:14:46.880
expect. And now we're also having to change things a little bit where we're first registering that validation with
00:14:52.720
the validates presence of method and then calling it with valid question mark.
00:14:58.399
Now we're so so close and I've said this a million times but we are very close to having the proof of concept. There's one
00:15:05.199
thing left and that's dependent validations. So this is where if you would have validates the presence of
00:15:11.040
first name if last name and so if we do a simple little test here where we want
00:15:17.279
to validate the presence of bad jokes if Aaron which you know prevents probably a
00:15:22.480
pretty big bug because that wouldn't make sense. No bad jokes. Um then we can
00:15:27.519
run this code and it blows up undefined method Aaron. All the work the guy's
00:15:33.519
done and no method named after him. And so here this kind of makes sense because under the hood we're basically still
00:15:40.079
calling methods on our underlying hash object. And since the hash doesn't have
00:15:45.760
methods that correspond to its keys, we don't have that method to call when
00:15:50.959
we're using the if or the unless. And so in order to have those methods defined,
00:15:56.160
we need to basically convert our hash from key value pairs into method return value pairs. And we've got a lot of ways
00:16:03.279
that we can do this in Ruby. uh method missing a bunch of different delegator patterns. I'm just going to choose to
00:16:08.560
use simple delegator because I like it and I don't think it's used enough so I want to use it here. So here we change
00:16:15.440
our new anonymous wrapper classes to inherit from simple delegator in the
00:16:20.639
constructor we now wrap our hash in a data object. And so where we once had
00:16:26.160
key value pairs, now we have basically a value object whose methods are the keys
00:16:31.279
from our hash and whose return values are the values from our hash. And then we call super so that any methods that
00:16:37.279
aren't defined on our anonymous class just get forwarded along to our new data object.
00:16:43.680
So if we go back to our test, validate the presence of bad jokes. If Aaron, we get bad jokes can't be blank, which I
00:16:51.120
think is a much better error message than anything I've actually written in production code. Uh but yeah, and this
00:16:57.040
is this is great because I mean, if you think back to the applications that you maintain and you imagine the bug that
00:17:02.399
would be on the level of severity as an Aaron Patterson talk that didn't have bad jokes, you would know that we're
00:17:07.600
really probably saving a lot of people a lot of money right now. The small laughter is the best laughter.
00:17:16.799
All right, so now I'll say our proof of concept is finished, but we do have a couple of other things that we probably
00:17:22.959
want to support, namely all of our other validators. And this is super easy with our current setup because it's just a
00:17:29.280
bunch more delegate calls. We're just delegating all of these Rails methods to
00:17:34.320
our little anonymous class that has that active model validations module in it.
00:17:39.679
And the one thing that might be a little bit less familiar to everyone here is this clear validators bang method. This
00:17:45.760
does exist on all of your active record models. You could call this in your
00:17:50.799
application code so that suddenly in the middle of your app running none of your validations exist anymore. I don't think
00:17:58.559
that makes sense to do in production personally. But here where you have this
00:18:03.840
more isolated state, it might make a little bit more sense to say, "Oh, you know, I've set some things up, but now I
00:18:09.679
just want to clear them all out because some weird condition has been met."
00:18:14.799
The other thing that you will notice if you look at the tests for this, which I'll cover the tests a little bit later,
00:18:21.120
is that we do have a little bit of a bug. And that bug is that if our
00:18:26.240
underlying hash that we're validating changes or mutates after our validator
00:18:31.520
has already been instantiated, particularly after that wrapper has already been instantiated, suddenly the
00:18:36.720
wrapper just has access to the old state because that was that's what was used to instantiate that value object, that data
00:18:43.200
object. But meanwhile, the hash has changed. So it might say, "Hey, bad
00:18:48.240
jokes is blank when bad jokes isn't blank on the actual hash." And so we do
00:18:54.559
have to get rid of our simple delegator our IP. It did not last long. And we're just going to hold on to our object to
00:19:00.720
validate that hash again in our instance variable in our constructor. We can bring back our read attribute for
00:19:06.480
validation. That's just how it looked before. And now we'll handle that delegation through method missing. And
00:19:12.400
so if the method is missing, check to see if it's a key on the hash. If so, return the value. Otherwise, do what you
00:19:19.280
normally do. And the thing that's nice about the read attribute for validation pattern here is that because it's not
00:19:26.080
just relying on methods being defined on our wrapper, this also means that you
00:19:31.679
can actually validate things that match methods on this class, which isn't much.
00:19:37.360
But if you wanted to say validate the presence of the errors in your hash, you can do this now because it'll actually
00:19:43.520
be looking at the key value pair and not the errors method on the wrapper. This
00:19:48.640
also means that you could do something like validating the presence of the nil question mark key value pair, but I
00:19:53.679
don't know that there's many use cases for that. All right, and those are kind of the the
00:20:00.000
big the big things for the validator, but there are a bunch of other things that we could spruce up at some bells
00:20:06.880
and whistles. So, first off, if our underlying hash doesn't actually have
00:20:12.400
the key defined on it that we're trying to validate, it will currently blow up because if we were looking back at that
00:20:19.280
uh if object validate key else super, the super will just call no method
00:20:25.360
error. That might not be what we want to do if we're validating say an incoming API request. We're expecting it to have
00:20:32.000
some particular key value pairs defined on it. So maybe we would want to treat that as like a nil value rather than a
00:20:38.480
no method error. And if you follow the link, I'll put these all in the Slack
00:20:43.679
channel. Then you can see some code to handle this. Next up, all of our code so
00:20:48.720
far is just assuming we're only passing in a hash. If you wanted to pass in a model or some other class, maybe that
00:20:55.440
comes from a gem, then this commit will do that for you. Now, this one is a
00:21:02.000
little bit funny. So if we think back to our validator, we are exposing the
00:21:07.600
typical Rails class methods as well as the typical Rails instance methods all
00:21:12.960
as instance methods on our validator class. And in Rails, we have a validate
00:21:18.480
method on both the instance and class level. Validate on the instance level runs the validations. Validate on the
00:21:25.600
class level is what you would use to run a custom method validation on your
00:21:30.640
instances. And since we can't have multiple methods with the same name, I had to choose one to call validate. I
00:21:36.799
chose the instance level one. But you probably will want the behavior that validate the class level validate gives
00:21:43.120
you. And so basically just create a little alias for it. And then you're all good.
00:21:49.280
Custom validator classes are completely supported out of the box. We don't actually have to change anything about our current code to support your custom
00:21:56.400
validator classes. But I do have an example in the repo that shows how you could actually set up a custom validator
00:22:03.120
to run validations against arrays. So if you have an array of hashes and you
00:22:09.039
wanted to validate that all of them have particular key value pairs, for example, you can do that with the linked custom
00:22:16.080
validator. It's a little bit weird. It's a little hacky, but hopefully a decent proof of concept.
00:22:22.799
Now the confirmation validator is kind of a weird one. I did not know this until I got some failing tests, but the
00:22:29.760
confirmation validator actually does a little bit of meta programming with the thing that you're validating sometimes,
00:22:36.960
maybe sort of, and that can cause some breaking tests um if you're passing in a
00:22:44.480
hash. And so this is probably the hackiest thing that's going to be in this entire bit of code that I put
00:22:50.320
together to support this, but it's kind of hacking around something that feels
00:22:55.919
like a little bit more hacky than normal to begin with. And last, but certainly not least, I
00:23:02.720
really love should match. Shout out to ThoughtBot. I use these for testing all of my validations, all of my
00:23:07.919
associations. And so I wanted to see if I could make them work with this pattern. And again, had to do something
00:23:14.720
a little bit hacky, but did actually make it work and it worked pretty well as far as I could tell. Um, so I've got
00:23:20.960
a link to a little support file that I put together as well as the example specs that anyone can look at later on.
00:23:29.039
So this is the repo. Again, I'll put all these slides in Slack. And if you look at this repo, you'll see a bunch of
00:23:35.200
commits, a bunch of branches. Every commit basically solves one problem, one that we either talked about today or
00:23:40.960
added some kind of new feature like the ones that I didn't get to talk about too much uh just a minute ago. All of the
00:23:46.960
branches are basically different checkpoints um like the proof of concept for example. And that is basically it.
00:23:55.520
It's set up as a gem. It's not pushed anywhere. If anyone wants to push it anywhere, feel free to fork it and push
00:24:01.039
it up to Ruby gems. But otherwise, uh, as classic talks go, I did go a lot
00:24:07.919
faster talking than I did in prep. So, if anyone has any questions, uh, you can
00:24:14.400
feel free to shout them out now or come up to me after.
00:24:25.120
Uh, the question, thank you for the reminder. The question was, could I give an example of when a hash might change
00:24:31.440
after instantiating the validator? Is that right? Or after the wrapper. Yeah. So in
00:24:37.600
shoulda matchers when you first uh set up your your subject which would
00:24:42.960
normally be your model your like describe class new it first will test
00:24:49.120
with I think I don't know if it actually does the happy path or the unhappy path first but basically it will first take
00:24:56.880
the subject that you've set up in your RSpec test say I've set my bad jokes to
00:25:01.919
false and then if I'm validating the presence of bad jokes or maybe in this
00:25:07.679
case the absence of bad jokes. Then once it's validated, okay, this doesn't raise
00:25:12.799
an error when it's absent, then it actually goes and changes the underlying object. In your active record model, it
00:25:18.720
would be like model.bad jokes equals true. Um, so in that case, all of the
00:25:24.320
should matcher tests were failing until I swapped out the delegator pattern to
00:25:29.440
actually keep track of the underlying hash. And so I don't think in real
00:25:35.440
production application code you're probably likely to see that happen. But
00:25:40.559
it is good to know if you're in the habit of like duping something that you pass around and then you maybe have some
00:25:47.440
code that will modify it in place later and the validator only has access to the dupe and not the original then it might
00:25:53.520
get a little out of sync. Any other questions? Yes.
00:25:59.679
What was your use case? Yeah. Uh the use case for using this
00:26:05.120
validator um so we have an event sourced system at work and so we have a lot of
00:26:12.320
data that starts off as a hash and because we don't use active record or any kind of real OM we either just have
00:26:20.240
it as a hash or later on as like kind of like a custom value object similar to the Ruby data class. And so because of
00:26:27.919
that and because of the requirement that I had that we didn't want to be setting up a new class for every different type
00:26:34.559
of event in the system, I needed some way of just passing in an event, a hash
00:26:40.880
event into some object that could then run the validators against it. And so
00:26:46.320
since we didn't have a model where we could just throw in the typical Rails pattern of validates this, validates
00:26:51.840
that, had to figure out some new solution.
00:26:57.120
Yes. Um was were both the the readers and the writers essentially of the event
00:27:03.520
using this this pattern um before it was sent over the wire. So the way that it currently works is that the source of
00:27:10.880
the events can kind of change. We have a number of different events in the system. Uh the ones that are most
00:27:17.039
relevant to this particular pattern we call user sourced events. And this is like something that starts off as params
00:27:24.159
coming into a Rails controller for example and they just come in get put in
00:27:30.159
like a little hash similar to the params object and then when they go into kind of the beginning of our events sourced
00:27:37.360
pipeline where we stuck the validation logic eventually they get like written to a database and whatnot. Um when they
00:27:44.400
first come in as a hash that's where we validated them. We don't currently have
00:27:49.600
any validation happening after they've already been written. Um, so far I don't
00:27:55.440
think we've seen the need for that. It was mostly the case of with all of our
00:28:00.640
events happening, they were naturally ordered based on when they occurred. And so if we had some event that we later
00:28:07.919
found out was invalid or was broken, then fixing that past data could mean
00:28:15.120
that we would have some ramifications for the data that's entered the system since then. So it got kind of complex
00:28:20.480
and just saying, okay, before we actually persist this event in our event stream, we want to make sure it's valid,
00:28:27.200
otherwise we're going to discard it. discarding it then kind of triggers potentially down to like the UI raising
00:28:32.399
like a little toast that says, you know, you didn't enter in the first name when you needed to or something.
00:28:39.360
Um, are you asking where we store the the events? No, the validator
00:28:45.760
uh like the class that I showed. So when you do validate
00:28:52.880
Oh yes yes yes yes. So that uh the question is where are the validations
00:28:58.000
that we've registered kind of stored within the system. Is that right? Yeah. So that just leverages what Rails does
00:29:04.799
by default which we get from that include active model validations. And so under the hood um when you include
00:29:11.440
active model validations there's a class attribute called underscore validators
00:29:17.039
that get set up. And so when you do something like validates presence of it basically appends to that underscore
00:29:23.919
validators at the class level. And the way that the class attribute is set up
00:29:29.120
is that it can just be if you have like a series of of class an ancestry like
00:29:35.120
model inherits from other model inherits from other model. Then if I remember correctly the subclass has kind of its
00:29:42.480
own version of the class attribute that pulls in the parent. And so if you've ever had like a single table inheritance
00:29:50.880
active record model setup where you have a base class that has some validators defined and then you have a subclass
00:29:56.960
that adds more that subclass will have not only the ones defined on itself but the the superass as well. And so long
00:30:05.440
story short underscore validators uh it's a method that you can call on your active record models on the class level
00:30:11.760
if you ever want to see all the ones that have been set up. You can also look at uh I think it might just be the
00:30:17.679
callbacks method and that kind of shows you how all of the validators tie back
00:30:22.720
to just call backs that are set on your models as well.
00:30:28.080
Let's see. And I am out of time. So, thank you all again for coming. If anyone has any more questions, feel free
00:30:33.520
to flag me down.