 So it's great to be here with you all and enjoying all the positivity and all the love for Elixir. It's awesome. And so yeah, I wanted to share with you today something I've been doing lately at work that maybe you guys haven't thought of or encountered yet, which is using Ecto in the absence of a database, just using the schema aspect of it. So yeah, my name is Rosemary. And I work for a company called Rent Path. The headquarters is in Atlanta. I work remote. And we've been using Elixir for over a year, and we do have apps in production already. And we've been doing mostly microservices and tooling for our own devs and part of our deployment pipeline. And we're excited about Elixir, so I'm sure we have a rosy future there. And we are hiring Elixir devs, including potentially remote. So feel free to reach out. So yeah, Ecto schema without a database. So Ecto schema library. Oh, by the way, hands up here, who has never used Ecto? So good, most of you know about Ecto. So yeah, normally you would use both the schema and the repo aspect of it. But the schema part alone provides for you a way to talk about the model of your data, like how to cast data types and how to potentially transform and validate data. And you can import it and use it without having Ecto repo as a dependency or in any way having an actual database hooked up to your app. And I'm going to show you how you can do that and what to do with the results you get from importing some data into Ecto schema and talk about some of the problems I encountered when I've been using it in this way. And we'll see how it goes. So in order to demonstrate this, I've written an app. And it's on GitHub. And I'll give you the link. And anything you don't follow from this lecture, I'm sure you can figure out by looking at that. It's pretty straightforward. It's a really, really simple JSON API service. And all you can do with it is to create a pet object. And I'm demonstrating how you can use Ecto schema to help you with some of the things you need to do to be a JSON API standard service. So that's a sample request that you might hit the app with. So you're trying to create a pet. And there's some data that you're providing. And then I'm going to use Ecto schema to provide this error response that helps you realize that you've got some problems. Maybe you're missing something. Or there's an error in your data. It's the wrong type. Or it doesn't meet some condition. And that would be a pain to implement by hand. But I'm going to show you how to get it pretty much for free. So when you're trying to describe some data without Ecto repo, you wouldn't use the normal syntax. This may look familiar to you if you've used Ecto before. This is just how to describe a table that resides in your database. And the table's name is pets. And those are some of the fields. But if you don't have a real database, the type you would use, the description, is called embedded schema. This is also something that you might use if you had data in your database that had a deeper structure that you don't want to call out with fields, like a JSON type or something. So when you use this, you don't give it a name because there's no table associated with it. So as you start to talk about more complex data structures, you can nest those embeds. So you can use embeds many or embeds one to talk about how you've got more deep structure there. And this example, I'm doing it in line. So you can see I've created another schema called owner. And I'm describing what it consists of right there in the same block. Another way you can accomplish the identical result is by having a separate module. So this will be if you had your own little owner module. And then you would go ahead and reference it in your nested embed. So if you do the inline style that I had on the left there, this would be how you would cast into it. You would go ahead and provide as an argument the function that's going to process that change set. So it's going to take in the params and do whatever casting and stuff you need to do. If you do the other strategy where you're importing a module, that module needs to have a change set function. And that will get called for you. So you don't have to do that. But you do have to do this special type of casting called cast embed. And then you can say things like whether it's required and stuff at that point. Another interesting thing you might want to do is implement a custom type. That's something I did in my real life. So an example I had was that there was some data that was coming from a legacy system that was sort of encoded as an integer, but it wasn't really meant to be an integer. So it's just kind of awkward. So you can bridge that gap by describing your own type, which is a behavior. So if you want to do that, you just need to implement a cast function. And there's a couple of other functions, load and dump. And what you need to return from the cast is either an error atom if there's a problem, or you could return the actual data that you have successfully cast. So that's your opportunity to do whatever you need to do. In this particular example, I'm looking up on a table and seeing what does that integer mean to me or whatever. But whatever you need to do, you can put whatever logic you need in there. So it's pretty powerful. Another thing that you can leverage is the full power of Ecto's validations, which is significant. There's a lot of just pre-existing ones that you probably already know plenty about. And you can also, relevant to me in this app I wrote, you can customize the error messages. And that will be useful if you're a JSON API, you know you care about the wording of the error messages. Because maybe the default Ecto ones aren't that great. Like if you're missing a required one, it says it can't be blank, which is kind of a weird wording for a JSON API. It makes more sense, maybe, on a web form, but not really in this use. But it's OK because you can just pass a string here whatever message you actually would like to see. And you can also use getText if you want them. And I haven't played with that. But if needed to be in other languages or needed to be smart about pluralization, you can leverage the built-in power that's already there to do that. And notice there that in that last line, that's a custom type that I described in the last slide. And I'm providing a message, a custom error message for that. Actually, yeah, that's true. So the validation of a custom type, you can supply an error message for like, in there I'm doing inclusion. So there I can give it my message. You can also write your own validators, which covers any gaps that you may have in terms of what you need to do to check your data. So here I wrote a kind of a silly validator that's checking to see if the integer for your license is divisible by 3. So it's just something arbitrary. Because I was looking for what isn't covered. And so much was covered that I was like, well, just write something kind of silly. And notice there that I'm also providing my own error message, which is really nice. So OK, after you've done your casting and your validation, there's two possibilities. Either you have a perfectly valid change set, in which case you would just do applied changes. And the action on that change set in this use case doesn't matter. Like it could be insert or it could even be nil. Because we're not really going to do anything to a real database. So we're just going to pull the data out. So to do that, you just do applied changes. Then you've got your already cast and altered and validated data. And the other fork in the road is if there's a problem. So now what you're going to want to access is the errors part of the change set. So in a simple scenario, it's pretty easy to access the errors and use them for something. But this can get a little more complicated. Because as I was showing you, you might have these nested embeds. So that makes the error object arbitrarily complex. So that's a little awkward. So I had to do some little recursive logic to be able to present that error message the way I wanted to. And this JSON API. So this is maybe some useful code for how to do that. We're just kind of navigating into the error message and pulling it out into a JSON API compatible format. And there we have the opportunity to tweak the wording a little bit too. So some of the limitations that I encountered while working in this way is that while you can provide strings for messaging in most cases, one area that would have been nice but there is no provision to do it is when you're casting a custom type. There's like a closed GitHub issue for this. They said, you can implement your own thing later. Like when you already have the errors change set, you can go in there and change the message or something. But it would have been cool to be able to just provide it. When you're already writing your custom type, you know how you want it to behave. But there is currently no way to do that. Another thing that was a little awkward limitation was when working with a microservice, I wasn't always able to decide exactly how I wanted the input parameters to be versus the output. And maybe the front-end people want camel case or whatever, but I don't want to do camel case on what I'm doing with the data. And I looked around and I couldn't find any simple out-of-the-box solution to that, which was kind of surprising. You'd think that would come up a lot for remapping parameters. And one of the things I came across when looking into that is there is this thing called field source mapping in Ecto. But that's like a red herring because what that actually is is when you want there to be a disparity between the naming you're using in your schema and the naming of the physical actual database. So it's like a different part of the workflow where you can do some renaming. So there's no renaming before the insert, right? Another problem I ran into was primary keys. Like if you've got a bunch of data that you're inputting, you're grabbing, you want to check things with it, one of the things you might want to check is uniqueness. Like if there's a field and a bunch of records that should be unique across all the records, you're like, well, yeah, I should be able to just validate unique in some way. But it turns out no because the way that primary keys work is they rely on the physical database. At the moment when you're considering inserting a record, Ecto repo is checking like, hey, is there already a record? In the metadata of this database saying like, this is a primary key and blah, blah, blah, blah. And you don't get that if there's no database. So unfortunately, primary keys are pretty much irrelevant in this workflow and you're on your own for uniqueness. So what I ended up doing is writing my own custom validation that took in all of the rows and made sure that what I cared about was okay. And if it wasn't, I went ahead and I inserted an error into the change set. That's something I'm not really showing here is that part of what's so great about change sets is you can do a lot with them. You can manually add errors. You can insert data in stages. Like it doesn't have to be this kind of simple, several step process that I put in my app. It's a very sophisticated library that has a lot of options that this guy's the limit to what to do with it. So yeah, think about what you could do with Ectoschema. It's able to support you as you express really complicated ideas about data types and data structures and it kind of provides a very intuitive way to organize these ideas. If you've got an email address or a snail mail address or any other thing, you can express that idea once and then you can just reuse it as needed. Like you don't really have to think too hard about how to structure that. It's very natural to use this library to explain things that way. Same with validations. I wrote a custom email validator and we're probably gonna open source that because why would you want to rewrite that? Like if you've already written it, right? It's a very kind of everyday kind of thing. And like in your app, what kind of data might you want to check against validators? For what different kinds of apps? So some of the things that I've encountered and used are to format something to insert into RabbitMQ because RabbitMQ doesn't really care about the structure of the data in there but you may care about standardizing it and enforcing some kind of particular structure and I showed you a little bit of some JSON request responses and another case where you don't really have this enforcement of structure is Redis and also maybe just calls to other APIs. You might want to enforce a standard using this tooling. And that's the URL for the little app that I wrote to demonstrate how this all works. And yeah, I'm done. So I guess there's a little bit of time for a question if anybody wants because I'm like a little bit under, right?