Transcript
-
So at this point, we know that migrations are for defining and altering the structure of the database, defining the tables, the columns,
-
and the relationships using foreign keys. So that's half the picture that we have taken care of. Now we need the other half of the picture where we actually define the structure of our database
-
to our application so that we can use it, make queries against it, create data, update and delete data as well. And that's where models would come into play.
-
Models allow us to describe our database and its structure to our application so that we can create, read, update, and delete data from our database itself.
-
We're gonna have one model per table that we have inside of our database. That one particular model will define all of the columns and relationships specific to that one table.
-
Now, when we created our application, AdonisJS started us off with a model for our user. So far we've ignored it, but if we scroll down a little bit, we're gonna see that it's exporting a default class,
-
the same as we did with our movie model, and it's calling a compose helper method. Right now, all you need to know about this method is that it's going to join two classes together
-
and allow us to extend off of both of them. So essentially our user model is extending off of a base model as well as an auth finder. Auth finder is described right up here,
-
and this is what will come into play whenever we talk about authentication. For right now though, we're just going to ignore it because it doesn't come into play with anything that we're discussing in this particular lesson or even module.
-
So at this point, if we were to take authentication out of the question, our model could look something like this. It's just a user class extending a base model class. This base model class is going to bring in
-
lucid superpowers, essentially allowing our class itself to communicate with our database, querying information, creating information, updating and deleting as well.
-
It's also gonna use these column decorators that we have added onto our property declarations to describe these property declarations as columns inside of our database. As we'll see throughout this module,
-
we can have additional properties described inside of our user class or any model they're in that aren't particularly columns inside of our database, but these column declarations inform Lucid
-
that this property should be matched against a column inside of our database itself. So that whenever we query using our user model, it knows to pluck the ID
-
and plop it into this particular property. These column decorators, as you can see right here, also allow us to define additional things about the column itself. For example, our ID is our primary key,
-
essentially saying that the ID uniquely identifies one particular record inside of our user model and inside of our users table. If we scroll down a little bit, we're also gonna see column.dateTime.
-
This informs Lucid that we want to ingest the createdAt and updatedAt for these two columns as Luxon dateTime objects, so that whenever we query a user,
-
these createdAt and updatedAt properties will actually come back as Luxon dateTime objects instead of just strings or traditional JavaScript dates. Now, in case you're not familiar with Luxon,
-
it's a more up-to-date version of Moment. If you recall back to the older Moment package, that made it really easy to work with dateTimes. This is the newer version of that, and it's gonna do pretty much the exact same thing.
-
It's gonna make working with dateTimes a lot more simple. Then just like we have on our ID column, we're describing additional information to these createdAt and updatedAt timestamps as well.
-
So we're describing that they should auto-create. So anytime that we create a record using our user model, both our createdAt and updatedAt properties will auto-set themselves to the current dateTime,
-
and then auto-update will also auto-update by itself as well. So anytime that we update a record using our user model, our updatedAt will automatically update
-
to the current dateTime of which we're updating the record, just automating some of that manual work that we would have to do and remember about whenever it comes time to work with CRUD operations against our models.
-
CRUD meaning create, read, update, and delete, in case you're not familiar with that acronym. Okay, so there's a couple of properties on our user within our database that we don't have added onto our model.
-
So we need to get those added. First, we have our roleId. So we'll come in here, we'll describe a brand new column, and let's go ahead and declare roleId,
-
and that'll be of type number. Now, as we type this out, you might notice one thing. If we open up pgAdmin here and take a look at our users table and take a look at the columns on it,
-
our roleId is snakeCased, role_id. The same is being applied to our full name, createdAt and updatedAt here as well. Let's go ahead and hide that back away. The reason we're doing camelCased here
-
is that this is in line with the default naming structure that Lucid uses to describe information to us. As developers within a JavaScript environment, it's a little bit more common
-
to work with properties directly as camelCased as opposed to snakeCased, but whenever it comes to database structure, it's more common to work with snakeCased. So they're keeping to the naming convention
-
used for the particular environment that we're working within. So by default, Lucid is going to expect columns inside of our database to be defined in snakeCased form,
-
so full_name, and then whenever we jump into the model and our code therein, it'll expect it in camelCased, so fullName. If you were to have any one property within your database
-
that doesn't align to this naming convention, you can add a configuration into your column decorator and manually define the column name for this one particular property.
-
So for example, if the column name inside of our database was instead userName, we could switch this to user_name, and now Lucid would use user_name
-
to try and look up the value for our full name within a database as user_name. Now you can also change the default naming convention used by Lucid, so you can change the snakeCased within the database
-
and even the camelCased within your models, and to propagate that everywhere, you can make that change on the base model itself, which all of our models will extend, but that particular use case is outside the realm of this lesson.
-
Just note that it is an option. Okay, so we're done with our tangent about naming conventions here for our models. We know snakeCased in the database, camelCased in the model. We have one additional column to define for our user,
-
so @Column, declare, and we have our avatar URL, which will be a string. Cool, now before we leave this model, I'm gonna add that compose back so that we don't forget about it,
-
along with our authFinder, just like so, but don't worry about that at this point in time. We'll cover that whenever we get to authentication.
Introducing Lucid Models
We'll introduce models using the Lucid ORM. We'll learn how we can map database columns to our model properties and specify special behavior for our date time columns.
Join the Discussion 6 comments
-
Hi Tom,
When I initially created the adonis project, it didn't create the “User” model for me. Is there a “node ace” command to specifically create the “User” class? At first glance I don't seem to see one in the group “maker" command.
1-
Responding to noctisy
Hi Noctisy! You most likely skipped installing an auth guard during your project's creation (this is a new question during project creation that was added after we released our lesson). You can get this easily added in by installing and configuring the AdonisJS Auth package!
node ace add @adonisjs/auth --guard=session
Copied!https://docs.adonisjs.com/guides/authentication/introduction#installation
0
-
-
I just have a question about defining the model. I'm coming from NestJS/TypeORM background, and it's weird for me why you defined the migration first and then update the model second, could you please explain why.
In TypeORM, I used to define the DB models, and it'll take care of generating the tables in the DB automatically, by just setting synchronize to true in the connection config. Is there something similar in Lucid.
1-
Responding to hexacker
Hey Hexacker!
In AdonisJS, migrations are what create our tables, foreign keys, indexes, etc. Migrations are great because they give us control to incrementally build or update our database as we need in the order we need. Most of the time, in starting an application, it'll seem tedious. But, as your application ages, they're fantastic for maintaining flexibility.
For example, if I have a pre-existing database with a
histories
table that holds two types of histories, view history and progression history. As the app ages, if I decide it'd behoove me to have this one table split into two, I can easily use migrations to create aview_histories
table and aprogression_histories
table. Then, in those same migrations, I can fill them directly with data from thehistories
table. Then, delete thehistories
table. These changes would take 3 migrations, which are run one after another at the same time my application with the needed code updates are deploying.create_view_histories_table
Creates theview_histories
table, then uses thedefer
method to populate it with pre-existing view histories from thehistories
table.create_progression_histories_table
Creates theprogression_histories
table, then uses thedefer
method to populate it with pre-existing progression histories from thehistories
table.delete_histories_table
Drops the no longer neededhistories
table
Models then, are strictly used to aide in CRUD operations inside our code. At present, there is no official way to generate migrations nor models. However, I do have a package in dev release to generate models from a database.
Fun fact, the migration example above is based on a refactor I did on the Adocasts site.
1-
Responding to tomgobich
Thank you Tom for the very detailed answer, it just looks really weird for me the process especially that I'm used to the flow on TypeORM, which scan the models, create the schema on the DB and you're good to go from there.
Your package looks very promising and it do a great job, I will check it and see if I can help you improve it.
1-
Responding to hexacker
Anytime! The grass can be a different shade of green depending on the ecosystem you're in, but just because it is different doesn't mean it's a bad thing. The migration approach is a common and popular choice, used in the like of .NET, Laravel, and likely others but those are two I know and have personally used.
Thank you, Hexacker! The one thing those two (.NET & Laravel) offer that AdonisJS doesn't is model generation, which is why I wanted to make a package to at least offer something in that regard. An approach for model/migration generation has been something planned by the core team, but it is likely still a ways out.
With model generation in play, the flow would go:
Define migrations → run migrations → generate models1
-
-