@mortz.prk/adonis-extra-trait v1.0.2
Adonis Extra Traits
This Package provides multiple useful Traits for Adonis
Table of Contents:
Usage:
- Install package:
npm i @mortz.prk/adonis-extra-trait - Register with Adonis: add
@mortz.prk/adonis-extra-traittoprovidersarray instart/app.jsfile
Traits:
NoTimestamp
Intro:
Removes created_at and updated_at field selectively.
This trait is much like NoTimestamp trait provided by Adonis.
Register:
Register trait in your model of choice:
const Model = use('Model'); class Post extends Model { static boot() { super.boot(); this.addTrait('@provider:Prk/Traits/NoTimestamp', <option>); } }Change
option:optionRequired Type Default Description createdAt ❌ bool falseset trueif the model has nocreated_atfieldupdatedAt ❌ bool falseset trueif the model has noupdated_atfield
Singleton
Intro:
Make a Model Singleton, which means:
- You can access last inserted row
- You can't change any instance after saved in DB
This is useful when you want to keep set of values in DB and also have a history of old values. for example, You may save a configuration in DB
Register:
Register trait in your model of choice:
const Model = use('Model'); class Post extends Model { static boot() { super.boot(); this.addTrait('@provider:Prk/Traits/Singleton', <option>); } }Change
option:optionRequired Type Default Description ignoreUpdate ❌ bool falseset trueto allow updating old instances
Usage:
Model.currentwill return last inserted row from DB:const last = await Post.current;updatewill fail:const post1 = await Post.create(data1); const post2 = await Post.create(data2); post1.name = 'new Name' post1.save() // Will throw ErrorYou can disable this behavior by passing
ignoreUpdate
CachedAttribute
Intro:
Cache last saved values in Redis.
This trait requires Singleton to get registered in the model (must be registered before this one). also Primary Key of model must be an integer value.
Register:
Register trait in your model of choice:
const Model = use('Model'); class Post extends Model { static boot() { super.boot(); this.addTrait('@provider:Prk/Traits/Singleton'); this.addTrait('@provider:Prk/Traits/CachedAttribute', <option>); } }Change
option:optionRequired Type Default Description fields ✅ string[] undefined name of attributes to cache redis ❌ Redis use('Redis') redis provider Register redis custom command by Creating/Changing
config/redis.js:add
loadScriptto file:const Env = use('Env'); module.exports = { connection: 'local', local: { host: Env.get('REDIS_HOST'), port: Env.get('REDIS_PORT'), password: Env.get('REDIS_PASS'), db: 0, keyPrefix: '' }, loadScript: true // <-- add this line };Set value of
loadScriptbased on following table:loadScriptEffect nullwon't register command undefinedwon't register command trueregister command for default connection 'cn'register command for redis with given connection name ['cn1', 'cn2']register command for redis with given connection names
Usage:
Model.cachedwill return last cached attrs from Redis:// assuming `fields` for trait is set to ['name', 'isMale'] const data = {name: 'lorem', lastName: 'ipsum', isMale: false} const post = await Post.create(data); console.log(await Post.cached) // { // name:"lorem", // isMale:false // }cachedwill throw an Error if table is emptyModel.cachedNamename of key which is used as key in Redis:const Redis = use('Redis') // assuming `fields` for trait is set to ['name', 'isMale'] const data = {name: 'lorem', lastName: 'ipsum', isMale: false} const post = await Post.create(data); const cacheValue = await Redis.get(Post.cachedName); console.log(JSON.pars(cacheValue)); // { // name:"lorem", // isMale:false // }Model.warmUpGenerates cache, removing old values:- use this method to regenerate cache
- Example: to populate cache in server startup
- Example: remove old cache value when a transaction rolls back
- use this method to regenerate cache
Known Limitations:
- Redis cluster is not tested and may not work as expected
- Cache will return invalid value, if DB transaction rolls back (run
Model.warmUpto fix it for now)
FAQ:
How to use another connection for redis:
Pass
Redis.connection('nameOfConnection')to trait option:const Model = use('Model'); const Redis = use('Redis'); class Post extends Model { static boot() { super.boot(); this.addTrait('@provider:Prk/Traits/Singleton'); this.addTrait( '@provider:Prk/Traits/CachedAttribute', {fields: ['name'], redis: Redis.connection('anotherName')} ); } }I don't want to use
Redisprovider from Adonis, but anioredisinstance:- Pass ioRedis to trait option:
const Model = use('Model'); const ioRedis = require('../ioredis'); // <-- this is an ioredis instance class Post extends Model { static boot() { super.boot(); this.addTrait('@provider:Prk/Traits/Singleton'); this.addTrait( '@provider:Prk/Traits/CachedAttribute', {fields: ['name'], redis: ioRedis} ); } } - Register Command in Redis:
const redisCommandLoader = use('Prk/Helper/RedisCustomCommand'); const ioRedis = require('../ioredis'); await redisCommandLoader(ioRedis);
- Pass ioRedis to trait option:
I don't want to use neither
Redisprovider from Adonis nor anioredisinstance:- Pass Redis like object to trait option:
const Model = use('Model'); const Redis = require('another-redis-lib'); const redis = { get: (keyName) => { return Redis.getFromCacheMethod(keyName); }, set: (keyName, value) => { return Redis.setToCacheMethod(keyName, value); }, evalsha: (hash, numOfKeys, k1,k2,k3,k4) => { return Redis.methodToRunCachedScript(hash, numOfKeys, k1,k2,k3,k4); }, }; class Post extends Model { static boot() { super.boot(); this.addTrait('@provider:Prk/Traits/Singleton'); this.addTrait( '@provider:Prk/Traits/CachedAttribute', {fields: ['name'], redis: redis} ); } } - Register Command in Redis:
const redisCommandLoader = use('Prk/Helper/RedisCustomCommand'); const {command} = use('Prk/Helper/RedisCustomCommandDetail'); const Redis = require('another-redis-lib'); await redisCommandLoader({ script: (action, command) => await Redis.methodToEvaluateCommand(command) });
- Pass Redis like object to trait option:
I didn't understand logic of
loadScript.short answer:
CachedAttributetrait uses aluascript internally to cache attributes in Redis. to cache the lua script itself in Redis (for better performance) we have to run a command in Redis.If you are using default config for redis:
Which is something like this:
const Env = use('Env'); module.exports = { connection: 'local', local: { host: Env.getOrFail('REDIS_HOST'), port: Env.getOrFail('REDIS_PORT'), password: null, db: 0, keyPrefix: '' }, ... };then you just need to change file to this:
const Env = use('Env'); module.exports = { connection: 'local', local: { host: Env.getOrFail('REDIS_HOST'), port: Env.getOrFail('REDIS_PORT'), password: null, db: 0, keyPrefix: '' }, loadScript: true, ... };now, provider will automatically register command in Redis.
If you use Redis provider from Adonis and know what you are doing:
with a config file like following:
const Env = use('Env'); module.exports = { connection: 'local', local: { host: Env.getOrFail('LOCAL_REDIS_HOST'), port: Env.getOrFail('LOCAL_REDIS_PORT'), password: null, db: 0, keyPrefix: '' }, anotherLocal: { host: Env.getOrFail('ANOTHER_REDIS_HOST'), port: Env.getOrFail('ANOTHER_REDIS_PORT'), password: null, db: 0, keyPrefix: '' }, againAnotherLocal: { host: Env.getOrFail('AGAIN_ANOTHER_REDIS_HOST'), port: Env.getOrFail('AGAIN_ANOTHER_REDIS_PORT'), password: null, db: 0, keyPrefix: '' }, ... };then add
loadScriptto config, like:const Env = use('Env'); module.exports = { connection: 'local', ..., loadScript: <value>, ... };based on
<value>you have different behavior:<value>:nullorundefined-> nothing happens<value>:true-> command registers inLOCAL_REDIS_HOST<value>:'anotherLocal'-> command registers inANOTHER_REDIS_HOST<value>:['anotherLocal', 'againAnotherLocal']-> command registers inANOTHER_REDIS_HOSTandAGAIN_ANOTHER_REDIS_HOST
If you use another library to connect with redis:
import command detail from helper:
const {command,hash,numOfKeys} = use('Prk/Helper/RedisCustomCommandDetail');now you should register script using your library
const redisClient = require('another-redis-lib'); redisClient.aMethodWhichLoadsLuaScript(command);
ToDo:
- test for redis cluster
- add badges (test, version, last version of dependencies usage)
- make this a typescript package (!)
- add ci (Travis, Circle or Gitlab)