From 7c048a3f95cc10aedf92c3267b1930709c93545c Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 12 May 2021 16:59:24 -0500 Subject: [PATCH 1/2] version bump --- box.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/box.json b/box.json index 5421861..27107b9 100644 --- a/box.json +++ b/box.json @@ -1,6 +1,6 @@ { "name":"Mementifier : The State Maker!", - "version":"2.6.0", + "version":"2.7.0", "location":"https://downloads.ortussolutions.com/ortussolutions/coldbox-modules/mementifier/@build.version@/mementifier-@build.version@.zip", "author":"Ortus Solutions, Corp", "homepage":"https://github.com/coldbox-modules/mementifier", From 53ff6eaf6ea6573c4ebb51845796b72e765def5d Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 7 Jun 2021 17:12:57 -0500 Subject: [PATCH 2/2] New mementifier `profiles`. You can now create multiple output profiles in your `this.memento.profiles` which can be used to mementify your object graph. --- changelog.md | 9 ++- interceptors/Mementifier.cfc | 130 ++++++++++++++++------------------- readme.md | 110 ++++++++++++++++++++++++++++- 3 files changed, 176 insertions(+), 73 deletions(-) diff --git a/changelog.md b/changelog.md index 9edb331..3e37f7c 100644 --- a/changelog.md +++ b/changelog.md @@ -5,10 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +---- + +## [2.7.0] => 2021-JUN-07 + +### Added + +* New mementifier `profiles`. You can now create multiple output profiles in your `this.memento.profiles` which can be used to mementify your object graph. ---- -## [2.5.0] => 2021-MAY-12 +## [2.6.0] => 2021-MAY-12 ### Added diff --git a/interceptors/Mementifier.cfc b/interceptors/Mementifier.cfc index 82b216c..0b8a435 100644 --- a/interceptors/Mementifier.cfc +++ b/interceptors/Mementifier.cfc @@ -59,20 +59,12 @@ component { */ function processMemento( entity ){ // Verify we haven't mementified this object already - if ( - !structKeyExists( - arguments.entity, - "$mementifierSettings" - ) - ) { + if ( !structKeyExists( arguments.entity, "$mementifierSettings" ) ) { // systemOutput( "==> Injectin mementifier: #getMetadata( arguments.entity ).name# ", true ); // Inject utility arguments.entity.$injectMixin = variables.$injectMixin; // Inject Settings - arguments.entity.$injectMixin( - "$mementifierSettings", - variables.settings - ); + arguments.entity.$injectMixin( "$mementifierSettings", variables.settings ); // Inject getMemento if not overriden if ( !structKeyExists( arguments.entity, "getMemento" ) ) { @@ -92,19 +84,18 @@ component { "$buildNestedMementoStruct", variables.$buildNestedMementoStruct ); - arguments.entity.$injectMixin( - "$getDeepProperties", - variables.$getDeepProperties - ); + arguments.entity.$injectMixin( "$getDeepProperties", variables.$getDeepProperties ); // We do simple date formatters as they are faster than CFML methods var dateMask = isNull( this.memento.dateMask ) ? variables.settings.dateMask : this.memento.dateMask; var timeMask = isNull( this.memento.timeMask ) ? variables.settings.timeMask : this.memento.timeMask; - arguments.entity.$FORMATTER_ISO8601 = createObject( "java", "java.text.SimpleDateFormat" ).init( - "yyyy-MM-dd'T'HH:mm:ssXXX" - ); - arguments.entity.$FORMATTER_CUSTOM = createObject( "java", "java.text.SimpleDateFormat" ).init( - "#dateMask# #timeMask#" - ); + arguments.entity.$FORMATTER_ISO8601 = createObject( + "java", + "java.text.SimpleDateFormat" + ).init( "yyyy-MM-dd'T'HH:mm:ssXXX" ); + arguments.entity.$FORMATTER_CUSTOM = createObject( + "java", + "java.text.SimpleDateFormat" + ).init( "#dateMask# #timeMask#" ); // Do we set timezones? if ( len( variables.settings.convertToTimezone ) ) { var tz = createObject( "java", "java.util.TimeZone" ).getTimeZone( @@ -128,6 +119,7 @@ component { * @iso8601Format If set to true, will use the ISO 8601 standard for formatting dates * @dateMask The date mask to use when formatting datetimes. Only used if iso8601Format is false. * @timeMask The time mask to use when formatting datetimes. Only used if iso8601Format is false. + * @profile The profile to use instead of the defaults */ struct function getMemento( includes = "", @@ -138,7 +130,8 @@ component { boolean trustedGetters, boolean iso8601Format, string dateMask, - string timeMask + string timeMask, + string profile = "" ){ // Inflate incoming lists, arrays are faster than lists if ( isSimpleValue( arguments.includes ) ) { @@ -160,12 +153,21 @@ component { "ormAutoIncludes" : isNull( this.memento.ormAutoIncludes ) ? variables.$mementifierSettings.ormAutoIncludes : this.memento.ormAutoIncludes, "iso8601Format" : isNull( this.memento.iso8601Format ) ? variables.$mementifierSettings.iso8601Format : this.memento.iso8601Format, "dateMask" : isNull( this.memento.dateMask ) ? variables.$mementifierSettings.dateMask : this.memento.dateMask, - "timeMask" : isNull( this.memento.timeMask ) ? variables.$mementifierSettings.timeMask : this.memento.timeMask + "timeMask" : isNull( this.memento.timeMask ) ? variables.$mementifierSettings.timeMask : this.memento.timeMask, + "profiles" : isNull( this.memento.profiles ) ? {} : this.memento.profiles }; - param arguments.trustedGetters = thisMemento.trustedGetters; param arguments.iso8601Format = thisMemento.iso8601Format; + // Choose a profile + if ( len( arguments.profile ) && thisMemento.profiles.keyExists( arguments.profile ) ) { + structAppend( + thisMemento, + thisMemento.profiles[ arguments.profile ], + true + ); + } + // Customize date formatting tools var customDateFormatter = this.$FORMATTER_CUSTOM; if ( !isNull( arguments.dateMask ) || !isNull( arguments.timeMask ) ) { @@ -202,12 +204,7 @@ component { thisMemento.defaultIncludes = typeMap .keyArray() .filter( function( propertyName ){ - switch ( - listLast( - typeMap[ arguments.propertyName ], - "." - ) - ) { + switch ( listLast( typeMap[ arguments.propertyName ], "." ) ) { case "BagType": case "OneToManyType": case "ManyToManyType": @@ -224,10 +221,7 @@ component { // Append primary keys if ( entityMd.hasIdentifierProperty() ) { - arrayAppend( - thisMemento.defaultIncludes, - entityMd.getIdentifierPropertyName() - ); + arrayAppend( thisMemento.defaultIncludes, entityMd.getIdentifierPropertyName() ); } else if ( thisMemento.defaultIncludes.getIdentifierType().isComponentType() ) { arrayAppend( thisMemento.defaultIncludes, @@ -321,10 +315,11 @@ component { // Verify Nullness thisValue = isNull( thisValue ) ? ( - arrayContainsNoCase( - thisMemento.defaults.keyArray(), - item - ) ? ( isNull( thisMemento.defaults[ item ] ) ? javacast( "null", "" ) : thisMemento.defaults[ item ] ) : variables.$mementifierSettings.nullDefaultValue + arrayContainsNoCase( thisMemento.defaults.keyArray(), item ) ? ( + isNull( thisMemento.defaults[ item ] ) ? javacast( "null", "" ) : thisMemento.defaults[ + item + ] + ) : variables.$mementifierSettings.nullDefaultValue ) : thisValue; if ( isNull( thisValue ) ) { @@ -346,7 +341,9 @@ component { // Iso Date? if ( arguments.iso8601Format ) { // we need to convert trailing Zulu time designations offset or JS libs like Moment will not know how to parse it - result[ thisAlias ] = this.$FORMATTER_ISO8601.format( thisValue ).replace( "Z", "+00:00" ); + result[ thisAlias ] = this.$FORMATTER_ISO8601 + .format( thisValue ) + .replace( "Z", "+00:00" ); } else { result[ thisAlias ] = customDateFormatter.format( thisValue ); } @@ -380,12 +377,14 @@ component { // Process the item memento result[ thisAlias ][ thisIndex ] = thisValue[ thisIndex ].getMemento( - includes = nestedIncludes, - excludes = $buildNestedMementoList( excludes, item ), - mappers = $buildNestedMementoStruct( mappers, item ), - defaults = $buildNestedMementoStruct( defaults, item ), + includes : nestedIncludes, + excludes : $buildNestedMementoList( excludes, item ), + mappers : $buildNestedMementoStruct( mappers, item ), + defaults : $buildNestedMementoStruct( defaults, item ), // cascade the ignore defaults down if specific nested includes are requested - ignoreDefaults = nestedIncludes.len() ? arguments.ignoreDefaults : false + ignoreDefaults: nestedIncludes.len() ? arguments.ignoreDefaults : false, + // Cascade the profile to children + profile : arguments.profile ); } else { result[ thisAlias ][ thisIndex ] = thisValue[ thisIndex ]; @@ -403,21 +402,19 @@ component { // Process the item memento var thisItemMemento = thisValue.getMemento( - includes = nestedIncludes, - excludes = $buildNestedMementoList( excludes, item ), - mappers = $buildNestedMementoStruct( mappers, item ), - defaults = $buildNestedMementoStruct( defaults, item ), + includes : nestedIncludes, + excludes : $buildNestedMementoList( excludes, item ), + mappers : $buildNestedMementoStruct( mappers, item ), + defaults : $buildNestedMementoStruct( defaults, item ), // cascade the ignore defaults down if specific nested includes are requested - ignoreDefaults = nestedIncludes.len() ? arguments.ignoreDefaults : false + ignoreDefaults: nestedIncludes.len() ? arguments.ignoreDefaults : false, + // Cascade the profile to children + profile : arguments.profile ); // Do we have a root already for this guy? if ( result.keyExists( thisAlias ) ) { - structAppend( - result[ thisAlias ], - thisItemMemento, - false - ); + structAppend( result[ thisAlias ], thisItemMemento, false ); } else { result[ thisAlias ] = thisItemMemento; } @@ -439,9 +436,10 @@ component { result[ item ] = thisMapper( result[ item ], result ); } else { // Check for null values - result[ item ] = ( !result.keyExists( item ) || isNull( result[ item ] ) ) ? javacast( "null", "" ) : result[ - item - ]; + result[ item ] = ( !result.keyExists( item ) || isNull( result[ item ] ) ) ? javacast( + "null", + "" + ) : result[ item ]; } } @@ -460,7 +458,10 @@ component { function $buildNestedMementoList( required list, required root ){ return arguments.list .filter( function( target ){ - return listFirst( arguments.target, "." ) == root && listLen( arguments.target, "." ) > 1; + return listFirst( arguments.target, "." ) == root && listLen( + arguments.target, + "." + ) > 1; } ) .map( function( target ){ return listDeleteAt( arguments.target, 1, "." ); @@ -483,10 +484,7 @@ component { * * @return A struct of the new hiearchy to use */ - function $buildNestedMementoStruct( - required struct s, - required string root - ){ + function $buildNestedMementoStruct( required struct s, required string root ){ return arguments.s.reduce( function( acc, key, value ){ if ( listFirst( arguments.key, "." ) == root && listLen( arguments.key, "." ) > 1 ) { arguments.acc[ listDeleteAt( arguments.key, 1, "." ) ] = arguments.value; @@ -518,15 +516,9 @@ component { // if this object extends another object, append any inherited properties. if ( structKeyExists( arguments.metaData, "extends" ) && - structKeyExists( - arguments.metaData.extends, - "properties" - ) + structKeyExists( arguments.metaData.extends, "properties" ) ) { - properties.append( - $getDeepProperties( arguments.metaData.extends ), - true - ); + properties.append( $getDeepProperties( arguments.metaData.extends ), true ); } // if this object has properties, append them. diff --git a/readme.md b/readme.md index 45098cd..c99b4a6 100644 --- a/readme.md +++ b/readme.md @@ -63,7 +63,18 @@ this.memento = { // Use a custom date mask for this component dateMask = $mementifierSettings.dateMask, // Use a custom time mask for this component - timeMask = $mementifierSettings.timeMask + timeMask = $mementifierSettings.timeMask, + // A collection of mementifier profiles you can use to create many output permutations + profiles = { + name = { + defaultIncludes : [], + defaultExcludes : [], + neverInclude = [], + defaults = {}, + mappers = {} + ... + } + } } ``` @@ -205,7 +216,8 @@ struct function getMemento( boolean trustedGetters, boolean iso8601Format, string dateMask, - string timeMask + string timeMask, + string profile = "" ) ``` @@ -217,6 +229,97 @@ As you can see, the memento method has also a way to add dynamic `includes`, `ex We have also added a way to ignore the default include and exclude lists via the `ignoreDefaults` flag. If you turn that flag to `true` then **ONLY** the passed in `includes` and `excludes` will be used in the memento. However, please note that the `neverInclude` array will **always** be used. +#### Output Profiles + +You can use the `this.memento.profiles` to define many output profiles a part from the defaults includes and excludes. This is used by using the `profile` argument to the `getMemento()` call. The mementifier will then pass in the profile argument to the object and it's entire object graph. If a child of the object graph does NOT have that profile, it will rever to the defaults instead. + +This is a great way to encapsulate many different output mementifiying options: + +``` +// Declare your profiles +this.memento = { + defaultIncludes : [ + "allowComments", + "cache", + "cacheLastAccessTimeout", + "cacheLayout", + "cacheTimeout", + "categoriesArray:categories", + "contentID", + "contentType", + "createdDate", + "creatorSnapshot:creator", // Creator + "expireDate", + "featuredImage", + "featuredImageURL", + "HTMLDescription", + "HTMLKeywords", + "HTMLTitle", + "isPublished", + "isDeleted", + "lastEditorSnapshot:lastEditor", + "markup", + "modifiedDate", + "numberOfChildren", + "numberOfComments", + "numberOfHits", + "numberOfVersions", + "parentSnapshot:parent", // Parent + "publishedDate", + "showInSearch", + "slug", + "title" + ], + defaultExcludes : [ + "children", + "comments", + "commentSubscriptions", + "contentVersions", + "customFields", + "linkedContent", + "parent", + "relatedContent", + "site", + "stats" + ], + neverInclude : [ "passwordProtection" ], + mappers : {}, + defaults : { stats : {} }, + profiles : { + export : { + defaultIncludes : [ + "children", + "comments", + "commentSubscriptions", + "contentVersions", + "customFields", + "linkedContent", + "relatedContent", + "siteID", + "stats" + ], + defaultExcludes : [ + "commentSubscriptions.relatedContentSnapshot:relatedContent", + "children.parentSnapshot:parent", + "parent", + "site" + ] + } + } +}; +// Incorporate all defaults into export profile to avoid duplicate writing them +this.memento.profiles[ "export" ].defaultIncludes.append( this.memento.defaultIncludes, true ); +``` + +Then use it via the `getMemento()` method call: + +``` +content.getMemento( profile: "export" ) +``` + +Please note that you can still influence the profile by passing in extra `includes`, `excludes` and all the valid memento arguments. + + #### Trusted Getters You can turn on trusted getters during call time by passing `true` to the `trustedGetters` argument. @@ -235,7 +338,8 @@ struct function getMemento( boolean trustedGetters, boolean iso8601Format, string dateMask, - string timeMask + string timeMask, + string profile = "" ){ // Call mementifier var memento = this.$getMemento( argumentCollection=arguments );