Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Masonite ORM was built for the Masonite Web Framework but is built to work in any Python project. It is heavily inspired by the Orator Python ORM and is designed to be a drop in replacement for Orator. Orator was inspired by Laravel's Eloquent ORM so if you are coming from a framework like Laravel you should see plenty of similiarities between this project and Eloquent.
Masonite ORM is a beatiful implementation that includues models, migrations, a query builder, seeds, command scaffolding, query scopes, eager loading, model relationships and many more features.
Masonite ORM currently supports MySQL, Maria, Postgres and SQLite databases.
You can contribute to the project at the Masonite ORM Repository
I will discuss the flow at a high level first and then can talk about each part separately.
There are a few different paths you can start out with. Not everything starts out at the model level. You may use the query builder class directly to build your queries. The query builder class is exactly that: a class to build queries. So you will interact with this class (more on the class later) and it will set things like wheres, limits, selects, etc to the class and then pass all that off to build a query.
First let's talk about the flow of the Model
. The Model
is probably what most people will be using the majority of the time. The Model
is basically an wrapper entity around a table. So 1 table will likely equal to 1 model. A users
table will have a User
model and a articles
table will have an Article
model.
The interesting things about the Model
is that its just a shell around the QueryBuilder
class. The majority of the time you call something on the Model
it's actually just building a query builder class immediately and passing the rest of the call off. This is important to understand:
For example,
Since it returns a query builder we can simply build up this class and chain on a whole bunch of methods:
Finally when we are done building a query we will call a .get()
which is basically an executionary command:
When you call get
, the query builder will pass everything you built up (1 select, 2 where statements) and pass those into a Grammar
class. The Grammar
class is responsible for looping through the 3 statements and compiling them into a SQL query that will run. So the Grammar
class will compile a query that looks like this:
If it needs to build a Qmark query (a query with question marks which will be replaced with query bindings to prevent SQL injection) then it will look like this:
and have 2 query bindings:
Once we get the query we can then pass the query into the connection class which will connect to the MySQL database to send the query.
We will then get back a dictionary from the query and "hydrate" the original model. When you hydrate a model it simply means we set the dictionary result into the model so when we access something like user.name
we will get the name of the user. Think of it as loading the dictionary into the class to be used later during accession and setting.
Grammar classes are classes which are responsible for the compiling of attributes into a SQL statement. Grammar classes are used for DML statements (select, insert, update and delete). Grammars are not used for DDL statements (create and alter). The SQL statement will then be given back to whatever called it (like the QueryBuilder
class) and then passed to the connection class to make the database call and return the result. Again the grammar class is only responsible for compiling the query into a string. Simply taking attributes passed to it and looping through them and compiling them into a query.
The grammar class will be responsible for both SQL and Qmark. Again, SQL looks like this:
And Qmark is this:
Qmark queries will then be passed off to the connection class with a tuple of bindings like (18,)
. This helps protect against SQL injection attacks. All queries passed to the connection class should be the qmark query. Compiling SQL is really for debugging purposes while developing. Passing straight SQL into the connection class could leave queries open to SQL injection.
Any values should be able to be qmarked. This is done inside the grammar class by replacing the value with a '?'
and then adding the value to the bindings. The grammar class knows it should be qmarked by passing the qmark boolean variable throughout the grammar class.
The grammar class is also really an abstraction as well. All the heavy lifting is done inside the BaseGrammar
class. Child classes (like MySQLGrammar
and PostgresGrammar
, etc) really just contain the formatting of the sql strings.
Currently there are 2 different grammar classes for each of the supported grammars. There is one for normal queries and one for schema queries. They could be 1 big class but the class would be giant and it is hard to maintain a god class like this responsable for everything. It also makes it harder to first build the grammar up for quering (selects, updates, deletes, etc) and then later support schema building.
Almost all SQL is bascially the same but with slightly different formats or placements for where some syntax goes. This is why this structure we use is so powerful and easy to expand or fix later on.
For example, MySQL has this format for select statements with a limit:
But Microsoft SQL Server has this:
Notice the SQL is bascially the same but the limiting statement is in a different spot in the SQL.
We can accomplish this by specifying the general: select, insert, update and delete formats so we can better organize and swap the placement later. We do this by using Python keyword string interpolation. For example let's break down to a more low level way on how we can accomplish this:
Here is the MySQL grammar class select statement structure. I will simplify this for the sake of explanation but just know this also contains the formatting for joins, group by's in the form of {joins}
, {group_by}
etc:
MySQL:
Microsoft SQL:
Simply changing the order in this string will allow us to replace the format of the SQL statement generated. The last step is to change exactly what the word is.
Again, MySQL is LIMIT X
and Microsoft is TOP X
. We can accomplish this by specifying the differences in their own method. Remember these are all in the subclasses of the grammar class. Mysql is in MySQLGrammar
and Microsoft is in MSSQLGrammar
MySQL:
and Microsoft:
Now we have abstracted the differences into their own classes and class methods. Now when we compile the string, everything falls into place. This code snippet is located in the BaseGrammar
class (which calls the supported grammar class we built above).
Let's remove the abstractions and explode the variables a bit so we can see more low level what it would be doing:
MySQL:
Microsoft:
So notice here the abstractions can be changed per each grammar for databases with different SQL structures. You just need to change the response of the string returning methods and the structure of the select_format
methods
The child grammar classes have a whole bunch of these statements for getting the smaller things like a table
Most methods in the child grammar classes are actually just these strings.
MySQL tables are in the format of this:
Postgres and SQLite tables are in the format of this:
and Microsoft are this:
So again we have the exact same thing on the grammar class like this:
Which unabstracted looks like this for MySQL:
and this for Microsoft:
There are a whole bunch of these methods in the grammar classes for a whole range of things. Any differences that there can possible be between databases are abstracted into these methods.
There are a whole bunch of methods that begin with process_
or _compile_
so let's explain what those are.
Now that all the differences between grammars are abstracted into the child grammar classes, all the heavy listing can be done in the BaseGrammar
class which is the parent grammar class and really the engine behind compiling the queries for all grammars.
This BaseGrammar
class is responsible for doing the actual compiling in the above section. So this class really just has a bunch of classes like process_wheres
, process_selects
etc. These are more supporting methods that help process the sql strings for the _compile_
methods.
There are also methods that begin with _compile_
. These are for responsable for compiling the actual respective queries. The heart of this class really lies in the _compile_select
, _compile_create
, _compile_update
, _compile_delete
methods.
Let's bring back the unabstracted version first:
Now let's start abstracting until we get what is really in the class.
And now what that method would really look likes with the supporting _compile
methods in place:
So notice we have a whole bunch of _compile
methods but they are mainly just for supporting the main compiling of the select, create or alter statements.
And now finally what the method actually looks like in the class:
Models and query builders are really hand in hand. In almost all cases, a single method on the model will pass everything off to the QueryBuilder
class immediately.
Just know the Model is really just a small proxy for the QueryBuilder
. Most methods on the model simply call the QueryBuilder
so we will focus on the QueryBuilder
.
The only thing the model class does is contains some small settings like the table name, the attributes after a database call is made (query results) and some other small settings like the connection and grammar to use.
It is important though to know the differences between class (cls
) and an object instance. Be sure to read the section below.
One of the trickier bits of magic we have when it comes to the model is we set a meta class on the Model
class (the base class that all of your User
and Article
models will inherit). What this does is essentially creates a middleware between first calling methods. Since its really hard to do everything while handling different class instantances and class classes it's easier to catch the call and turn it into an instance before moving on.
This is hard to explain but let's see what this really solves:
We COULD just do this with everything:
And then perform model calls:
But it doesn't look as clean as:
(Also for backwards compatability with Orator it would be a huge change if we didn't support this).
So if you look at the Model.py
class we have a meta class inherited (you'll notice if you look at the file) which actually does a bit of magic and actually instanitates the class before any methods are called. This is similiar to any normal Python hook you can tie into like __getattr__
.
This makes handling cls
and self
much easier. Although there are special use cases where we need to handle cls directly which is why you will see some @classmethod
decorators on some model methods.
We mentioned that the model simply constructs a query builder and essentially passes everything off to the query builder class.
The issue though is that when you call something like User.where(..)
it will call the where on the User class. Since theres actually no where
method on the model class it will hook into the __getattr__
on the model class. From there we catch a bunch of different methods located in the __passthrough__
attribute on the model and pass that right off to the query builder. This is important to understand.
This QueryBuilder
class is responsible for building up the query so it will have a whole bunch of attributes on it that will eventually be passed off to the grammar class and compiled to SQL. That SQL will then be passed to the connection class and will do the database call to return the result.
The QueryBuilder
class is really the meat and potatoes of the ORM and really needs to be perfect and will have the most features and will take the most time to build out and get right.
For example, when you call where
on the model it will pass the info to the query builder and return this QueryBuilder
class.
All additional calls will be done on THAT query builder object:
Finally when you call a method like .get()
it will return a collection of results.
If you call first()
it will return a single model:
So again we use the QueryBuilder
to build up a query and then later execute it.
There are a few different classes which will aid in the compiling of SQL from the grammar class. These really are just various classes with different attributes on them. They are internal only classes made to better compile things inside the BaseGrammar
class, since we use things like isinstance checks and attribute conditionals. You will not be using these directly when developing applications. These classes are:
QueryExpression
- Used for compiling of where statements
HavingExpression
- Used for the compiling of Having statements
JoinExpression
- Used for the compiling of Join statements
UpdateExpression
- Used for the compiling of Update statements.
SubSelectExpression
- Used for compiling sub selects. Sub selects can be placed inside where statements to make complex where statements more powerful
SubGroupExpression
- Used to be passed into a callable to be executed on later. This is useful again for sub selects but just a layer of abstraction for callables
These are simply used when building up different parts of a query. When the _compile_wheres
, _compile_update
and other methods are ran on the grammar class, these just make it more simple to fetch the needed data and are not too generic to make difficult use cases challenging to code for.
The Model passes off anything set on it directly to the query builder once accessed. All calls after will be based on a new query builder class. All query building will be done on this class.
To be more clear, once we are done building the query and then call .get()
or .first()
, all the wheres, selects, group_by's etc are passed off to the correct grammar class like MySQLGrammar
which will then compile down to a SQL string.
That SQL string returned from the grammar class is then sent to the connection class along with the bindings from the grammar class. We then have a result in the form of a dictionary. We don't want to be working with a bunch of dictionaries though, we want to work with more models.
The QueryBuilder
object when returning the response is also responsible for hydrating your models if a model is passed in. If no model is passed into the initializer then it will just return a dictionary or list. Hydrating is really just a fancy word for filling dummy models with data. We really don't want to work with dictionaries in our project so we take the dictionary response and shove it into a Model and return the model. Now we have a class much more useful than a simple dictionary.
For times we have several results (a list of dictionaries) we simply loop through the list and fill a different model with each dictionary. So if we have a result of 5 results we loop through each one and build up a collection of 5 hydrated models. We do this by calling the .hydrate()
method which creates a new instance and hydrates the instance with the dictionary.
RELATIONSHIPS ARE STILL A WORK IN PROGRESS AND SUBJECT TO CHANGE
Relationships are a bit magical and uses a lot of internal low level Python magic to get right. We needed to do some Python class management magic to nail the inherently magical nature of the relationship classes. For example we have a relationship like this:
This is innocent enough but we would like when you access something like this:
BUT we also want to be able to extend the relationship as well:
so we need to both access the attribute AND call the attribute. Very strange I know. How would we get an attribute accession to:
find the correct model in the method
build the query
Find the correct foreign key's to fetch on
return a fully hydrated model ready to go
but when you call it simple do the wheres and return the query builder.
For this we do some decorator and attribute accession magic using the __get__
magic method which is called whenever an attribute is accessed. We can then hijack this hook and return whatever we need. In this case, a fully hydrated model or a query builder.
Its useful to explain the relationship classes.
We have a BaseRelationship
class which really just contains all the magic we need for the actual decorator to work.
We then have a BelongsTo
relationship (which is imported as belongs_to
in the __init__.py
file so this is where the name change comes from in the decorator) which has a simple apply_query
method with does the query needed to return the connection using the models QueryBuilder
. Here we have foreign
and owner
variables. foreign
is the relationship class (In this case, Profile
) and owner
is the current model (in this case User
).
The query is applied and returns a result from the query builder in the form of a dictionary or a list (for one result it will be a dictionary and if multiple are returned it will be a list). Then the normal process takes its course. If a dictionary it will return a hydrated model and if a list is returned it will return a collection of hydrated models.
The Schema class is responsible for the creation and altering of tables so will have a slightly different syntax for building a normal Query Builder class. Here we don't have things like where
and limit
. Instead of have things in the format of:
So now let's talk about how each class of the 3 primary classes talk to eachother here.
The Schema class is responsible for specifying the table and/or the connection to use. It will then will pass that information off to the Blueprint
class which really is the same thing as the relationship between Model
and QueryBuilder
. The Schema class is also responsible for setting either the create
or alter
modes. This is set if you either use Schema.create('users')
or Schema.table('users')
respectively.
The Blueprint
class is similiar to the QueryBuilder
class because both simply build up a bunch of columns to to act on. One is just used for fetching data and the other is used for changing or creating tables.
The Schema class calls the blueprint class as a context manager.
The blueprint class will be built up in this format:
Notice we are just building up a blueprint class.
When we start up the blueprint class, if we are creating columns then we will be setting additional attributes on a Table
class. If we are updating a table then we will be setting attributes on the TableDiff
class.
For example when we call:
it is a proxy call to
The blueprint class then builds up the table class.
Compiling DDL statements are much more complicated than compiling DML statements so there is an entire class dedicated to compiling DDL statements. The Platform classes are similiar to Grammar classes as they are both used to compile sql.
For example in SQLite there is an extremely limited alter statement. So adding, renaming or modifying columns relies on actually creating temporary tables, migrating the existing table to the temp table, then creating a new table based on the existing and modified schema, then migrating the old columns to the new columns and then finally dropping the temp table. You can see how this is not generic so it requires its own logic.
Because of this, there are Platform classes. SQLitePlatform
, MySQLPlatform
, etc. These class have a compile_create_sql and compile_alter_sql methods. These methods take a single table class. The same table class the blueprint class built up.
This Table class has methods like added_columns, removed_indexes, etc. We can use these to build up our alter and create statements.
For example, Postgres requires alter statements for adding columns to be ran 1 at a time. So we can't add multiple columns with 1 alter query. So we need to loop through all the Table.added_columns and create multiple alter queries for each column.
Finally we need to compile the query which is simply done by doing blueprint.to_sql()
which will either build a create
or alter
query depending on what was originally set by the Schema
class before.
This guide will explain how to move from Orator to Masonite ORM. Masonite ORM was made to be pretty much a straight port of Orator but allow the Masonite organization complete creative control of the ORM.
Orator has since been abandoned and Masonite needed a good ORM to keep fresh features and security up to date with the ORM.
Before moving your project over to Masonite ORM please keep in mind some features are not (_at least currently)_ ported over from Orator. These are features that may be ported over in the future.
This list is a continuously evolving list of features and anything we develop will be removed from the list. These features are planned but not yet finished.
Currently these features are:
through relationships (HasOneThrough, HasManyThrough, etc)
If you are using Masonite 2 then you will not be able to upgrade to Masonite ORM because of version conflicts between Masonite and Masonite 2 ORM.
The configuration dictionary between Orator and Masonite ORM is identical. The only difference is that Masonite ORM requires a config/database.py
file whereas Orator was optional and needed to be explicitly specified in several places like commands.
If you are coming from Masonite already then don't worry, this file is already there. If not you will need to create this config/database.py
file.
This is an example of a Masonite ORM config dictionary:
The other thing you will need to do is change the resolver classes. Orator has a configuration structure like this:
Masonite ORM those same resolver classes looks like this:
Models are identical but the imports are different. Orator requires you to set the model resolver from the configuration file and then you import that model.
In Masonite ORM you import the model directly:
Scopes are also identical but the import changes:
Relationships are also slightly different. In Orator there is a has_one
relationship and a belongs_to
relationship. In Masonite ORM this is only a belongs_to relationship. The logic behind has_one and belongs_to is generally identical so there was no reason to port over has_one other than for semantical purposes.
So if you have something like this in your Orator codebase:
It will now become:
In Orator, some relationships require a specific order of keys. For example a belongs to relationship is belongs_to('local_key', 'other_key')
but a has one is has_one('other_key', 'local_key')
. This is very confusing to remember so in Masonite ORM the keys are always local_key, other_key
.
In Orator you could do this:
This would delay the relationship call and would instead append the builder before returning the result.
The above call in Masonite ORM becomes:
Setting up Masonite is extremely simple.
If you are using the Masonite web framework than all the installation is setup for you. If you are using anything other than Masonite or building your own Python application then be sure to follow the install steps below:
First install via pip:
To start configuring your project you'll need a config/database.py
file. In this file we will be able to put all our connection information.
One we have our config/database.py
file we can put a DATABASES
variable with a dictionary of connection details. Each key will be the name of our connection. The connection name can be whatever you like and does not need to relate to a database name. Common connection names could be something like dev
, prod
and staging
. Feel free to name these connections whatever you like.
The connection variable will look something like this
Lastly you will need to import the ConnectionResolver
class and and register the connection details:
After this you have successfully setup Masonite ORM in your project!
If you would like, you can log any queries Masonite ORM generates to any supported Python logging handler.
Inside your config/database.py
file you can put on the bottom here. The StreamHandler will output the queries to the terminal.
You can specify as many handlers as you would like. Here's an example of logging to both the terminal and a file:
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.
The first step in using models is actually creating them. You can scaffold out a model by using the command:
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
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.
If your table name is something other than the plural of your models you can change it using the __table__
attribute:
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:
The next thing Masonite assumes is that you are using the default
connection you setup in your configuration settings. You can also change thing on the model:
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:
Masonite also assumed you have created_at
and updated_at
columns on your table. You can easily disable this behavior:
Models use UTC
as the default timezone. You can change the timezones on your models using the __timezone__
attribute:
sub queries
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:
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:
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:
You may also use subqueries to do more advanced queries using lambda expressions:
Another great feature when using models is to be able to relate several models together (like how tables can relate to eachother).
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 -> id
. You can change the relating columns if that is not the case:
The first argument is always the column name on the current models table and the second argument is the related field on the other table.
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 models table and the second argument is the related field on the other table.
You can easily use relationships to get those related records. Here is an example on how to get the company record:
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 users 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 may also eager load multiple relationships. Let's take another more advanced example:
Let's say you would like to get a users phone as well as the 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.
Scopes are a way to take common queries you may be doing and be able to 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 a lot:
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:
Masonite ORM also comes with a global scope to enable soft deleting for your models.
Simply inherit the SoftDeletes
scope:
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.
You can also update or create records as well:
If there is a record with the username or "Joe" it will update that record and else 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
.
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 UUID4 as a primary key. It will be automatically generated at creation.
You can change UUID version standard you want to use:
Not all data may be in the format you need it it. If you find yourself casting attributes to different values, like casting active to an int
then you can set it right on the model:
Now whenever you get the active attribute on the model it will be an int
.
Other valid values are:
int
bool
json
Masonite uses pendulum
for dates. Whenever dates are used it will return an instance of pendulum.
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.
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
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. This could be done in a service provider:
There's many times you need to take several related records and assign them all 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 associate
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:
The query builder is a class which is used to build up a query for execution later. For example if you need multiple wheres for a query you can chain them together on this QueryBuilder
class. The class is then modified until you want to execute the query. Models use the query builder under the hood to make all of those calls. Many model methods actually return an instance of QueryBuilder
so you can continue to chain complex queries together.
Using the query builder class directly allows you to make database calls without needing to use a model.
To get the query builder class you can simply import the query builder. Once imported you will need to pass the connection_details
dictionary you store in your config.database
file:
You can also switch or specify connection on the fly using the on
method:
You can then start making any number of database calls.
If you would like to use models you should reference the documentation. This is an example of using models directly with the query builder.
By default, the query builder will return dictionaries or lists depending on the result set. Here is an example of a result using only the query builder:
You can easily get the first record:
You can also simply fetch all records from a table:
Once you start chaining methods you should call the get()
method instead of the all()
method to execute the query.
For example, this is correct:
And this is wrong:
You may also specify any one of these where statements:
The simplest one is a "where equals" statement. This is a query to get where username
equals Joe
AND age
equals 18
:
You can also use a dictionary to build the where method:
You can also specify comparison operators:
Another common where clause is checking where a value is NULL
:
This will fetch all records where the admin column is NULL
.
Or the inverse:
This selects all columns where admin is NOT NULL
.
In order to fetch all records within a certain list we can pass in a list:
This will fetch all records where the age is either 18
, 21
or 25
.
You can do a WHERE LIKE or WHERE NOT LIKE query:
You can make subqueries easily by passing a callable into the where method:
Sometimes you need to specify conditional statements and run queries based on the conditional values.
For example you may have code that looks like this:
Instead of writing the code above you can use the when
method. This method accepts a conditional as the first parameter and a callable as the second parameter. The code above would look like this:
If the conditional passed in the first parameter is not truthy then the second parameter will be ignored.
It's also very simple to use both limit and/or offset a query.
Here is an example of a limit:
Here is an example of an offset:
Or here is an example of using both:
You may need to get all records where column values are between 2 values:
You may want to group by a specific column:
Having clauses are typically used during a group by. For example, returning all users grouped by salary where the salary is greater than 0:
You may also specify the same query but where the sum of the salary is greater than 50,000
Joining is a way to take data from related tables and return it in 1 result set as well as filter anything out that doesn't have a relationship on the joining tables.
This join will create an inner join.
You can also choose a left join:
and a right join:
There are times where you really just need to increment a column and don't need to pull any additional information. A lot of the incrementing logic is hidden away:
Decrementing is also similiar:
There are several aggregating methods you can use to aggregate columns:
If some queries would be easier written raw you can easily do so for both selects and wheres:
If you need to loop over a lot of results then consider chunking. A chunk will only pull in the specified number of records into a generator:
If you want to find out the SQL that will run when the command is executed. You can use to_sql()
. This method returns the full query and is not the query that gets sent to the database. The query sent to the database is a "qmark query". This to_sql()
method is mainly for debugging purposes.
See the section below for more information on qmark queries.
Qmark is essentially just a normal SQL statement except the query is replaced with question marks. The values that should have been in the position of the question marks are stored in a tuple and sent along with the qmark query to help in sql injection. The qmark query is the actual query sent using the connection class.
You can update many records.
You can delete many records as well. For example, deleting all records where active is set to 0.
Seeding is simply a way to quickly seed, or put data into your tables.
You can create a seed file and seed class which can be used for keeping seed information and running it later.
To create a seed run the command:
This will create some boiler plate for your seeds that look like this:
From here you can start building your seed.
A simple seed might be creating a specific user that you use during testing.
You can easily run your seeds:
Factories are simple and easy ways to generate mass amounts of data quickly. You can put all your factories into a single file.
Factory methods are simple methods that take a single Faker
instance.
Once created you can register the method with the Factory
class:
If you need to you can also name your factories so you can use different factories for different use cases:
To use the factories you can import the Factory
class from where you built your factories. In our case it was the config/factories.py
file:
This will persist these users to the database. If you want to simply make the models or collection (and not persist them) then use the make
method:
Again this will NOT persist values to the database.
By default, Masonite will use the factory you created without a name. If you named the factories you can call those specific factories easily:
If you want to modify any values you previously set in the factory you created, you can pass a dictionary into the create
or make
method:
This is a great way to make constant values when testing that you can later assert to.
Migrations are used to build and modify your database tables. This is done through use of migration files and the Schema
class. Migration files are really just wrappers around the Schema
class as well as a way for Masonite to manage which migrations have run and which ones have not.
Creating migrations are easy with the migration commands. To create one simply run:
This will create a migration file for you and put it in the databases/migrations
directory.
If you want to create a starter migration, that is a migration with some boilerplate of what you are planning to do, you can use the --table
and --create
flag:
This will setup a migration for you with some boiler plate on creating a new table
This will setup a migration for you for boiler plate on modifying an existing table.
To start building up your migration, simply modify the up
method and start adding any of the available methods below to your migration.
A simple example would look like this for a new table:
In addition to building up the migration, you should also build onto the down
method which should reverse whatever was done in the up
method. If you create a table in the up method, you should drop the table in the down method.
At any time you can get the migrations that have run or need to be ran:
If you would like to see just the SQL that would run instead of running the actual migrations, you can specify the -s
flag (short for --show
). This works on the migrate and migrate:rollback commands.
Refreshing a database is simply rolling back all migrations and then migrating again. This "refreshes" your database.
You can refresh by running the command:
In addition to the available columns you can use, you can also specify some modifers which will change the behavior of the column:
In addition to columns, you can also create indexes. Below are the available indexes you can create:
If you want to create a foreign key you can do so simply as well:
And optionally specify an on_delete
or on_update
method:
You can use these options:
If you would like to change a column you should simply specify the new column and then specify a .change()
method on it.
Here is an example of changing an email field to a nullable field:
You can truncate a table:
You can also temporarily disable foreign key checks and truncate a table:
You can drop a table:
You can drop a table if it exists:
Almost all of a models 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 documentation here.
If you would like to see more methods available like pluck
be sure to read the documentation.
For methods available on the faker
variable reference the documentation.
The default primary key is often set to an auto-incrementing integer, but you can .
aggregate
all
between
count
create
decrement
delete
first
get
group_by
having
increment
join
left_join
limit
max
not_between
offset
order_by
right_join
select
select_raw
sum
to_qmark
to_sql
update
where
where_column
where_exists
where_has
where_in
where_not_in
where_not_null
where_null
where_raw
Command
Description
table.string()
The varchar version of the table. Can optional pass in a length table.string('name', length=181)
table.integer()
The INT version of the database. Can also specify a length table.integer('age', length=5)
table.increments()
The auto incrementing version of the table. An unsigned non nullable auto incrementing integer.
table.big_increments()
An unsigned non nullable auto incrementing big integer. Use this if you expect the rows in a table to be very large
table.binary()
BINARY equivalent column. Sometimes is text field on unsupported databases.
table.boolean()
BOOLEAN equivalent column.
table.char()
CHAR equivalent column.
table.date()
DATE equivalent column.
table.datetime()
DATETIME equivalent column.
table.timestamp()
TIMESTAMP equivalent column.
table.timestamps()
Creates created_at
and updated_at
columns on the table with the timestamp
column and defaults to the current time.
table.decimal()
DECIMAL equivalent column. Can also specify the length and decimal position. table.decimal('salary', 17, 6)
table.double()
DOUBLE equivalent column. Can also specify a float length table.double('salary', 17,6)
table.enum()
ENUM equivalent column. You can also specify available options as a list. table.enum('flavor', ['chocolate', 'vanilla'])
. Sometimes defaults to a TEXT field with a constraint on unsupported databases.
table.text()
TEXT equivalent column.
table.unsigned_integer()
UNSIGNED INT equivalent column.
table.unsigned()
Alias for unsigned_integer
table.soft_deletes()
A nullable DATETIME column named deleted_at
. This is used by the SoftDeletes scope.
Command
Description
table.drop_table()
DROP TABLE equivalent statement.
table.drop_table_if_exists()
DROP TABLE IF EXISTS equivalent statement.
table.drop_column()
DROP COLUMN equivalent statement.
table.drop_index()
Drops the constraint. Must pass in the name of the constraint. drop_index('email_index')
table.drop_unique()
Drops the uniqueness constraint. Must pass in the name of the constraint. table.drop_unique('users_email_unique')
table.drop_foreign()
Drops the foreign key. Must specify the index name. table.drop_foreign('users_article_id_foreign')
table.drop_primary()
Drops the primary key constraint. Must pass in the constraint name table.drop_foreign('users_id_primary')
Command
Description
.nullable()
Allows NULL values to be inserted into the column.
.unique()
Forces all values in the column to be unique.
.after()
Adds the column after another column in the table. Can be used like table.string('is_admin').after('email')
.
.unsigned()
Makes the column unsigned. Used with the table.integer('age').unsigned()
column.
.use_current()
Makes the column use the CURRENT_TIMESTAMP
modifer.
Command
Description
table.primary()
Make the column use the PRIMARY KEY modifer.
table.unique()
Makes a unique index. Can pass in a column table.unique('email')
or list of columns table.unique(['email', 'phone_number'])
.
table.index()
Creates an index on the column. table.index('email')
Command
Description
.on_update('set null')
Sets the ON UPDATE SET NULL property on the constraint.
.on_update('cascade')
Sets the ON UPDATE CASCADE property on the constraint.
.on_delete('set null')
Sets the ON DELETE SET NULL property on the constraint.
.on_delete('cascade')
Sets the ON DELETE CASCADE property on the constraint.
all
avg
chunk
collapse
contains
count
diff
each
every
filter
first
flatten
for_page
forget
get
group_by
implode
is_empty
last
map_into
map
max
merge
pluck
pop
prepend
pull
push
put
reduce
reject
reverse
serialize
shift
sort
sum
take
to_json
transform
unique
where
zip