#23 Using A Fluent Interface To Validate The Vuex Getters
Saturday, November 9, 2019
In this article we will start by adding a couple of new functions that we can use to find individual items within our store by their id as well as all of the items in a particular state. These new functions will just make finding items within our vuex store just a little bit cleaner. We will then turn our attention to creating a new class, that implements a fluent interface, that we can use to validate the actions that we perform on our store. In particular we will see how to throw an error if we attempt to use a vuex getter to retrieve an item by id and the result of that retrieval is undefined.
Parts
- Part 45: Adjusting Shares
- Part 44: Plan Percentages
- Part 43: Home Securities
- Part 42: Updating Plans
- Part 41: Plan Details View
- Part 40: Portfolio Getters
- Part 39: Portfolio Plan
- Part 38: Portfolio Category
- Part 37: Account Securities
- Part 36: Account Transfer
- Part 35: View Account Security
- Part 34: Updating Deposit
- Part 33: View Account Deposit
- Part 32: Display Account Details
- Part 31: Account Getters
- Part 30: Deposits And Securities
- Part 29: Add Accounts Details
- Part 28: Refactoring Accounts
- Part 27: Add Security Models
- Part 26: Edit Security Details
- Part 25: View Security Details
- Part 24: Navigating To Details
- Part 23: Getters Validation
- Part 22: Query Parameters
- Part 21: Tab Entries
- Part 20: Tab Header
- Part 19: List View
- Part 18: Vuex Getters
- Part 17: End Domain Model
- Part 16: Start Domain Model
- Part 15: Pop Routes
- Part 14: Push Routes
- Part 13: Removing Accounts
- Part 12: Vuex (Decorators)
- Part 11: Vuex (Accounts)
- Part 10: The App Bar (Settings)
- Part 9: Remove Consumer Rxjs
- Part 8: The App Bar (Back)
- Part 7: Structuring Our App
- Part 6: Animation Between Views
- Part 5: Navigation Fade
- Part 4: Navigation Requests
- Part 3: Fade Animations (cont.)
- Part 2: Fade Animations
- Part 1: Splash Screen
Code Snippets
functions.ts (2:10)
interface IStateItem {
id: number;
}
interface IState<T extends IStateItem> {
index: number;
items: T[];
}
export function findAll<T extends IStateItem>(state: IState<T>) {
return state.items;
}
export function findById<T extends IStateItem>(state: IState<T>, id: number) {
return state.items.find((x) => x.id === id);
}
store-constants.ts (4:14)
...
export const GETTER_SECURITY_MARKET = "GETTER_SECURITY_MARKET";
export const GETTER_SECURITY_MARKETS = "GETTER_SECURITY_MARKETS";
export const GETTER_SECURITY_SEGMENT = "GETTER_SECURITY_SEGMENT";
export const GETTER_SECURITY_SEGMENTS = "GETTER_SECURITY_SEGMENTS";
export const GETTER_SECURITY_TERRITORIES = "GETTER_SECURITY_TERRITORIES";
export const GETTER_SECURITY_TERRITORY = "GETTER_SECURITY_TERRITORY";
export const GETTER_SECURITY_TYPE = "GETTER_SECURITY_TYPE";
export const GETTER_SECURITY_TYPES = "GETTER_SECURITY_TYPES";
...
security-types.ts (4:47)
import {
...
GETTER_SECURITY_MARKET,
GETTER_SECURITY_MARKETS,
GETTER_SECURITY_SEGMENT,
GETTER_SECURITY_SEGMENTS,
GETTER_SECURITY_TERRITORIES,
GETTER_SECURITY_TERRITORY,
GETTER_SECURITY_TYPE,
GETTER_SECURITY_TYPES,
...
} from "@/store/store-constants";
...
export type GetterCategory = (id: number) => SecurityCategoryModel;
export type GetterMarket = (id: number) => SecurityMarketModel;
export type GetterSecurity = (id: number) => SecurityModel;
export type GetterSegment = (id: number) => SecuritySegmentModel;
export type GetterTerritory = (id: number) => SecurityTerritoryModel;
export type GetterType = (id: number) => SecurityTypeModel;
...
export interface ISecurityGetters {
[GETTER_SECURITIES]: SecurityModel[];
[GETTER_SECURITY]: GetterSecurity;
[GETTER_SECURITY_CATEGORIES]: SecurityCategoryModel[];
[GETTER_SECURITY_CATEGORY]: GetterCategory;
[GETTER_SECURITY_MARKET]: GetterMarket;
[GETTER_SECURITY_MARKETS]: SecurityMarketModel[];
[GETTER_SECURITY_SEGMENT]: GetterSegment;
[GETTER_SECURITY_SEGMENTS]: SecuritySegmentModel[];
[GETTER_SECURITY_TERRITORIES]: SecurityTerritoryModel[];
[GETTER_SECURITY_TERRITORY]: GetterTerritory;
[GETTER_SECURITY_TYPE]: GetterType;
[GETTER_SECURITY_TYPES]: SecurityTypeModel[];
}
...
security-module.ts (6:06)
import {
...
GETTER_SECURITY_MARKET,
GETTER_SECURITY_MARKETS,
GETTER_SECURITY_SEGMENT,
GETTER_SECURITY_SEGMENTS,
GETTER_SECURITY_TERRITORIES,
GETTER_SECURITY_TERRITORY,
GETTER_SECURITY_TYPE,
GETTER_SECURITY_TYPES,
...
} from "@/store/store-constants";
import {
findAll,
findById,
} from "@/store/functions";
...
export const securitiesGetters: StoreGetterTree = {
...
[GETTER_SECURITY]: (state: IStoreState, getters: ISecurityGetters) => {
return (id: number) => {
const security = findById(state[STATE_SECURITIES], id);
const category = getters[GETTER_SECURITY_CATEGORY](security!.categoryId);
const market = getters[GETTER_SECURITY_MARKET](security!.marketId);
return security!
.setCategory(category)
.setMarket(market);
};
},
...
[GETTER_SECURITY_CATEGORY]: (state: IStoreState, getters: ISecurityGetters) => {
return (id: number) => {
const category = findById(state[STATE_SECURITY_CATEGORIES], id);
const segment = getters[GETTER_SECURITY_SEGMENT](category!.segmentId);
const territory = getters[GETTER_SECURITY_TERRITORY](category!.territoryId);
const type = getters[GETTER_SECURITY_TYPE](category!.typeId);
return category!
.setSegment(segment!)
.setTerritory(territory!)
.setType(type!);
};
},
[GETTER_SECURITY_MARKET]: (state: IStoreState) => {
return (id: number) => {
const market = findById(state[STATE_SECURITY_MARKETS], id);
return market;
};
},
[GETTER_SECURITY_MARKETS]: (state: IStoreState) => {
return findAll(state[STATE_SECURITY_MARKETS]);
},
[GETTER_SECURITY_SEGMENT]: (state: IStoreState) => {
return (id: number) => {
const segment = findById(state[STATE_SECURITY_SEGMENTS], id);
return segment;
};
},
[GETTER_SECURITY_SEGMENTS]: (state: IStoreState) => {
return findAll(state[STATE_SECURITY_SEGMENTS]);
},
[GETTER_SECURITY_TERRITORIES]: (state: IStoreState) => {
return findAll(state[STATE_SECURITY_TERRITORIES]);
},
[GETTER_SECURITY_TERRITORY]: (state: IStoreState) => {
return (id: number) => {
const territory = findById(state[STATE_SECURITY_TERRITORIES], id);
return territory;
};
},
[GETTER_SECURITY_TYPE]: (state: IStoreState) => {
return (id: number) => {
const type = findById(state[STATE_SECURITY_TYPES], id);
return type;
};
},
[GETTER_SECURITY_TYPES]: (state: IStoreState) => {
return findAll(state[STATE_SECURITY_TYPES]);
},
};
...
Advertisement
Securities.vue (10:36)
<script lang="tsx">
...
import {
...
GETTER_SECURITY_MARKETS,
GETTER_SECURITY_SEGMENTS,
GETTER_SECURITY_TERRITORIES,
GETTER_SECURITY_TYPES,
...
SecurityMarketModel,
...
SecuritySegmentModel,
SecurityTerritoryModel,
SecurityTypeModel,
...
} from "@/store";
...
export default class Securities extends Vue {
@Getter(GETTER_SECURITY_CATEGORIES) private readonly categories!: SecurityCategoryModel[];
@Getter(GETTER_SECURITY_MARKETS) private readonly markets!: SecurityMarketModel[];
@Getter(GETTER_SECURITIES) private readonly securities!: SecurityModel[];
@Getter(GETTER_SECURITY_SEGMENTS) private readonly segments!: SecuritySegmentModel[];
@Getter(GETTER_SECURITY_TERRITORIES) private readonly territories!: SecurityTerritoryModel[];
@Getter(GETTER_SECURITY_TYPES) private readonly types!: SecurityTypeModel[];
...
}
</script>
store-action-validator.ts (12:19)
export enum StoreActions {
None,
Creating,
Getting,
Reading,
Updating,
Deleting,
}
type ItemType = object | undefined;
interface IItem {
error: string;
message: string;
}
interface IStoreActionValidatorA {
begin: () => IStoreActionValidatorB;
}
interface IStoreActionValidatorB {
while: (action: StoreActions) => IStoreActionValidatorC;
}
interface IStoreActionValidatorC {
throwIf: (item: ItemType) => IStoreActionValidatorD;
}
interface IStoreActionValidatorD {
isUndefined: (message: string) => IStoreActionValidatorB;
}
export class StoreActionValidator implements IStoreActionValidatorA, IStoreActionValidatorB,
IStoreActionValidatorC, IStoreActionValidatorD {
private _action = StoreActions.None;
private _item: ItemType | null = null;
public begin = (): IStoreActionValidatorB => {
return this;
}
public isUndefined = (message: string): IStoreActionValidatorB => {
if (this._item === null) {
throw new Error("You must specify the item.");
}
const item: IItem = {
error: "undefined",
message,
};
if (typeof(this._item) === "undefined") {
this.failureResponse(item);
}
return this;
}
public throwIf = (item: ItemType): IStoreActionValidatorD => {
this._item = item;
this.failureResponse = this.throw;
return this;
}
public while = (action: StoreActions): IStoreActionValidatorC => {
this._action = action;
return this;
}
private failureResponse: (item: IItem) => void = () => { return; };
private failureMessage = (item: IItem) => {
const action = StoreActions[this._action];
return `Failed [${action}] ${item.message}. The result of the operation was <${item.error}>.`;
}
private throw = (item: IItem) => {
throw new Error(this.failureMessage(item));
}
}
// const storeActionValidator = new StoreActionValidator();
// storeActionValidator
// .begin()
// .while(StoreActions.Getting)
// .throwIf(security)
// .isUndefined(undefinedMessage("security", id, state[STATE_SECURITIES].index));
functions.ts (23:33)
...
export function undefinedMessage(entityName: string, id: number, stateIndex: number) {
return id < stateIndex
? `${entityName} with id ${id}`
: `${entityName} with id ${id}. The id is greater than or equal to the state index of ${stateIndex}`;
}
security-module.ts (24:16)
import {
StoreActions,
StoreActionValidator,
} from "@/store/store-action-validator";
import {
...
undefinedMessage,
} from "@/store/functions";
...
const storeActionValidator = new StoreActionValidator();
export const securitiesGetters: StoreGetterTree = {
[GETTER_SECURITIES]: (state: IStoreState, getters: ISecurityGetters) => {
return state[STATE_SECURITIES].items.map((x) => getters[GETTER_SECURITY](x.id));
},
[GETTER_SECURITY]: (state: IStoreState, getters: ISecurityGetters) => {
return (id: number) => {
...
storeActionValidator
.begin()
.while(StoreActions.Getting)
.throwIf(security)
.isUndefined(undefinedMessage("security", id, state[STATE_SECURITIES].index));
...
};
},
[GETTER_SECURITY_CATEGORIES]: (state: IStoreState, getters: ISecurityGetters) => {
return state[STATE_SECURITY_CATEGORIES].items.map((x) => getters[GETTER_SECURITY_CATEGORY](x.id));
},
[GETTER_SECURITY_CATEGORY]: (state: IStoreState, getters: ISecurityGetters) => {
return (id: number) => {
...
storeActionValidator
.begin()
.while(StoreActions.Getting)
.throwIf(category)
.isUndefined(undefinedMessage("category", id, state[STATE_SECURITY_CATEGORIES].index));
...
};
},
[GETTER_SECURITY_MARKET]: (state: IStoreState) => {
return (id: number) => {
...
storeActionValidator
.begin()
.while(StoreActions.Getting)
.throwIf(market)
.isUndefined(undefinedMessage("market", id, state[STATE_SECURITY_MARKETS].index));
...
};
},
[GETTER_SECURITY_MARKETS]: (state: IStoreState) => {
return findAll(state[STATE_SECURITY_MARKETS]);
},
[GETTER_SECURITY_SEGMENT]: (state: IStoreState) => {
return (id: number) => {
...
storeActionValidator
.begin()
.while(StoreActions.Getting)
.throwIf(segment)
.isUndefined(undefinedMessage("segment", id, state[STATE_SECURITY_SEGMENTS].index));
...
};
},
[GETTER_SECURITY_SEGMENTS]: (state: IStoreState) => {
return findAll(state[STATE_SECURITY_SEGMENTS]);
},
[GETTER_SECURITY_TERRITORIES]: (state: IStoreState) => {
return findAll(state[STATE_SECURITY_TERRITORIES]);
},
[GETTER_SECURITY_TERRITORY]: (state: IStoreState) => {
return (id: number) => {
...
storeActionValidator
.begin()
.while(StoreActions.Getting)
.throwIf(territory)
.isUndefined(undefinedMessage("territory", id, state[STATE_SECURITY_TERRITORIES].index));
...
};
},
[GETTER_SECURITY_TYPE]: (state: IStoreState) => {
return (id: number) => {
...
storeActionValidator
.begin()
.while(StoreActions.Getting)
.throwIf(type)
.isUndefined(undefinedMessage("type", id, state[STATE_SECURITY_TYPES].index));
...
};
},
[GETTER_SECURITY_TYPES]: (state: IStoreState) => {
return findAll(state[STATE_SECURITY_TYPES]);
},
};
Exciton Interactive LLC