Tutorial Build a Adonisjs Package

in utopian-io •  7 years ago  (edited)

Repository

[https://github.com/adonisjs](AdonisJS Repository)

What Will I Learn?

At the end of this tutorial, we should have a fully functional Adonis package. We'll cover the following concepts.

  • Setting Up Package Specific Data Management.
  • Writing to The Filing System through NPM.
  • Writing Tests For Our Package with Japa.

Requirements

Difficulty

  • Intermediate

Let's Get Straight To Work.

If we head back into our src/Sophos.js, we begin our class with the following lines:

class Sophos {
sourceURL = 'https://wisdomapi.herokuapp.com/v1/';
}

Not good. Our resource endpoint is not flexible. We can do better by making this configurable by our end user. To do this, we'll add an array of objects to our configuration file which we'll be using to allow us switch resource endpoints effortlessly.

Heading back into config/index.js, let's make some changes; we'll change the sourceURL string property to a sourceURLs object to help us accommodate a wider range of options.

'use strict'

/*
|--------------------------------------------------------------------------

Sophos
Sophos returns bits of inspiration for your next big design and startup ideas.

*/

module.exports = {
/*
|--------------------------------------------------------------------------

Source URLs
The URLs of various resources queried for data.

*/
sourceURLs: {
'startups': 'https://wisdomapi.herokuapp.com/v1/random',
'design': 'http://quotesondesign.com/wp-json/posts?filter[orderby]=rand&filter[posts_per_page]=1'
},
}

We added two key-value pairs to our sourceURLs map for resources mapping to various quote categories. Next, we simply need to modify our Sophos class to accommodate the changes.

/**

  • The Sophos class makes a request to a url returning a promise

  • resolved with data or rejected with an error.

  • @class Sophos

  • @param {Object} Config
    */
    class Sophos {
    sourceURLs = {};

    constructor (Config) {
    this.config = Config.merge('sophos', {
    sourceURLs: this.sourceURLs
    })
    }

    getQuotes (category = 'startups') {
    return new Promise((resolve, reject) => {
    if (!this.config.sourceURLs.hasOwnProperty(category)) {
    reject({
    error: Sorry, Sophos does not support requests for "${category}" at the moment.
    })
    }

        let endpoint = `${this.config.sourceURLs[category]}`
        
        request(endpoint, { json: true }, (err, res, body) => {
          if (err) return reject(err);
          return resolve(body)
        });
    });
    

    }
    }

Great, we've just added some more custom options to our code. Our package is now capable of fetching quotes on design or startups from two different URLs. We also added a check to make sure our users request a supported category. If our check fails, we reject the promise and return a little error message to our user.

Writing to the Filing System Using the CLI Helper.

We'll be adding some functionality that requires data management practices. We'd like users to be able to be able to store a collection of favorite quotes simply by providing a user_id and the URL of the resource of interest. To do this, we'd need to publish migration files to the database/migrations folder of the Adonis application leveraging this package. Let's create our templates/SophosQuoteSchema.js migration template.

'use strict'

const Schema = use('Schema')

class SophosQuoteSchema extends Schema {
up () {
this.create('sophos_quotes', table => {
table.increments()
table.integer('user_id').notNullable();
table.string('quote_url', 80).notNullable()
table.timestamps()
})
}

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

module.exports = SophosQuoteSchema

This migration template will be published to database/migration at installation. We are creating tables with the sophos namespace to prevent namespace collisions between our package migrations and other third party package migrations. This is a pretty good time to add an accompanying Lucid model for our SophosQuotes schema. Let's create templates/SophosQuote.js and add some code.

'use strict'

const Model = use('Model')

class SophosQuote extends Model {

/**

  • A relationship on users is required as a means of identification.
  • @method user
  • @return {Object}
    */
    user () {
    return this.belongsTo('App/Models/User')
    }
    }

module.exports = SophosQuote

We'll update instructions.js to copy our migrations and models at runtime. Open up instructions.js and modify as below.

'use strict'

const path = require('path')

async function copyQuoteMigration (cli) {
try {
const migrationsFile = cli.helpers.migrationsPath(${new Date().getTime()}_sophos_quote.js)
await cli.copy(
path.join(__dirname, 'templates', 'SophosQuoteSchema.js'),
path.join(migrationsFile)
)
cli.command.completed('create', migrationsFile.replace(cli.helpers.appRoot(), '').replace(path.sep, ''))
} catch (error) {
// ignore error
}
}

async function copyQuoteModel (cli) {
try {
await cli.copy(
path.join(__dirname, 'templates', 'SophosQuote.js'),
path.join(cli.appDir, 'Models/SophosQuote.js')
)
cli.command.completed('create', 'Models/SophosQuote.js')
} catch (error) {
// ignore error
}
}

async function makeConfigFile (cli) {
try {
const inFile = path.join(__dirname, './config', 'index.js')
const outFile = path.join(cli.helpers.configPath(), 'sophos.js')
await cli.copy(inFile, outFile)
cli.command.completed('create', 'config/sophos.js')
} catch (error) {
// ignore error
}
}

module.exports = {
await makeConfigFile(cli)
await copyQuoteModel(cli)
await copyQuoteMigration(cli)
}

Comparing our updated instructions.js module to our previous instructions.js module, you'll notice we abstracted our code into a series of async functions. We then export these asynchronous functions in module.exports.

Setting Up Package Specific Data Management

We'd love to provide a means for our users to be able to save their favorite quotes for ease of use at a later date. To do this, we must add a model string property to our config specifying the proper model we'll be using to save this information. Its important to note that, when you're developing packages, you should prioritize ease of customization above every other criteria. We'll add this setting to our config/index.js file.

module.exports = {
/*
|--------------------------------------------------------------------------

Model
The model to be used for Sophos' quotes

*/
model: 'App/Models/SophosQuote',

}

Next, we'll write a bit of functionality to the src/Sophos class. We'll add the asynchronous saveQuote method.

/*
* Saves a quote for referencing later.
*/
async saveQuote (attributes = null) {
if (!attributes || typeof attributes !== 'object') throw('Required argument "attributes" was not provided.');
const quote = await this._getModel().create(await)
}

If the method fails to receive any attributes as an argument or if the attributes supplied is not an object, we throw an error and keep it movin'. Next, we call the this._getModel method that we define below.

_getModel () {
return use(this.config.model);
}

Next, we need to provide a method that allows us to return the saved quotes (or quote, as the case may be). We'll call this method getSavedQuotes and define it below.

/*
* Returns all saved quotes.
* If an id param is provided, returns a single quote.
*/
async getSavedQuotes (id = nulll) {
let self = this;
return new Promise((resolve, reject) => {
if (id) {
return resolve(
await self._getModel()
.query()
.where('id', id)
.first()
)
}

        // return all saved quotes.
        return await self._getModel().query();
    });
}</code>

Here, we'll return a single Quote object if an id is provided and an array of Quote objects otherwise. We're doing great! Now all we need to do is write some tests for our code.

Writing Tests For Our Package with Japa.

A good package is properly tested. We'd like to write tests for our package and we can do this with the help of the Japa NPM module. Let's start by creating a file called japaFile.js. It will help us run our tests.

'use strict'

const cli = require('japa/cli')
cli.run('test/*/.spec.js')

Next, we'll be writing a couple of tests. We'll be testing the save and retrieve quote functionality which we just wrote. Let's add some assertions and code.

'use strict'

/*

  • sophos
  • (c) Caleb Mathew [email protected]
  • For the full copyright and license information, please view the LICENSE
  • file that was distributed with this source code.
    */

const test = require('japa')
const { Config } = require('@adonisjs/sink')
const Sophos = require('../src/Sophos')

test.group('Sophos', () => {
test('should save a quote to the database', async (assert) => {
const data = {
user_id: 1,
quote_url: 'https://quotesondesign.com/apple-commercial/'
};

  let result = null;

  const Sophos = new Sophos()

  Sophos.saveQuote(data).then(quote => {
      result = quote
  }).catch(err => console.log(err))

  assert.equal(result.user_id, data.user_id)
  assert.equal(result.quote_url, data.quote_url)
})

test('should retrieve quote #1', async (assert) => {
  const id = 1;

  let result = null;

  const Sophos = new Sophos()

  Sophos.getQuotes(data).then(quote => {
      result = quote
  }).catch(err => console.log(err))

  assert.equal(result.id, id)
})

})

Here, we are writing a couple of assertions that utilize our Sophos class. We are making sure the data passed to our class methods return expected values.

Conclusion

We've covered some pretty important concepts in this tutorial. We examined a different approach to providing a configurable package. We also setup package specific data management and we wrote tests for our package. Finally, we published our package to the NPM registry available at https://www.npmjs.com/package/adonis-sophos

Resources

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Steem Flag Rewards mention comment has been approved! Thank you for reporting this abuse, @flugschwein categorized as plagiarism. This post was submitted via our Discord Community channel. Check us out on the following link!
SFR Discord

Unfortunately your prior temporary ban did not yield the proper results, and you are still copying content from the web.
"Previously, we added functionality that allowed us to fetch an inspirational random quote concerning startups from the ..." , seriously?
You have been permanently banned from receiving Utopian reviews.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Hello @mcfarhat I do not take the content on the web,, do you have any proof before you permanently ban me?