Wednesday, 10 January, 2018 UTC


Summary

Depending on the complexity of an application, it can be a hassle to write the SQL queries that retrieve data from a database in realtime; more often, developers would rather use an ORM for database transactions than structure long chains of SQL queries.
Object-relational mapping (ORM) in computer science is a programming technique for converting data between incompatible type systems using object-oriented programming languages. This creates, in effect, a “virtual object database” that can be used from within the programming language. – Wikipedia
Several MVC frameworks already come with ORMs that allow database querying using Models and powerful query builders; they make it easier to manipulate the data of a given database.
What Is Lucid?
Adonis ships with an ORM called Lucid. It serves as an interface for communication between an application’s Models and the database in use. Lucid implements Active record which enables it to manipulate and store object attributes and data in relational databases.
Active record is an architectural pattern found in software that stores in-memory object data in relational databases.
Source: Wikipedia
This means each Model in an Adonis application is mapped to a corresponding database table and can manipulate SQL data using readable chains of JavaScript methods.
For example, if we wanted to retrieve all the posts in a blog application, we simply have to write:
    const posts = await Post.all()
It is worthy of note that Lucid works with the Active record convention for naming; this implies that if a Model is called Post, it will automatically be mapped to a posts table in the database or else otherwise stated
If you want your model to use a database table with a different name you can configure it to do so.
Lucid is very powerful and has a lot of benefits but, in this article, we will consider how to create and effectively use database relationships among tables.
Requirements
To follow along with this tutorial, you’ll need to have these installed:
  • Node.js 8.0 or higher
  • Npm 3.0 or higher
  • Adonis CLI (This command npm i -g adonis-cli )
  • A database module driver installed using NPM. (Adonis supports a number of databases and we’ll be using MySQL)
In this tutorial, to demonstrate Lucid ORM, we will create a simple application that represents the relationships between a public garage, drivers and their cars.
Every day, a public garage has many cars parked inside it. Each of these cars belong to a driver, so a garage ultimately has many cars in it because of the drivers.
It’s also right to say that one garage has many drivers, hence many drivers belong to a garage.
A driver has one car and one car belongs to a driver. Lastly, a garage has many cars through its drivers. See diagram below.
We will be working with a garage, driver and car model through out this article. These three models will interact with one another over the database relationships created with Lucid ORM.
When you have the requirements stated above, we’ll jump right in.
Creating Models
First, we create a new Adonis application using the adonis CLI:
    adonis new relationships
Next, we’ll create the models using the adonis CLI. Run the commands below in the directory of your Adonis project:
    adonis make:model Garage
    adonis make:model Driver
    adonis make:model Car
Each of these commands creates a new file in the app/Model directory. Let’s add some code to the newly created files to define relationships. The migration files will be created in the next section.
In the Garage model, we will add two methods:
  • the first method will be called drivers and it will retrieve all the drivers associated with a garage.
  • the other method will be called cars and it will retrieve all the cars associated with a garage through drivers.
    const Model = use('Model')

    class Garage extends Model {

      drivers () {
        return this.hasMany('App/Models/Driver')
      }

      cars () {
        return this.manyThrough('App/Models/Driver', 'car')
      }
    }

    module.exports = Garage
In the Driver model, we’ll add two methods:
  • the first method called garage will return the garage that a particular driver belongs to.
  • the second method called cars will return the car owned by that driver.
    const Model = use('Model')

    class Driver extends Model {

      garage () {
        return this.belongsTo('App/Models/Garage')
      }

      car () {
        return this.hasOne('App/Models/Car')
      }
    }

    module.exports = Driver
Lastly, in the Car model, we’ll add a single method called driver, which will return the driver object a car belongs to.
    const Model = use('Model')

    class Car extends Model {

      driver () {
        return this.belongsTo('App/Models/Driver')
      }
    }

    module.exports = Car
