 Adam Cuppey, I am a zealot, let it be known. I am a part of a company called Zeal. We're actually located in Southern Oregon, but we're actually, come January, we're actually opening up an office down here in San Diego, Southern California area, so if any of you Rubyists are in the area, please talk to me. So you can find us on our website, codingzeal.com, and of course my Twitter handle is Adam Cuppey as well, please follow. Just heckle from there, like not here, heckle from that point, you know what I'm saying. So I don't encourage you entirely to go get this now, but all of the content, because there's actually a lot of code that I'm gonna be presenting today, is going to be located at this URL on this GitHub. You can get it now. There are some modifications, they are intentional, so just be kind of smart, intelligent people and kind of acknowledge that there's a little bit of a difference. I subscribe to the Sandy Metz mentality of really small classes, so in this talk I'm gonna be doing things that are more consolidated in one, a much larger class, but I break it apart into its more subsequent parts in this repo, so again, don't maim me. If you didn't hear me the first time, follow me on Twitter, so please by all means heckle me from there, it's perfectly acceptable. And so this talk, so what I'm gonna be going over, again, this is the system architecture track, so this talk is on rapidly mapping APIs. Now in the talk description, I mentioned JSON and XML, XML is kind of like Latin, so I'm gonna be really focusing on JSON, but at the same time, the reason why I put it in there is that kind of one of the core fundamental ideas is that you should be able to adapt any data structure into the appropriate Ruby objects and start working with them, so again, I'm gonna be focusing a lot on JSON in this talk, but just know that XML and YAML, at the end of the talk, I'm gonna give some great examples of some tools you can utilize to interchange that schema into whatever you want. Oh, I forgot something, oh I totally forgot something, this is really important, I need to do a presenter selfie. I need you guys to scoot in over here, do it, scoot in, okay, ready? Oh wait, I need to do this, okay. Presenter selfie, come on, give me some energy, woo! Okay, that's all I needed, thank you, okay, moving on. Okay, so a couple of assumptions, didn't wanna miss that, seriously, a couple of assumptions that I'm going to be making about your skill level as a Ruby developer is that you're gonna be familiar with a good amount of the standard lib, and more specifically like decorator, simple decorator, the decorator and forwardable. I'm not gonna be hitting on them too heavily, but just know that that is kind of a major construct. And similarly, the decorator pattern, so if you're not familiar with the decorator programming pattern, I recommend you look it up online, it's a pretty popular pattern where you're delegating or you're assigning additional attributes onto an object kind of at runtime. So take a look at that, I'm gonna hit on that today, and if it goes over your head, Wikipedia has answers, maybe wrong ones, but it does have answers. So those are some assumptions. Now, to start this off, I'm gonna first talk about the three Cs, and one of them is specifically when we're talking about mapping an API is that we have to build adapters. So the first step is that our goal needs to be that we're gonna consume the data set of some kind. So if you're familiar with a standard, like we're dealing with Postgres or something along those lines and some sort of database or relational database system, you'd have something like ActiveRecord, which is going to hit the endpoint, it's gonna pull it all in, it's gonna build it into objects, and then you interact with those objects. We rarely ever parse the actual return coming back, which would be the equivalent of a terminal from any of these databases. So similar sort of things. So in this talk, we're gonna be going through the process of building out our own very small adapter to be able to connect to the resource itself. But the goal of that is to consume it. So the first thing we're gonna talk about is the connection itself, right? So this, the way to think about the connection is that it is just simply the relationship manager. So its intent is to, it's kind of like a lock and key, right? So it knows we're gonna have a real strong separation between our resource and then our application. And the furthest point to the resource is gonna be our connection. And again, this is our relationship manager. And what it holds is it holds the translation table, essentially, right? So the rest of our application doesn't need to know a single thing about how it gets the data, where it's getting it from, or any of that. If it's using URLs or internal connections, it is totally separated and it's totally isolated. This is a really important concept. So in the case of our application, we're gonna start with this very simple diagram, is we have a resource down below, and at the top we have our connection, maybe just connection RB or something along those lines, but we have a connection module. And its simple function is to handle the communication between the two. And in our case, we're gonna talk about HTTP and specifically get and post requests, okay? But again, this connection should be, hold that translation table. It should know all of those various components, how it connects, what it does when it connects, and all of that. The rest of your application should have no concern or awareness towards it, okay? This is a really kind of core component to it. So I'm gonna start with a use case here. And the use case is the Google Civic API, okay? So here's a very simple endpoint, okay? So in our endpoint, you'll notice that the first, the opening component is that we have just, you know, we have a scheme of some kind, right? So our protocol here is gonna be secure connection, right? HTTPS. We're gonna hit the Google APIs domain and specifically nested amongst that, we're gonna hit the Civic Info, right? Very simple, very basic. Now I also wanna point out that this is very common with APIs. When our consultancy builds out an API, we very much encourage the versioning that API for kind of obvious reasons. Again, with our module with our connection, our connection needs to be aware of that so that it can route to the appropriate places, right? It can handle that translation table appropriately, right? So I just wanna point out here that we're currently working with version one, all right? So building out the connection, we're just gonna start with a very, very simple connection class, right? So in this case, I would recommend, you're gonna notice right there, we're including HTT Party, okay? And this is a very simple library that will handle the basics of the HTTP connection. Very, very simple. You can use anything you like really. There's many fair days, very, very popular. I've used that a lot. A little bit more configurations required for it. That's the only reason why I'm not using it here. But in this situation, we're gonna have a basic connection class. We're gonna mix in this library. Now the other thing we're gonna do is, remember what I was talking about is, our connection object, its primary scope of awareness is towards those first two components, right? It's gonna concern itself with the version that it's on, and it's gonna concern itself with the route, right? Where am I gonna get the data and what version am I gonna be working with, right? The rest of our application reality doesn't care, right? It's like, I'm gonna call on this information, I want you to give it to me and I now wanna work with it. It's really that simple, right? So we're gonna isolate all of that into our connection object. Now, just for the sake of HTTP Party, you're gonna see that a base URL, we're gonna be passing that in. This is kind of a practice that I very much encourage, and that is that you utilize constants to set default values that you want, and specifically, and this is generally the case anyway, but you load those towards the top of the file. Part of that is, again, we're talking developer happiness here with Ruby and the ability for another engineer to jump in and jump on the project. So with that in mind, this gives a really strong sense of if I do nothing, then this is what I'll get, okay? If I modify nothing. Now, in our initializer, we're gonna set this API version into an instance variable. Now, this is gonna provide a couple of different things which are gonna be really helpful. One of them is we, at runtime, we can redefine this if we want to. So if we're talking about a lock and key, think of our connection sitting over here as the lock and key. That lock and key is gonna contain that routing. So let's say, for example, you have one connection that manages users, you have another connection that manages companies, and at some point, you're gonna consolidate that information down. Well, you, in theory, could have two different connection objects that are sitting on even different versions entirely that are pulling from different resources, and as a result, but at the end of the day, when we look at our application, our application doesn't know that and frankly doesn't care, okay? So it can be collecting all that data and working with it in one single container. Now, the only other thing I wanted to point out on this endpoint is that for this endpoint specifically, the authentication key or the API key is being done as a query param. That's not always the case. Many APIs will utilize headers. In either situation, the, these default values, specifically if it's headers, or if it's cookies or anything along those lines, they would also stay inside the connection manager as well. So those values would be set on that object and it would be handled that way, okay? So extending out our connection class a little bit more, you're gonna see that we've added in a default query and again, I'm a big fan of just default values to set expectation. So we're gonna say it's just an empty hash of parameters. We're gonna add in another instance variable into our initializer for query and similar to the API version, all we're gonna do is we're gonna say, okay, I want, if there is a query being passed into the initializer, use it, if not, use my defaults. That's the case with both. And then we're gonna add just a, it's not a full setter, but a method for query where we can bind in additional attributes into that query, okay? So it's going to persist our API key as an example or other default values that we may pass in and then we can just bind into that and they will persist through the entire connection, okay? So our implementation of this, our initialization and implementation will look something like this, right? So we have a connection object that we're gonna instantiate. We're gonna pass in an API version of two. Coincidentally, our API version is defaulted into two anyway, so it wouldn't really matter, but just for the sake of example. But again, because we have defaults, we could exclude that entirely and that's okay. It's gonna default. Then we've got a query that we're going to set in where we're gonna pass in our API key, right? And again, our connection object now, it's going to have all those values that are gonna persist and it's gonna be able to handle those through entirety, right? Now let's talk about the next component to this, right? So we have our connection that sits out here that's gonna call on our resource. It's gonna pull information via an HTTP request, but now we have to have some sort of routing and translation table between our application and the data that's retrieved, right? So the client object is often mistaken as the connection object where they are generally combined. It's my feeling that that's a bad practice because the lock and key is a really important concept. So as an example, or actually I'll come to that here in just a second. So our client object should represent the, as Gary Bernhard puts it, the imperative shell, right? So what it has is it contains all of the logic, effectively a controller, that knows how to take the request, make the request of the connection, retrieve the results, parse those results and hand it back to the application, right? It's the intermediary, right? So if you're familiar with Rails, you're talking about models and you're talking about views, right, and your controller sits in the middle. The client does the same, okay? So routing and collection is its primary function. If we just have a really simple diagram, it's something along those lines, right? So its function, again, is to stand between the application and the connection. So our application is gonna make a request to the client, client's gonna reference out to the connection and it's gonna come, the communication's gonna come back. So to bind these together, we're gonna set up a basic client object, right? So we've got two that are gonna be passed in. The first is the connection itself and the second is a routing table, okay? So we're just gonna have two instance variables that are sitting in there, right? Now, here's when we're talking about a routing table, this is what I'm talking about is we're talking about the scoping of the endpoint, right? Again, this may not be in the URL if we're talking about like HTTP bindings and so forth. This may be something that's a parameter that's passed through a form, just an additional value of some kind, but the net result is that we have a route here that is going to combine the two. Now, our connection doesn't care about what you wanna do with the data, it doesn't care at all. Our client doesn't necessarily care what you wanna do with the data either, but it understands that you want this data and I know how to get it for you, okay? That's the relationship. So if we were to take this and we were just to represent it here as just a straight hash, it may look something like this, right? So this is kind of the basics of our routing table, per se, is we have an elections key. This is the route that we're gonna define and we've got the HTTP method on that as a get request and then the path more specifically is elections, right? So this is our routing table. So if we were to put all of this together, it would look something like this, right? So we've established our connection, we've defined a bunch of routes and then we're going to pass those into our client. Now we've endowed our client with the ability to understand the correlation between our application and the endpoint, right? Now all we have to do is we have to tell our client what we want and how we want that information returned back to us, okay? So this is all the very basics of it, but you can expand this concept kind of to fit the needs, but again, I just wanna point out that there is clear separation between the two components. So we may expect something like this of our client, right? We would like to be able to say, okay, of the client, I wanna call the method elections and I want it to return back, something, right? Exactly that, right? So how do we get there, right? Now the simple answer would be to just say, okay, well, we're gonna define an elections object on it, but the problem is we don't run into a lot of flexibility, that's kind of the challenge, right? So a quick and rapid way of getting there is that we can take a look again at our route, we've got this key in there that's elections, we're gonna keep it dry, right? That's in there, and we've got our routing map. So what we could do is we could start by doing something like this. So we're gonna define method missing where the method is going to be the key, right? Elections, for example, okay? Now our routing map, which was passed in, which was passed in during initialization, and route map, you'll see there is the routes, and we're just gonna fetch that method, elections. Now we have a route map, and from that map we're gonna extract out the HTTP method in the path, and again we're gonna call on our connection object, and we're gonna pass those in. So again, so what's gonna happen is it would be the equivalent of saying connection.get, let's say we had a post, we could do connection.post, and then our relative path. Now because our connection object contains all the base properties, the domain and the version, none of that has to be passed in, okay? So again when we're calling on the client from the context of our application, it doesn't ever need to know any of that stuff. It doesn't need to know that we're on version two or version three. Now when you're talking about very large applications, so when we'll work on really large apps that have 1,000 classes in them and so forth, the amount of intertwining that you can find yourself into is substantial, right? Especially when it comes to versioning. Anytime you change a version, you're like, oh, how many times I gotta do find and replace? If you have to go and you have to touch find and replace and parse through 30 files, you got a problem. That's a smell that there's an issue, right? You should be able to isolate this thing down to one or two instances really, no more than four or five different instances that you have to adjust to make this all of a sudden work. So again when we're talking about versioning, which can be a real serious problem, we can handle that in a really beautiful way by separating the concern and isolating it down so that our client doesn't care, okay? Now from our, now that we've run this method, so again we're gonna run this guy, right? Client elections. Now when we get back because it's gonna hit our end point is we're gonna get a JSON payload, okay? Now it could be an XML payload or whatever, but in our example it's a JSON payload. An HTTP party, luckily, is gonna actually parse this immediately into a Ruby hash, the primitive, and this is actually what it will come back as, right? So we have some simple attributes here and then we have another collection that's in there called elections, and that collection is an array with more objects in it. In this case it only has one, but it could come back with 30, doesn't really matter, okay? And we've got three different attributes that are in there, an ID, a name, and an election day, okay? So we've got our data back, it's in a Ruby construct, it's in a hash, now we can start to work with it, right? Now the problem is this, that inner application by living in the world of a hash, and this is the case with most of the apps that I've seen, is that we'll keep it in the form of a hash, and that's how we'll interact with this payload, right? In a Ruby hash, right? Well here's the problem, right? The problem is that you lose a lot of support for dynamically adjusting for that data, right? So as an example, if I'm using brackets per se to access my hash, what does Ruby return if I use brackets and there is no key for those brackets? What's a return? Nill, right? There's few things I hate more than nil, and Kesha is only one of them, okay? I hate nil, nil is horrible, you'll see that I use fetch all over the place because I hate nil, nil sucks, okay? Well, so the goal should be, I wanna get it out of that construct, right? I wanna pull it into something that I can use, that I can add additional methods to, maybe some validations, you name it, right? But I can't easily do that. If I think that I'm gonna, if else my way through this whole thing, ain't gonna happen, okay? So some of the things that we could do, and I've seen this way too many times, is monkey patch hash, don't do that, please, please don't do that, right? Monkey patch hash, that's one. Or I've also seen decorating a hash, right? Where they take the hash, they throw it in, they decorate it, they wrap it, and they give it more functionality, which is okay, and I'm gonna do something similar to that, but there are better ways, people, okay? So the next of the Cs is to coerce, right? So we're gonna coerce this into another format that our application understands, right? So again, when we're talking about the separation of concerns here, right? We have a resource over here, which is just the raw dataset. We have our connection layer, which understands, it has awareness and understanding of that resource. We have a client that knows nothing about the resource, but it understands the connection and how to interact with the connection. Now we have our application that knows about the client and understands that it can make requests of the client. I want this information, please return it back to me, but the expectation should be that I want the same type of data, and so I have a phrase that I'd like to use and it's instance-oriented architecture. IOA, isn't that great? Thank you, trademarked it, people. Okay, so instance-oriented architecture, which it gets it out of primitives. Yes, Ruby deals, everything's a class, right? But it pulls it out of these primitive constructs like a hash and it moves it into more of like the straight dot, straight class instance architecture, right, so that we can make requests on it, we can add more methods to it, and we can decorate the object itself in ways that are more fitting to our application, okay? So I'm gonna introduce this idea and that is that you have a representation, right, and a representation is an entity mapping, okay? So again, we have this hash, we have this hash, right? So we have our application, oh, sorry, we have our application. There are a collection of representations that represent hash instances, and those hash instances, and the representations can communicate with the client as well, right? So if we're talking about our JavaScript, oh, not JavaScript, not JavaScript. Ooh, if we're talking about our JSON, excuse me, right, we would hope to be able to do something like this, that our representation can interchange between a Ruby instance and its JSON counterpart, right? Can anyone name the part of standard lib that has this fairly interchangeably? Open struct, right? So it's a very, very simple library, right, for the most part, but it gives this kind of baseline functionality, right? Now, you can use open struct, and it's not a bad one, it actually does really, really well. It tends to be a little slower than just working with a hash, and that tends to be a big criticism of it. However, if we're talking about our application architecture and specifically Ruby 3.0 that's coming out, right? All these performance issues are gonna go to the wayside, that's great, right? Okay, so, but I'm a big fan, one of the things I love about Ruby is just the fact that it's so easy to work with, right? I love the fact that it's not Java, that it's not C, right? All of these other languages have a ton of benefits, but one of the great benefits that Ruby has is the interchangeability between data types and structures. So I'm a big fan of getting it into the instance structure as quickly as possible, getting it out of its primitives for the most part, and getting into something that I can work with. Now, in addition to that, our application, the separation here is that our application should not have to concern itself with what the data looks like at the resource level. Again, we have the separation of these concerns. So our application should not have to go, oh yeah, that's right, I remember. The data structure changed totally. I gotta fix that, I'm gonna if else my way to solutions, okay? Should never have to do that, right? I should be able to call the same method on the object and get a result and get a common expectation. So we're gonna set up our representation. Right now, all we're gonna do just for naming, we're gonna say we're gonna require OpenStruct, we're gonna create a representation that simply inherits from OpenStruct, okay? Now our next chunk here, and I'll come back to the representation here in a second, but our next chunk here is that our goal needs to be to consolidate this data, right? So going back to our payload, our payload is gonna come back with all of this data. It's gonna come back with attributes and collections, and we need to be able to consolidate that data and we need to course that data into formats that our application can understand and work with, and at the same time as we wanna be able to consolidate that data into a data set that we can work with more easily and that our application expects. This is a part of good architecture, is the separation of these big, large components, right? So as your apps get bigger and bigger, or you need to be able to split these apart, the separate to segment these components even greater is not that big of a deal anymore because they're so separated already, right? So separation of concerns is really great at this point. So going back to the representation class, we're gonna introduce a few items here. So you'll notice that in our initializer, we're gonna pass two arguments in. One of them is going to be the representation and it's gonna default to just a blank hash, and we're also gonna pass an apparent and the default is nil. In other words, it could be the grandfather of all things, right? Now we're gonna run super on it, we're gonna pass in a representation. This is obviously gonna defer itself to open struct, which only takes one argument. So it's gonna build out the open struct representation, and then down below we've got four different declarations in here. Two of them are we're gonna define the attributes of the parent and the attributes of the child, which of course, we're gonna assign the parent to parent and for children, we're just gonna say that it's a blank array, okay? Pretty simple. Then we've got two methods, it's very large ice. We've got two methods in here. One of them is that we're gonna bind the parent, which I could show you the implementation, you could see it online, but it's very basic. Just essentially, it's going to check if the parent exists and if so, bind it in. And more specifically, make this representation the child of the parent. That's very basic, but again, you can see it, you can see it through that repo I showed you. Now here's the real hook in here is the method represent children and we're gonna pass in our hash representation, okay? Now this is where the rapidly mapping part, this is it. This is it, people, right now, right here, right now. Rapidly mapping, this is why it's rapid-ish, okay? So looking at these, a few of these methods, okay? The core of these methods are two different components. One of them is the represent method, the other one is the definition class for type method. Now walking through these separately and explaining them a little bit, the first thing we have, let's look at represent, and represent is simply going to take a type. In our case, it will be elections, okay? It's going to take a collection and it's going to take a parent for a later assignment. Now we're going to coerce that collection in the event that it's a single entity and that first line there into an array. So if you pass in a single entity, it'll just be an array of one. Then we're gonna map through the collection and for each item, we're gonna determine the definition, right? So more specifically, this is the kind of hook that we've got going right now, okay? So we're gonna pull a definition class and how we determine that is in our definition class for type method, which basically is going to convert the key to a string, a class string. So elections lowercase to election singular with an uppercase, okay? And we're going to look, our application is going to look for that entity type, okay? If it's initialized. Now if it's not initialized, or not, excuse me, not initialized, but defined, if it's not defined, then it's gonna throw a name error which we will catch and we are then going to create a new entity under that name type that is going to be representation, okay? So you see that down there, it says object const set, definition class, which would be election. We're gonna say we want it to model representation, okay? So now what we have is we have structures in place that are gonna map our hash, our JSON models, which are now hashes, and it's going to create entities that map to those. And it's all gonna do this very, very rapidly, okay? Now, I wanna point out a couple of things that when I wrote this, I was like, I'm gonna shoot myself a little bit, and that is one of the dangers in constant get and constant set is that you may overwrite things, okay? Or not overwrite things, excuse me, that you may define a class in a namespace that you don't want, that's okay. Well, it's not okay, but a way to get around that is in the definition class declaration there, you could specify a namescape, like a scope, or a subset, like you could say representations and then a class of representations is election or whatever it happens to be, okay? So again, it's going to create the class if it doesn't exist. So now what we can do, and this is really cool, this is where our separation is really strong, is now we can declare an election class that inherits from representation. We can do that now. And once we've done that, when we call representation.new and we pass in our hash, okay? It's gonna convert this, okay? Our raw hash to that, right? So now what we have is a representation. We have an attribute of kind. We have an elections collection, which also has collections inside of it that are elections that also have attributes of ID, name, and election day, right? So all of that's happening in that code that I wrote, okay? So it's a very, very rapid thing. And again, what we have the ability to do is we have the ability to define these classes and give them more functionality, right? So we're just simply mapping these together. So when we're talking about mutability, when we're talking about the ability to mutate over time, right, and the support of that, because again, our API may change. So in this use case, we're talking about the Google Civic API, for example. Well, I don't have control over that. And last time I checked, I can't really just get Google on the phone and say, hey, can you change that back, right? So when we're dealing with a lot of third party APIs and things that are outside of our control our code needs to be able to support this mutation and it needs to be able to do it in a really rapid manner. If you've ever dealt with a production application where you've had a bug show up, like it becomes like, I gotta solve this, right? Well, if you have to go through 30 files to make these adjustments to change it, it may literally equate to time and money, okay? And that may not be good. But by simply supporting this mutability throughout the application, we can very, very quickly deal with these changes. So as an example, let's say our data set for election changes slightly and now it adds in locations, okay? We have a new attribute called locations. Well, in our representation, we simply declare it. Now we can add address. And this will work in the reverse. So let's say for example, our application starts with an attribute called address, okay? And it's just a concatenation of these different values. But then the API changes, the payload changes. We no longer have address. Our application squawks and goes nuts and we now have to deal with that well. That now can be quickly addressed inside the representation. Nothing else has to change. It's gonna handle it, okay? So this is the rapid part. We like rapid, rapid be good, okay? Now here's another really cool thing, and I love this, is that now that we have classes and we have objects we can interact with, we can mix things into them, right? So what about something like that, right? We have an object now. So now with our, instead of dealing with a hash, where we've got a bunch of brackets and we're like, okay, now, okay, if election day is greater than tomorrow than this or okay, well now I need to build a JSON schema validator to make sure that the payload comes back right and it parses in the right things. Yeah, that's someone else's problem, right? Now what we can do is we can mix in the validations or any other libraries if we want and we can deal with that here, okay? And our objects are all the same. Now this was kind of the chunk that, regarding the JSON or the XML or YAML or whatever, I mean there's gonna be something else maybe, we'll find out, right? And so to speak to that, and it looks like I'm running quite early, which is very good for me, I guess. But with that, I wanna introduce a gem that I have found very, very helpful. I could go through the details of this, but it would take a lot of time. Representable is a really fantastic gem. Big fan of this one. There's a few others that are like it. One of the things that Representable does is it allows for, and you can read from the description, is it allows you to decorate representor modules, okay? To render to and from JSON, XML, or YAML, and you can change that parsing strategy however you like. So it's very interchangeable. So as an example, you could mix in, you could take Representable, you could mix it into our election model, and now we can, now that model is going to handle the various properties and benefits of Representable, okay, and those various representations, and the XML or whatever, right? So that really heavily addresses our flexibility. So, I wanna talk about just a couple, briefly a couple of points that if you have to make changes today, right? Things that you can do to really save yourself, save yourself a ton and catch a lot of errors, right? So what I wanna talk about is just very briefly, catch and release, right? So, be able to catch changes and release patches very, very quickly, okay? So again, just very briefly, here's our data set, our payload that comes back, and again, we've got the location model right there, okay? And you'll notice that locations, and specifically the address, only exists on one of the two records, okay? So that when we run something like this, okay, I want elections, the first one in the array, I want elections and address, it will come back with a value, but when we try and run it on the second one, I don't know if any of you have ever experienced this, you'll get this really happy thing, right? Raise your hand if you've ever seen this beautiful, beautiful, yes, yes, my people, yes, right? It's the biggest pain in the world, and the reason, as we know, for those of you who don't know, is because that is an issue, it does not exist, right? So, this is fairly basic, I highly recommend you look at it, but the very simple adjustment you can make, right? And I recommend everybody do it, and there's no functional change, is that, right? The reason is because now when you run Fetch on this, it's gonna do the same thing, it's gonna ask for that key, but the difference is going to be, you will get that back. So we'll tell you that there is a change, effectively a change in your data structure. And instead of the error presenting itself down the pike, down the pike, where you don't have the ability to catch those things, or who knows? I mean, if you've ever dealt with that last, the brackets method doesn't exist for nil, it's way down in the app, and it takes a lot of debugging. Well, this is now gonna pop up right when it's asked on. Now here's some of the things that people don't really commonly use, but Fetch can take arguments, right? So it was an example, let's say we're, again, we're looking at location, right? But what we're really wanting, is we're wanting address, right? Well, again, when it runs the second one, location doesn't exist. So address won't exist, right? So we have to, now again, we're gonna like, if else are way down to that, right? No good. So what we can do, so we're gonna do something like that, right? So now we can tell Fetch, I want you to now, when locations is called, if it doesn't exist, I want you to return back a new hash, and the hash is gonna have an address in it that says not determined. So now when I call Fetch address, right? From before, it's gonna get this value back. Now this is not a super long-term solution, okay? What this is, is catch and release. I'm gonna catch an error quickly, I'm gonna make a quick patch, get it into production so that I can solve my problem, then I can go back, I can work through my representations, I can make my modifications, and get those out the door. Okay, does that make sense? So, that ladies and germs is the end of this bad boy. I wanna thank you guys very much again, feel free to go back. I've got all of this plus the slide notes, I'll put that slide back. There's probably a faster way to get there. Okay, there it is, okay. So feel free to go here, all of the code I wrote plus some, that gives a lot more detail is there. I really appreciate it, again, my name's Adam from Zeal, love to stay in touch with you. If you see anybody that guys in, or people in red shirts, come up, have a good time, let's chat, it'd be great. I really do appreciate it. Thank you guys, have a really awesome, really cool.