-
Notifications
You must be signed in to change notification settings - Fork 19
Enum Attributes
Your Resource is persisted with a database that supports Enum types and your API service application supports using Enum attributes on your model, yet JavaScript does not. You would like to support storing a value that your API references as a key yet stores a human readable value for the enum.
When using the JSONAPI::Resources gem for your JSON API solution you most likely will use Rails and Active Record.
Use an enum attribute for the model class, for example in a Ruby on Rails
app you model may use:
class Game < ActiveRecord::Base
enum status: {
active: 'Active',
inactive: 'Inactive'
}
validates : status, presence: true
endYour migration may include some SQL, like so:
class CreateGames < ActiveRecord::Migration
def up
execute <<-SQL
CREATE TYPE status AS ENUM ('Active', 'Inactive');
SQL
enable_extension 'uuid-ossp' unless extension_enabled?('uuid-ossp')
create_table :games, id: :uuid, default: 'uuid_generate_v4()' do |t|
t.string :name
t.column 'status', :status
t.timestamps null: false
end
end
def down
drop_table :games
execute <<-SQL
DROP TYPE status;
SQL
end
endThe JSONAPI::Resource for serialization:
module Api
module V1
class GameResource < JSONAPI::Resource
attributes :name, :status
key_type :uuid
end
end
endNow your API will send the status attribute as snake_case and store your
enum type Capitalized - active: 'Active', inactive: 'Inactive'
Your JavaScript (Ember) application will need to use a human readable value
for the status attribute of your resource. (In this case the lowercase
value would work but imagine if the keys were snake case and the stored
values used multiple words.)
Create a Map like utility class that can lookup a value for an object. Here is an example I use:
utils/transform-map.js
import { isBlank, isType } from 'ember-jsonapi-resources/utils/is';
/**
Abstract class to transform mapped data structures
@class TransformMap
**/
export default class TransformMap {
/**
@method constructor
@param {Object} [map] created with `null` as prototype
**/
constructor(map) {
this.map = map;
this.keys = Object.keys(map);
let inverse = Object.create(null);
let values = [];
let entries = [];
let pair;
for (let key in map) {
values.push(map[key]);
inverse[map[key]] = key;
pair = Object.create(null);
entries.push([key, map[key]]);
}
Object.freeze(inverse);
this.values = values;
this.inverse = inverse;
this.entries = entries;
}
/**
@method lookup
@param {String} [value]
@parm {String} [use='keys'] keys or values
@return {String|Null} [value] name or null
*/
lookup(value, use = 'keys') {
if (isBlank(value) || value === '') {
value = null;
} else if (isType('string', value)) {
if (this[use].indexOf(value) > -1) {
if (use === 'keys') {
value = this.map[value];
} else if (use === 'values') {
value = this.inverse[value];
}
}
}
return (value) ? value : null;
}
}I like to create utility objects that I can use as constants in my app.
Here is a util for the status keys/values:
utils/constants/status
// Map of status enum values
let map = Object.create(null);
map.active = 'Active';
map.inactive = 'Inactive';
Object.freeze(map);
export default map;And use the status constant when extend the TransformMap class in
a Transform:
transforms/status
import TransformMap from 'your-app/utils/transform-map';
import STATUS_ENUM from 'your-app/utils/constants/status';
/**
@class TransformStatusAttribute
@extends TransformMap
@constructor
*/
class TransformStatusAttribute extends TransformMap {
/**
@method serialize
@param {String} [status] name
@return {String|Null} [status] name or null
*/
serialize(status) {
return this.lookup(status, 'values');
}
/**
@method deserialize
@param {String} [status] name
@return {String|Null} [status] name or null
*/
deserialize(status) {
return this.lookup(status);
}
}
let statusTransform = new TransformStatusAttribute(STATUS_ENUM);
export default statusTransform;Define a Transforms mixin to use:
import Ember from 'ember';
import statusTransform from 'pipeline-app/transforms/active-status';
/**
@class TransformsMixin
*/
export default Ember.Mixin.create({
/**
@method serializeStatusAttribute
@param {Date|String} [status]
@return {String|Null} [status] value for JSON payload, or null
*/
serializeStatusAttribute(status) {
return statusTransform.serialize(status);
},
/**
@method deserializeStatusAttribute
@param {String} [status]
@return {String|Null} [status] value from JSON payload, or null
*/
deserializeStatusAttribute(status) {
return statusTransform.deserialize(status);
}
});See the Transforms page for details on how the serializer uses transform utilities you define. The serializer expects a specific convention for method names to (de)serialize attributes.
Rather than using Transforms only for data (de)serialization, transform modules can be use to enforce custom data types in your client application like your API server does with it's database, specifically Enum types.
In this example the Rails backend communicates enum data values as strings, in snake_case, and the Ember application represents those data values as human readable in the same format that the database stores the values. There