Database Migrations
We will use database migrations to create the database tables for this tutorial. When we run a migration on a database, the tables represented in the migration file are created, altered or dropped within the database in use.
A database migration is a programmatic representation or blueprint for the relational database schemas.
To enable the relationship methods we defined in the models, we have to include foreign keys to the migration files. The foreign keys we will include are :
garage_id on the drivers table (Primary key is id).
driver_id on the cars table (Primary key is id).
Run the commands below to create the migration files:
    adonis make:migration garages
    adonis make:migration drivers
    adonis make:migration cars
Running each of these commands will prompt you to choose an action. From this prompt, choose Create table option and a new migration file will be included within the database/migrations directory.
We will focus on updating the up() method in the migration files because it holds the table structure definitions.
    // Garage Model Migration
    const Schema = use('Schema')

    class GaragesSchema extends Schema {
      up () {
        this.create('garages', (table) => {
          table.increments() //primary key id
          table.string('name', 80).notNullable().unique() // unique name
          table.timestamps() // created_at, updated_at time stamps
        })
      }

      down () {
        this.drop('garages')
      }
    }

    module.exports = GaragesSchema



    // Driver Model Migration
    'use strict'

    const Schema = use('Schema')

    class DriversSchema extends Schema {

    up () {
        this.create('drivers', (table) => {
          table.increments() //primary key id
          table.string('name', 80).notNullable().unique() // unique name
          table.integer('garage_id',11).unsigned().references('id').inTable('garages')  
          table.timestamps()
        })
      }

      down () {
        this.drop('drivers')
      }
    }

    module.exports = DriversSchema


    // Car Model Migration
    'use strict'

    const Schema = use('Schema')

    class CarsSchema extends Schema {

    up () {
        this.create('cars', (table) => {
          table.increments() //primary key id
          table.string('brand', 80).notNullable().unique() // unique name
          table.integer('driver_id',11).unsigned().references('id').inTable('drivers') // foreign key to driver table's id      
          table.timestamps() // created_at, updated_at time stamps
        })
      }

      down () {
        this.drop('cars')
      }
    }

    module.exports = CarsSchema
Finally, we run the migration with this single command:
    adonis migration:run
Seeding the Database
We need data in our database tables to test the application. While database migrations help us build the structure of the tables, database seeding inputs data in our database.
To create a database seed file, we run the following command:
    adonis make:seed
This will create a new directory called seeds and a DatabaseSeeder.js file within it.
We will use Factories to seed our tables, by creating blueprints for our models in the factory.js file in the database directory.
Consider the factory.js file as the place where we manufacture the dummy data to be inserted into the database.
In the factory.js file, we’ll add
    const Factory = use('Factory')

    Factory.blueprint('App/Models/Garage', (faker) => {
      return {
        name: faker.first()
      }
    })

    Factory.blueprint('App/Models/Driver', (faker) => {
      return {
        name: faker.first()
      }
    })

    Factory.blueprint('App/Models/Car', (faker) => {
      return {
        brand: faker.first()
      }
    })
