Model Initialization
A shared queries variable is created using the SQLC-generated query struct. This instance is reused across all models to interact with the database efficiently.
Article Domain Model
The Article struct is updated to mirror the database schema, including fields such as UID, timestamps, title, excerpt, and content. Although currently aligned one-to-one with the database, the model is designed for future extensions (e.g., tags) to better fit the application domain.
Database-to-Domain Mapping
Custom mapping is implemented to convert SQLC-generated database types (e.g., pgx types) into standard library types. This avoids tight coupling to external libraries and reduces the risk of breaking changes from dependency updates.
Fetching a Single Article
The findArticle function retrieves an article by UID using a database transaction interface (DBTX). It executes a generated query, handles errors, and maps the result into the domain Article struct.
Fetching Multiple Articles
The findArticles function retrieves multiple records and preallocates a slice for memory efficiency. Each database row is converted into the domain model before being returned.
Refactoring with Row Mapping Function
A rowToArticle helper function is introduced to eliminate duplicated mapping logic. This centralizes transformation logic and improves maintainability.
Creating an Article
An ArticleData struct is introduced to encapsulate input data for article creation. The createArticle function inserts a new record, generates a UID, handles nullable content fields, and returns the mapped domain model.
Separation of Concerns and Validation
The design separates domain models from database entities and prepares for future data validation. Timestamp fields are handled by the database, and validation logic will be added later.
All right, let's actually start building out our articles model. So I'm going to start by going into models and then into this models.go file where I want to create a variable called queries. And I can't spell queries. There we go. DB new. And we simply just create an instance of the query struct that we saw in the last
Every search we can share this variable across all of our models that we're going to be creating. Let's begin by adjusting our definition of our article model and article model that we defined in the previous in Part 1 where we created some dummy data and we created a simplified version of a model. Now we need the ID to be a UID. We need a created app that's going to be a time that
We can duplicate that and create our update red as well. There's also going to be a time dot time. We still need title, we still need content, but we also need to have the excerpt string that we go. And let's just remove this dummy data. For now, this mirrors what we have in the database or mirrors the database table and the entity that is generated by SQL C. But by defining our own
It gives us a lot of freedom to better model it to our domain or to our application. In a future episode, we will be introducing tags to our article model, so we can associate a tag or multiple tags with an article, so we can showcase that in the UI. These will not live in the articles, they will live in a separate table, but our domain model will still have a slice of tags associated
So for now, it's going to be a one-to-one representation of the database, but just know that we are going to be updating this in the future to better fit how we define our article in our code base. With that, we can also update how our findArticle function works, so it actually uses the database. And we need to pass a few things here. We need to pass a connection to the database. So we're going to be using dbtx, which is also generated by SQLC.
And then we're going to pass a UID. And this will simply just map to a connection that we can pass on a higher level that will call this function. Then we will simply say row error and say curious the variable we just created. And now we can say query article by ID, pass the context, pass the dbtx and pass the id. Then we're going to return and
and empty structure of article and the error, and let whomever calls this function deal with any errors that might occur. And then finally, we simply just fill out this article structure with the data from the database. So ID, row, come on, create it at that time, row, update it at that time, we're gonna have row,
title, row excerpts, and finally, row content string. Notice here that we have some of these, this data we get back from the database, have a dot time on extra method. And this is because the database package uses a library called pdx that has its own defined types.
We could just use these types in our own structure or literally just pass the entity that gets generated by SQLC. But doing so, we also tie ourselves to that library and we risk having breaking changes if something in that library would change internally or change how they work with type. So by doing this, we only have one place where we need to go and update.
They're mapping to how we define an article in our code base. So I much more prefer to stick with the standard libraries types so that we can fully rely on them being there and not have a library upgrade or library change potentially break our code in the future. So we simply just map to standard library types. And a good example of this is the content.
The content type we get back here from the database is a pttype.txt. And this is because this field can be null, so we can have no value. So we need to tell the code how to deal with that situation. And for now, we're just going to get back a string. There can either be a value if there is content or just an empty string if there is no content. But we're not guaranteed a value like we are with title and excerpt that is said to be not null.
Next, we can update defined articles function here, but we also need to pass the dbtx.go. And we're gonna say rows, error. Again, use the curious variable and say query articles. Pass the context, pass dbtx. Just gonna turn it on error if there is an error. And now we're gonna say articles equals to make.
slice article, length rows, and then we are simply gonna map over the returned rows and assign it to the slice. And this is a little bit more of a memory efficient way since we know how many elements that needs to be in the slice. So you say for i and row in range rows, range rows, you're gonna say articles,
at the index is going to be an article and we can actually just grab what we have up here and pass that, give that a save and just return articles and nil. So we are doing a little bit of the same here. The only change is really that we pre-create article slice with a predefined number of
of elements that can fit into this slice and then we just loop over what we get back from the database and add it to this article slice and we turn that. But as you can see, we are starting to repeat ourselves a little bit here in the way we map the values from the database to our representation of an article. So let's quickly fix that. And to do that, we can create another function called row to article. That's going to take in a row, which is the
DB representation of an article, and it will just return an article. So we can grab this thing right here. Oh, let me get all of it. Go down here, give that a save. And now instead of mapping, we just say row to article, pass the row, and we do the same here. Row to article, and now everything looks a little bit cleaner. Okay.
We can pull out one article and we can pull out multiple articles. But we also need a way to insert the article. And for that, I'm gonna specify an article data struct that will simply just have the data we need to create an article. And it's gonna be title. It's gonna be excerpt. And it's gonna be content. And then we can create a new function called create article. That will have the context again.
dbtx, and it's gonna have the data that is gonna be an article data struct. And we're gonna be returning an article or an error. Just as before, we're gonna get back a row or an error, and we're gonna use the curious variable, but here we're gonna say insert article, and pass the context, dbtx, and then we're gonna have this params,
struct that is generated for us by SQLC. So let me just quickly, we're just gonna handle the error by returning it and turning an empty article. And then we can fill in this, yeah, these params. We're gonna create the ID. We could technically pass the ID as well, but might as well just handle it here. I'll say data title, data excerpt, and content uses this pg type.
The text that we showed a little bit earlier. So we need to pass in a string here. Gonna be data content. And we need to say if it's valid or not, so we can simply say data content does not equal an empty string. And then we simply just return row to article. I put the comment wrong place and nil.
We are not doing any data validation at this stage. We will add it later on in the course. So just note that one of the reasons why we have this data struct is that we can use some tools or libraries to add validation and make sure that whenever we create a new article, it follows our definition of being a valid article. For now, we're simply just passing data along.
and creating an ID in here. We can see that we also don't have to create the created add and updated add fields because that gets taken care of by the database. And now we're actually ready to start using these models. We can create it, we can cure it, and we can actually start using it in our views.