heroes, by storing our heroes in a database. This will let us to edit our heroes and keep the changes when we restart the application.
heroesapplication, we will have one type of entity - a "hero". To create a new entity, we subclass
ManagedObject<T>. Create a new directory
lib/model/and then add a new file to this directory named
hero.dart. Add the following code:
Heroentity. Entities are always made up of two classes.
_Heroclass is a direct mapping of a database table. This table's name will have the same name as the class:
_Hero. Every property declared in this class will have a corresponding column in this table. Therefore, the
_Herotable will have two columns -
idcolumn is this table's primary key (a unique identifier for each hero). The name of each hero must be unique.
Hero, is what we work with in our code - when we fetch heroes from a database, they will be instances of
Heroclass is called the instance type of the entity, because that's what we have instances of.
_Herois the table definition of the entity. You won't use the table definition for anything other than describing the database table.
Heroall of the properties of
_Hero. An instance type must extend
Tis also the table definition.
ManagedObject<T>has behavior for automatically transferring objects to the database and back (among other things).
lastName, but it's useful in some places to have a
fullNameproperty. Declaring the
fullNameproperty in the instance type means we have easy access to the full name, but we still store the first and last name individually.
channel.dart, add a new property
ManagedDataModel.fromCurrentMirrorSystem()will find all of our
ManagedObject<T>subclasses and 'compile' them into a data model. A
PostgreSQLPersistentStoretakes database connection information that it will use to connect and send queries to a database. Together, these objects are packaged in a
HeroesControllerto have access to the context.
heroes_controller.dart, add a property and create a new constructor:
HeroesControllerrequires a context in its constructor, we need to pass it the context we created in
HeroesControllercan execute database queries.
HeroesControllercurrently return heroes from an in-memory list. To fetch data from a database instead of this list, we create and execute instances of
heroes_controller.dart. Make sure to import your
model/hero.dartfile at the top:
Query<Hero>and then execute its
fetch()method. The type argument to
Query<T>is an instance type; it lets the query know which table to fetch rows from and the type of objects that are returned by the query. The context argument tells it which database to fetch it from. The
fetch()execution method returns a
List<Hero>. We write that list to the body of the response.
getHeroByIDto fetch a single hero from the database.
wheremethod to filter heroes that have the same
idas the path variable. For example,
/heroes/1will fetch a hero with an
1. This works because
Query.whereadds a SQL WHERE clause to the query. We'd get the following SQL:
wheremethod uses the property selector syntax. This syntax is a closure that takes an argument of the type being queried, and must return a property of that object. This creates an expression object that targets the selected property. By invoking methods like
equalToon this expression object, a boolean expression is added to the query.
fetchOne()execution method will fetch a single object that fulfills all of the expressions applied to the query. If no database row meets the criteria,
nullis returned. Our controller returns a 404 Not Found response in that scenario.
ManagedContext.fetchObjectWithID. When fetching with
fetchOne, make sure the search criteria is guaranteed to be unique.
+button on the bottom left corner of the screen to create a new database server. Choose a version (at least 9.6, but the most recent version is best), name the server whatever you like, and leave the rest of the options unchanged before clicking
Create Server. Once the server has been created, click
psqlshould be available in your
$PATH. You can also add
psqlto your path with the directions here.
psql, create a new database and a user to manage it.
migrations/00000001_initial.migration.dart, it should look like this:
_Herowith columns for
name. Before we run it, we should seed the database with some initial heroes. In the
seed()method, add the following:
heroesdatabase with the following command in the project directory:
conduit serve. Then, reload http://conduit-tutorial.conduit.dart.io. Your dashboard of heroes and detail page for each will still show up - but this time, they are sourced from a database.
seed()method, we executed SQL queries instead of using the Conduit ORM. It is very important that you do not use
ManagedObject<T>or other elements of the Conduit ORM in migration files. Migration files represent an ordered series of historical steps that describe your database schema. If you replay those steps (which is what executing a migration file does), you will end up with the same database schema every time. However, a
ManagedObject<T>subclass changes over time - the definition of a managed object is not historical, it only represents the current point in time. Since a
ManagedObject<T>subclass can change, using one in our migration file would mean that our migration file could change.
GET /heroes. For example, if you entered the text
abc, it'd make this request:
getAllHeroes()to bind the 'name' query parameter:
@Bind.query('name')annotation will bind the value of the 'name' query parameter if it is included in the request URL. Otherwise,
namewill be null.
nameis an optional parameter (it is surrounded by curly brackets). An optional parameter in an operation method is also optional in the HTTP request. If we removed the curly brackets from this binding, the 'name' query parameter would become required and the request
?name=xwould fail with a 400 Bad Request.
DateTime). For more information, see ResourceControllers.