This defines the blueprint for each model. We have also included the faker object, which will generate random data for us whenever we seed our database, e.g. The garage will have a new name on each run. We also asked faker to generate the brand name for each car for us; faker will assign a random human name (sorry, Honda and Jaguar lovers), as the brand name each time.
In the seeds/DatabaseSeeder.js file, we will instruct adonis to seed the database using the blueprint defined in the factory.js file:
    const Factory = use('Factory')
    const Garage = use('App/Models/Garage')
    const Driver = use('App/Models/Driver')
    const Car = use('App/Models/Car')

    class DatabaseSeeder {
      async run () {

    // create a Garage_1 variable and assign the first `Garage` model to it

            await Factory.model('App/Models/Garage').create()
            const Garage_1 = await Garage.find(1)

    // use the `make` keyword to make new instances of `drivers` but do not persist them

            const driver_model_1 = await Factory.model('App/Models/Driver').make()
            const driver_model_2 = await Factory.model('App/Models/Driver').make()
            const driver_model_3 = await Factory.model('App/Models/Driver').make()
            const driver_model_4 = await Factory.model('App/Models/Driver').make()
            const driver_model_5 = await Factory.model('App/Models/Driver').make()

    // Use the Garage_1 model to persist each of the `driver` models we made

            await Garage_1.drivers().save(driver_model_1)
            await Garage_1.drivers().save(driver_model_2)
            await Garage_1.drivers().save(driver_model_3)
            await Garage_1.drivers().save(driver_model_4)
            await Garage_1.drivers().save(driver_model_5)

    // create 5 Driver_* variables and assign the newly created Driver models to them

            const Driver_1 = await Driver.find(1)
            const Driver_2 = await Driver.find(2)
            const Driver_3 = await Driver.find(3)
            const Driver_4 = await Driver.find(4)
            const Driver_5 = await Driver.find(5)

    // use the `make` keyword to make new cars but do not persist them

            const car_model_1 = await Factory.model('App/Models/Car').make()
            const car_model_2 = await Factory.model('App/Models/Car').make()
            const car_model_3 = await Factory.model('App/Models/Car').make()
            const car_model_4 = await Factory.model('App/Models/Car').make()
            const car_model_5 = await Factory.model('App/Models/Car').make()

    // Use the Driver models to persist each of the car model we made

            await Driver_1.car().save(car_model_1)
            await Driver_2.car().save(car_model_2)
            await Driver_3.car().save(car_model_3)
            await Driver_4.car().save(car_model_4)
            await Driver_5.car().save(car_model_5)

      }
    }

    module.exports = DatabaseSeeder
The DatabaseSeeder.js file, when run, will;
– Create a new Garage model.
– Make 5 instances of the Driver model without persisting them.
– Save and associate all the Driver models with the single Garage model.
– Make 5 instances of the Car model but will not persist them.
– Saves and associates each of the Car model with a unique Driver model.
To run the database seeder, we use this command on the terminal:
    adonis seed
Here’s a sample view of the newly inserted rows:
Retrieving Data Using Model Relationships
Now we can explore the capabilities of the Lucid relationships we set up in the model files. We’ll see how easy it is to retrieve data using the methods we defined.
To retrieve relation Models, we will write define routes in the routes.js file so they all accept an id parameter in the URL. The routes will further find an instance of a Model in the database (using the id) and return its matching relations in JSON format.
    // start/routes.js
    const Route  = use('Route')
    const Car    = use('App/Models/Car')
    const Garage = use('App/Models/Garage')
    const Driver = use('App/Models/Driver')

    // Retrieve all drivers associated with the `id` garage
    Route.get('garage/:id/drivers', async ({ params }) => {
        const garage = await Garage.find(params.id)
        return garage.drivers()
    })

    // Retrieve all cars associated with the `id` garage
    Route.get('garage/:id/cars', async ({ params }) => {
       const garage = await Garage.find(params.id)
       return garage.cars()
    })

    // Retrieves all garages associated with the `id` driver
    Route.get('/driver/:id/garage', async ({ params }) => {
       const driver = await Driver.find(params.id)
       return driver.garage()
    })

    // Retrieves a single instance of a car Model associated with the `id` driver.
    Route.get('driver/:id/car', async ({ params }) => {
       const driver = await Driver.find(params.id)
       return driver.car()
    })

    // Retrieves a single instance of a driver Model associated with the `id` car
    Route.get('car/:id/driver', async ({ params }) => {
       const car = await Car.find(params.id)
       return car.driver()
    })
The above code illustrates how you can retrieve results using the Lucid ORM. Here is a screenshot of a sample response from Postman:
Conclusion
With just a few examples, we have seen some of the abilities of the Lucid ORM, though we have barely scratched the surface of its powers. To learn more about Lucid relationships, check out the official documentation here.
If you have questions or feedback, leave them at the comment section below. The code used in this tutorial is also available on GitHub.
The post Introducing the Lucid ORM appeared first on Pusher Blog.