Models
Models
Models are the easiest way to interact with your tables. A model is a way for you to interact with a Python class in a simple and elegant way and have all the hard overhead stuff handled for you under the hood. A model can be used to query the data in the table or even create new records, fetch related records between tables and many other features.
Creating A Model
The first step in using models is actually creating them. You can scaffold out a model by using the command:
You can use the --directory
flag to specify the location of these models
This will create a post model like so:
From here you can do as basic or advanced queries as you want. You may need to configure your model based on your needs, though.
From here you can start querying your records:
We'll talk more about setting up your model below
Conventions And Configuration
Masonite ORM makes a few assumptions in order to have the easiest interface for your models.
The first is table names. Table names are assumed to be the plural of your model name. If you have a User model then the users
table is assumed and if you have a model like Company
then the companies
table is assumed. You can realize that Masonite ORM is smart enough to know that the plural of Company
is not Companys
so don't worry about Masonite not being able to pick up your table name.
Table Name
If your table name is something other than the plural of your models you can change it using the __table__
attribute:
Primary Keys
The next thing Masonite assumes is the primary key. Masonite ORM assumes that the primary key name is id
. You can change the primary key name easily:
Connections
The next thing Masonite assumes is that you are using the default
connection you setup in your configuration settings. You can also change this on the model:
Mass Assignment
By default, Masonite ORM protects against mass assignment to help prevent users from changing values on your tables you didn't want.
This is used in the create and update methods. You can set the columns you want to be mass assignable easily:
Guarded attributes can be used to specify those columns which are not mass assignable. You can prevent some of the fields from being mass-assigned:
Timestamps
Masonite also assumes you have created_at
and updated_at
columns on your table. You can easily disable this behavior:
Timezones
Models use UTC
as the default timezone. You can change the timezones on your models using the __timezone__
attribute:
Querying
Almost all of a model's querying methods are passed off to the query builder. If you would like to see all the methods available for the query builder, see the QueryBuilder documentation here.
Single results
A query result will either have 1 or more records. If your model result has a single record then the result will be the model instance. You can then access attributes on that model instance. Here's an example:
You can also get a record by its primary key:
Collections
If your model result returns several results then it will be wrapped in a collection instance which you can use to iterate over:
If you want to find a collection of records based on the models primary key you can pass a list to the find
method:
The collection class also has some handy methods you can use to interact with your data:
If you would like to see more methods available like pluck
be sure to read the Collections documentation.
Deleting
You may also quickly delete records:
This will delete the record based on the primary key value of 1.
You can also delete based on a query:
Sub-queries
You may also use sub-queries to do more advanced queries using lambda expressions:
Selecting
By default, Masonite ORM performs SELECT *
queries. You can change this behavior in a few ways.
The first way is to specify a __selects__
attribute with a list of column names. You may use the as
keyword to alias your columns directly from this list:
Now when you query your model, these selects will automatically be included:
Another way is directly on the all()
method:
This will also work on the get
method as well:
Relationships
Another great feature, when using models, is to be able to relate several models together (like how tables can relate to each other).
Belongs To (One to One)
A belongs to relationship is a one-to-one relationship between 2 table records.
You can add a one-to-one relationship easily:
It will be assumed here that the primary key of the relationship here between users and companies is id -> {method_name}_id
. You can change the relating columns if that is not the case:
The first argument is always the column name on the current model's table and the second argument is the related field on the other table.
Has One (One to One)
In addition to belongs to, you can define the inverse of a belongs to:
Note the keys here are flipped. This is the only relationship that has the keys reversed
Has Many (One to Many)
Another relationship is a one-to-many relationship where a record relates to many records, in another table:
The first argument is always the column name on the current model's table and the second argument is the related field on the other table.
Has Many (Many To Many)
When working with many to many relationships, there is a pivot table in between that we must account for. Masonite ORM will handle this pivot table for you entirely under the hood.
In a real world situation you may have a scenario where you have products and stores.
Stores can have many products and also products can be in many stores. For example, a store can sell a red shirt and a red shirt can be sold in many different stores.
In the database this may look something like this:
Notice that there is a pivot table called product_store
that is in between stores and products.
We can use the belongs_to_many
relationship to get all the products of a store easily. Let's start with the Store
model:
We can change the signature of the decorator to specify our foreign keys. In our example this would look like this:
The first 2 keys are the foreign keys relating from stores to products through the pivot table and the last 2 keys are the foreign keys on the stores and products table.
If there are additional fields on your pivot table you need to fetch you can add the extra fields to the pivot record like so:
This will fetch the additional fields on the pivot table which we have access to.
Once we create this relationship we can start querying from stores
directly to products
:
On each fetched record you can also get the pivot table and perform queries on it. This pivot record is the joining record inside the pivot table (product_store
) where the store id and the product ID match. By default this attribute is pivot
.
Changing Options
There are quite a few defaults that are created but there are ways to override them.
The first default is that the pivot table has a primary key called id
. This is used to hydrate the record so you can update the pivot records. If you do not have a pivot primary key you can turn this feature off:
You can also change the ID to something other than id
:
The next default is the name of the pivot table. The name of the pivot table is the singular form of both table names in alphabetical order. For example, if you are pivoting a persons
table and a houses
table then the table name is assumed to be house_person
. You can change this naming:
The next default is that there are no timestamps (updated_at
and created_at
) on your pivot table. If you would like Masonite to manage timestamps you can:
The next default is that the pivot attribute on your model will be called pivot
. You can change this:
Now when you need to get the pivot relationship you can do this through:
If you have timestamps on your pivot table, they must be called created_at
and updated_at
.
Using Relationships
You can easily use relationships to get those related records. Here is an example on how to get the company record:
With Count
The with_count
method can be used to get the number of records in a relationship.
If you want to fetch the number of permissions a role has for example:
This will return a collection on each record with the {relationship}_count
attribute. You can get this attribute like this:
The method also works for single records
You may also optionally pass in a lambda function as a callable to pass in an additional query filter against the relationship
Eager Loading
You can eager load any related records. Eager loading is when you preload model results instead of calling the database each time.
Let's take the example of fetching a user's phone:
This will result in the query:
This will result in a lot of database calls. Now let's take a look at the same example but with eager loading:
This would now result in this query:
This resulted in only 2 queries. Any subsquent calls will pull in the result from the eager loaded result set.
You can also default all model calls with eager loading by using the __with__
attribute on the model:
Nested Eager Loading
You may also eager load multiple relationships. Let's take another more advanced example...
Let's say you would like to get a user's phone as well as their contacts. The code would look like this:
This would result in the query:
You can see how this can get pretty large as we are looping through hundreds of users.
We can use nested eager loading to solve this by specifying the chain of relationships using .
notation:
This would now result in the query:
You can see how this would result in 3 queries no matter how many users you had.
Joining
If you have relationships on your models you can easily join them:
If you have a model that like this:
You can use the joins
method:
This will build out the join
method.
You can also specify the clause of the join (inner, left, right). The default is an inner join
Additionally if you want to specify additional where clauses you can use the join_on
method:
Scopes
Scopes are a way to take common queries you may be doing and condense them into a method where you can then chain onto them. Let's say you are doing a query like getting the active user frequently:
We can take this query and add it as a scope:
Now we can simply call the active method:
You may also pass in arguments:
then pass an argument to it:
Soft Deleting
Masonite ORM also comes with a global scope to enable soft deleting for your models.
Simply inherit the SoftDeletesMixin
scope class:
Now whenever you delete a record, instead of deleting it it will update the deleted_at
record from the table to the current timestamp:
When you fetch records it will also only fetch undeleted records:
You can disable this behavior as well:
You can also get only the deleted records:
You can also restore records:
Lastly, you can override this behavior and force the delete query:
You still need to add the deleted_at
datetime field to your database table for this feature to work.
There is also a soft_deletes()
helper that you can use in migrations to add this field quickly.
If the column name is not called deleted_at
you can change the column to a different name:
Truncating
You can truncate the table used by the model directly on the model:
Updating
You can update records:
When updating a record, only attributes which have changes are applied. If there are no changes, update won't be triggered.
You can override this behaviour in different ways:
you can pass
force=True
toupdate()
method
you can define
__force_update__
attribute on the model class
you can use
force_update()
method on model:
You can also update or create records as well:
If there is a record with the username of "Joe" it will update that record or, if not present, it will create the record.
Note that when the record is created, the two dictionaries will be merged together. So if this code was to create a record it would create a record with both the username of Joe
and active of 1
.
When updating records the updated_at
column will be automatically updated. You can control this behaviour by using activate_timestamps
method:
Creating
You can easily create records by passing in a dictionary:
This will insert the record into the table, create and return the new model instance.
Note that this will only create a new model instance but will not contain any additional fields on the table. It will only have whichever fields you pass to it.
You can "refetch" the model after creating to get the rest of the record. This will use the find
method to get the full record. Let's say you have a scenario in which the active
flag defaults to 1 from the database level. If we create the record, the active
attribute will not fetched since Masonite ORM doesn't know about this attribute.
In this case we can refetch the record using .fresh()
after create:
Bulk Creating
You can also bulk create using the query builder's bulk_create method:
This will return a collection of users that have been created.
Since hydrating all the models involved in a bulk create, this could be much slower when working with a lot of records. If you are working with a lot of records then using the query builder directly without model hydrating will be faster. You can do this by getting a "new" query builder and call any required methods off that:
Serializing
You can serialize a model very quickly:
This will return a dict of all the model fields. Some important things to note:
Date fields will be serialized with ISO format
Eager loaded relationships will be serialized
Attributes defined in
__appends__
will be added
If you want to hide model fields you can use __hidden__
attribute on your model:
In the same way you can use __visible__
attribute on your model to explicitly tell which fields should be included in serialization:
You cannot use both __hidden__
and __visible__
on the model.
If you need more advanced serialization or building a complex API you should use masonite-api package.
Changing Primary Key to use UUID
Masonite ORM also comes with another global scope to enable using UUID as primary keys for your models.
Simply inherit the UUIDPrimaryKeyMixin
scope:
You can also define a UUID column with the correct primary constraint in a migration file
Your model is now set to use UUID as a primary key. It will be automatically generated at creation.
You can change UUID version standard you want to use:
Casting
Not all data may be in the format you need it. If you find yourself casting attributes to different values, like casting active to an int
then you can set it to the right type in the model:
Now whenever you get the active attribute on the model it will be an int
.
Other valid values are:
int
bool
json
Dates
Masonite uses pendulum
for dates. Whenever dates are used it will return an instance of pendulum.
You can specify which fields are dates on your model. This will be used for serializing and other logic requirements:
Overriding Dates
If you would like to change this behavior you can override 2 methods: get_new_date()
and get_new_datetime_string()
:
The get_new_date()
method accepts 1 parameter which is an instance of datetime.datetime
. You can use this to parse and return whichever dates you would like.
If the datetime parameter is None then you should return the current date.
The get_new_datetime_string()
method takes the same datetime parameter but this time should return a string to be used in a table.
Accessors and Mutators (Getter and Setter)
Accessors and mutators are a great way to fine tune what happens when you get and set attributes on your models.
To create an accessor we just need to create a method in the get_{name}_attribute
method name:
The same thing is true for mutating, or setting, the attribute:
Events
Models emit various events in different stages of its life cycle. Available events are:
booting
booted
creating
created
deleting
deleted
hydrating
hydrated
saving
saved
updating
updated
Observers
You can listen to various events through observers. Observers are simple classes that contain methods equal to the event you would like to listen to.
For example, if you want to listen to when users are created you will create a UserObserver
class that contains the created
method.
You can scaffold an obsever by running:
If you do not specify a model option, it will be assumed the model name is the same as the observer name
Once the observer is created you can add your logic to the event methods:
The model object receieved in each event method will be the model at that point in time.
You may then set the observer to a specific model.
If you are using Masonite, this could be done in a service provider:
If you are using Masonite ORM outside of Masonite you can simply do this at the bottom of the model definition:
Related Records
There are many times you need to take several related records and assign them all to the same attribute based on another record.
For example, you may have articles you want to switch the authors of.
For this you can use the attach
and save_many
methods. Let's say you had a User
model that had a articles
method that related to the Articles
model.
This will take all articles where user_id is 2 and assign them the related record between users and article (user_id).
You may do the same for a one-to-one relationship:
Attributes
There are a few attributes that are used for handling model data.
Dirty Attributes
When you set an attribute on a model, the model becomes "dirty". Meaning the model now has attributes changed on it. You can easily check if the model is dirty:
You specifically get a dirty attribute:
This will get the value of the dirty attribute and not the attribute that was set on the model.
Original
This keeps track of the original data that was first set on the model. This data does not change throughout the life of the model:
Saving
Once you have set attributes on a model, you can persist them up to the table by using the save method:
Last updated