-
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
end
Your 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
end
The JSONAPI::Resource for serialization:
module Api
module V1
class GameResource < JSONAPI::Resource
attributes :name, :status
key_type :uuid
end
end
end
Now 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:
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 dictionaries in my app.
Here is a util for the status
keys/values:
utils/dictionaries/status
To generate a dictionary use a blueprint:
ember g jsonapi-dictionary status active:Active inactive:Inactive
// Map of status enum values
const dictionary = Object.create(null);
dictionary["active"] = "Active";
dictionary["inactive"] = "Inactive";
export default Object.freeze(dictionary);
And use the status
constant when extend the TransformMap class in
a Transform:
To generate a value transform object that uses the (util) dictionary:
ember g jsonapi-transform status
transforms/status
import TransformMap from 'ember-jsonapi-resources/utils/transform-map';
import dictionary from '../utils/dictionaries/status';
class TransformStatusAttribute extends TransformMap {
deserialize(serialized) {
return this.lookup(serialized);
}
serialize(deserialized) {
return this.lookup(deserialized, 'values');
}
}
export default new TransformStatusAttribute(dictionary);
Define a Transforms mixin:
See the Transforms for an example of using the jsonapi-transform-mixin
generator.
import Ember from 'ember';
import statusTransform from 'app-name/transforms/status';
/**
@class TransformsMixin
*/
export default Ember.Mixin.create({
serializeStatusAttribute(deserialized) {
return statusTransform.serialize(deserialized);
},
deserializeStatusAttribute(serialized) {
return statusTransform.deserialize(serialized);
}
});
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 presents those data values in a human readable format; the same format that the database stores the values. There is some duplication in defining the types on both the backend and in the client application in order to provide consistency where representing the custom types.