This Package provides multiple useful Traits for Adonis
- Install package:
npm i @mortz.prk/adonis-extra-trait
- Register with Adonis: add
@mortz.prk/adonis-extra-trait
toproviders
array instart/app.js
file
Removes created_at
and updated_at
field selectively.
This trait is much like NoTimestamp
trait provided by Adonis.
-
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
:option
Required Type Default Description createdAt ❌ bool false
set true
if the model has nocreated_at
fieldupdatedAt ❌ bool false
set true
if the model has noupdated_at
field
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 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
:option
Required Type Default Description ignoreUpdate ❌ bool false
set true
to allow updating old instances
-
Model.current
will return last inserted row from DB:const last = await Post.current;
-
update
will fail:const post1 = await Post.create(data1); const post2 = await Post.create(data2); post1.name = 'new Name' post1.save() // Will throw Error
You can disable this behavior by passing
ignoreUpdate
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 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
:option
Required 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
loadScript
to 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
loadScript
based on following table:loadScript
Effect null
won't register command undefined
won't register command true
register command for default connection 'cn'
register command for redis with given connection name ['cn1', 'cn2']
register command for redis with given connection names
-
-
Model.cached
will 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 // }
cached
will throw an Error if table is empty -
Model.cachedName
name 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.warmUp
Generates 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
- Redis cluster is not tested and may not work as expected
- Cache will return invalid value, if DB transaction rolls back (run
Model.warmUp
to fix it for now)
-
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
Redis
provider from Adonis, but anioredis
instance:- 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
Redis
provider from Adonis nor anioredis
instance:- 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:
CachedAttribute
trait uses alua
script 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
loadScript
to config, like:const Env = use('Env'); module.exports = { connection: 'local', ..., loadScript: <value>, ... };
based on
<value>
you have different behavior:<value>
:null
orundefined
-> nothing happens<value>
:true
-> command registers inLOCAL_REDIS_HOST
<value>
:'anotherLocal'
-> command registers inANOTHER_REDIS_HOST
<value>
:['anotherLocal', 'againAnotherLocal']
-> command registers inANOTHER_REDIS_HOST
andAGAIN_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);
-
- test for redis cluster
- add badges (test, version, last version of dependencies usage)
- make this a typescript package (!)
- add ci (Travis, Circle or Gitlab)