Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@
/modules

.vscode
!.engine/
.engine/*
!.engine/WEB-INF/
.engine/WEB-INF/*
!.engine/WEB-INF/lib
.engine/WEB-INF/lib/*
!.engine/WEB-INF/lib/h2-1.4.196.jar
/.engine/

.env
.tmp
1 change: 1 addition & 0 deletions ModuleConfig.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ component {
"defaultQueryOptions" : {},
"preventDuplicateJoins" : true,
"preventLazyLoading" : false,
"automaticTimestamps" : true,
"refreshOnSaveFallback" : true,
"lazyLoadingViolationCallback" : ( entity, relationName ) => {
throw(
Expand Down
166 changes: 126 additions & 40 deletions models/BaseEntity.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,22 @@ component accessors="true" {
persistent="false"
inject ="box:setting:refreshOnSaveFallback@quick";

/**
* The module-level default for automatic entity timestamps.
*/
property
name ="_automaticTimestampsDefault"
persistent="false"
inject ="box:setting:automaticTimestamps@quick";

/**
* Whether automatic timestamps are disabled for the current operation chain.
*/
property
name ="_withoutAutomaticTimestamps"
default ="false"
persistent="false";

/**
* A boolean flag representing that events should not be fired.
*/
Expand Down Expand Up @@ -289,21 +305,23 @@ component accessors="true" {
private any function assignDefaultProperties() {
assignAttributesData( {} );
assignOriginalAttributes( {} );
variables._globalScopeExclusions = [];
param variables._key = "id";
param variables._meta = {};
param variables._data = {};
param variables._relationshipsData = {};
param variables._relationshipsLoaded = {};
param variables._with = [];
variables._withoutRelationshipConstraints = createObject( "java", "java.util.HashSet" ).init();
variables._applyingGlobalScopes = false;
variables._globalScopesApplied = false;
variables._ignoreNotLoadedGuard = false;
variables._withoutFiringEvents = false;
variables._nullValueArgumentSentinel = createObject( "java", "java.lang.Object" ).init();
param variables._preventLazyLoading = false;
param variables._refreshOnSaveFallback = true;
variables._globalScopeExclusions = [];
param variables._key = "id";
param variables._meta = {};
param variables._data = {};
param variables._relationshipsData = {};
param variables._relationshipsLoaded = {};
param variables._with = [];
variables._withoutRelationshipConstraints = createObject( "java", "java.util.HashSet" ).init();
variables._applyingGlobalScopes = false;
variables._globalScopesApplied = false;
variables._ignoreNotLoadedGuard = false;
variables._withoutFiringEvents = false;
variables._nullValueArgumentSentinel = createObject( "java", "java.lang.Object" ).init();
param variables._preventLazyLoading = false;
param variables._refreshOnSaveFallback = true;
param variables._automaticTimestampsDefault = true;
param variables._withoutAutomaticTimestamps = false;
if ( !variables.keyExists( "_lazyLoadingViolationCallback" ) || isNull( variables._lazyLoadingViolationCallback ) ) {
variables._lazyLoadingViolationCallback = ( entity, relationName ) => {
throw(
Expand Down Expand Up @@ -490,7 +508,55 @@ component accessors="true" {
* @return [String]
*/
public array function timestampFields() {
return [ "createdDate", "modifiedDate" ];
var fields = [];
var createdDateAttribute = retrieveCreatedDateAttribute();
if ( len( createdDateAttribute ) ) {
fields.append( createdDateAttribute );
}
var modifiedDateAttribute = retrieveModifiedDateAttribute();
if ( len( modifiedDateAttribute ) ) {
fields.append( modifiedDateAttribute );
}
return fields;
}

/**
* Returns whether this entity automatically maintains timestamps.
*/
public boolean function usesAutomaticTimestamps() {
return variables.automaticTimestamps && !variables._withoutAutomaticTimestamps;
}

/**
* Returns the configured created timestamp attribute when it exists on the entity.
*/
public string function retrieveCreatedDateAttribute() {
return hasAttribute( variables.createdDateAttribute ) ? variables.createdDateAttribute : "";
}

/**
* Returns the configured modified timestamp attribute when it exists on the entity.
*/
public string function retrieveModifiedDateAttribute() {
return hasAttribute( variables.modifiedDateAttribute ) ? variables.modifiedDateAttribute : "";
}

/**
* Applies conventional timestamps to the current insert or update when configured attributes exist.
*/
private void function applyAutomaticTimestamps() {
if ( !usesAutomaticTimestamps() ) {
return;
}
var timestamp = now();
var modifiedDateAttribute = retrieveModifiedDateAttribute();
if ( len( modifiedDateAttribute ) && !isDirty( modifiedDateAttribute ) ) {
assignAttribute( modifiedDateAttribute, timestamp );
}
var createdDateAttribute = retrieveCreatedDateAttribute();
if ( !isLoaded() && len( createdDateAttribute ) && !isDirty( createdDateAttribute ) ) {
assignAttribute( createdDateAttribute, timestamp );
}
}

/**
Expand Down Expand Up @@ -1354,13 +1420,15 @@ component accessors="true" {
*/
public any function newEntity( string name ) {
if ( isNull( arguments.name ) ) {
return variables._wirebox.getInstance(
name = mappingName(),
initArguments = {
meta : variables._meta,
runtimeAttributeOverlay : variables._runtimeAttributeOverlay
}
);
return variables._wirebox
.getInstance(
name = mappingName(),
initArguments = {
meta : variables._meta,
runtimeAttributeOverlay : variables._runtimeAttributeOverlay
}
)
.set_withoutAutomaticTimestamps( variables._withoutAutomaticTimestamps );
}
// Custom named instance
return variables._wirebox.getInstance( arguments.name );
Expand Down Expand Up @@ -1563,6 +1631,7 @@ component accessors="true" {
}
guardNoAttributes();
guardReadOnly();
applyAutomaticTimestamps();
fireEvent(
"preSave",
{
Expand Down Expand Up @@ -1971,7 +2040,12 @@ component accessors="true" {
var timestamp = now();
var timestampAttributes = {};
for ( var field in timestampFields() ) {
timestampAttributes[ field ] = timestamp;
if ( hasAttribute( field ) ) {
timestampAttributes[ field ] = timestamp;
}
}
if ( timestampAttributes.isEmpty() ) {
return this;
}
guardAgainstReadOnlyAttributes( timestampAttributes );

Expand Down Expand Up @@ -3753,13 +3827,16 @@ component accessors="true" {
meta[ "entityName" ] = meta.originalMetadata.entityName;
param meta.localMetadata.properties = [];
guardDuplicatePropertyNames( meta.localMetadata, meta.mapping );
param meta.originalMetadata.table = variables._str.plural( variables._str.snake( meta.entityName ) );
meta[ "table" ] = meta.originalMetadata.table;
param meta.originalMetadata.readonly = false;
meta[ "readonly" ] = meta.originalMetadata.readonly;
param meta.originalMetadata.softDeletes = false;
param meta.originalMetadata.softDeleteColumn = "deletedDate";
meta[ "softDeletes" ] = isBoolean( meta.originalMetadata.softDeletes )
param meta.originalMetadata.table = variables._str.plural( variables._str.snake( meta.entityName ) );
meta[ "table" ] = meta.originalMetadata.table;
param meta.originalMetadata.readonly = false;
meta[ "readonly" ] = meta.originalMetadata.readonly;
param meta.originalMetadata.softDeletes = false;
param meta.originalMetadata.softDeleteColumn = "deletedDate";
param meta.originalMetadata.automaticTimestamps = variables._automaticTimestampsDefault;
param meta.originalMetadata.createdDateAttribute = "createdDate";
param meta.originalMetadata.modifiedDateAttribute = "modifiedDate";
meta[ "softDeletes" ] = isBoolean( meta.originalMetadata.softDeletes )
? meta.originalMetadata.softDeletes
: lCase( trim( meta.originalMetadata.softDeletes & "" ) ) == "true";
meta[ "softDeleteColumn" ] = meta.originalMetadata.softDeleteColumn;
Expand Down Expand Up @@ -3883,14 +3960,20 @@ component accessors="true" {
if ( variables._queryOptions.isEmpty() && variables._meta.originalMetadata.keyExists( "datasource" ) ) {
variables._queryOptions = { datasource : variables._meta.originalMetadata.datasource };
}
variables._readonly = variables._meta.readonly;
variables._softDeletes = variables._meta.softDeletes;
variables._softDeleteColumn = variables._meta.softDeleteColumn;
variables._attributes = variables._meta.attributes;
variables._columns = variables._meta.columns;
variables._functionNames = variables._meta.functionNames;
variables._nonPersistentProperties = variables._meta.nonPersistentProperties;
variables._grammar = variables._meta.originalMetadata.keyExists( "grammar" )
variables._readonly = variables._meta.readonly;
variables._softDeletes = variables._meta.softDeletes;
variables._softDeleteColumn = variables._meta.softDeleteColumn;
var metadataAutomaticTimestamps = isBoolean( variables._meta.originalMetadata.automaticTimestamps )
? variables._meta.originalMetadata.automaticTimestamps
: lCase( trim( variables._meta.originalMetadata.automaticTimestamps & "" ) ) == "true";
param variables.automaticTimestamps = metadataAutomaticTimestamps;
param variables.createdDateAttribute = variables._meta.originalMetadata.createdDateAttribute;
param variables.modifiedDateAttribute = variables._meta.originalMetadata.modifiedDateAttribute;
variables._attributes = variables._meta.attributes;
variables._columns = variables._meta.columns;
variables._functionNames = variables._meta.functionNames;
variables._nonPersistentProperties = variables._meta.nonPersistentProperties;
variables._grammar = variables._meta.originalMetadata.keyExists( "grammar" )
? variables._meta.originalMetadata.grammar
: "";
variables._discriminatorColumn = variables._meta.localMetadata.keyExists( "discriminatorColumn" )
Expand Down Expand Up @@ -3959,7 +4042,10 @@ component accessors="true" {
"singleTableInheritance",
"datasource",
"grammar",
"discriminatorColumn"
"discriminatorColumn",
"automaticTimestamps",
"createdDateAttribute",
"modifiedDateAttribute"
]
) {
if ( arguments.metadata.annotations.keyExists( key ) && !isNull( arguments.metadata.annotations[ key ] ) ) {
Expand Down
90 changes: 82 additions & 8 deletions models/QuickBuilder.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ component accessors="true" transientCache="false" {
*/
property name="_entityTransformers";

/**
* Whether automatic timestamps are disabled for mutations created by this builder.
*/
property name="_withoutAutomaticTimestamps" default="false";

/**
* Used to quickly identify QueryBuilder instances
* instead of resorting to `isInstanceOf` which is slow.
Expand All @@ -93,14 +98,15 @@ component accessors="true" transientCache="false" {
this.isQuickBuilder = true;

function init() {
variables._eagerLoad = [];
variables._globalScopesApplied = false;
variables._globalScopeExcludeAll = false;
variables._asMemento = false;
variables._asQuery = false;
variables._withAliases = false;
variables._entityTransformers = [];
param variables._preventLazyLoading = false;
variables._eagerLoad = [];
variables._globalScopesApplied = false;
variables._globalScopeExcludeAll = false;
variables._asMemento = false;
variables._asQuery = false;
variables._withAliases = false;
variables._entityTransformers = [];
variables._withoutAutomaticTimestamps = false;
param variables._preventLazyLoading = false;
if ( !variables.keyExists( "_lazyLoadingViolationCallback" ) || isNull( variables._lazyLoadingViolationCallback ) ) {
variables._lazyLoadingViolationCallback = ( entity, relationName ) => {
throw(
Expand Down Expand Up @@ -136,11 +142,21 @@ component accessors="true" transientCache="false" {

public QuickBuilder function setEntity( required any newEntity ) {
variables.entity = arguments.newEntity;
variables.entity.set_withoutAutomaticTimestamps( variables._withoutAutomaticTimestamps );
variables.qb.setEntity( arguments.newEntity );
variables.aliasMap[ arguments.newEntity.tableAlias() ] = arguments.newEntity;
return this;
}

/**
* Disables automatic timestamps for mutations and entities created by this builder.
*/
public QuickBuilder function withoutAutomaticTimestamps() {
variables._withoutAutomaticTimestamps = true;
getEntity().set_withoutAutomaticTimestamps( true );
return this;
}

/**
* Resolves a relationship without allocating nested guard callbacks.
*/
Expand Down Expand Up @@ -492,6 +508,7 @@ component accessors="true" transientCache="false" {
getEntity().guardReadOnly();
getEntity().guardAgainstReadOnlyAttributes( arguments.attributes );
}
arguments.attributes = appendUpdatedTimestamp( arguments.attributes );
return variables.qb.update( prepareBulkMutationAttributes( arguments.attributes ) );
}

Expand Down Expand Up @@ -534,9 +551,21 @@ component accessors="true" transientCache="false" {
}
}

arguments.values = appendInsertTimestamps( arguments.values );
arguments.values = prepareBulkMutationValues( arguments.values );
if ( structKeyExists( arguments, "update" ) && isStruct( arguments.update ) ) {
arguments.update = appendUpdatedTimestamp( arguments.update );
arguments.update = prepareBulkMutationAttributes( arguments.update );
} else if ( structKeyExists( arguments, "update" ) && isArray( arguments.update ) ) {
var modifiedDateAttribute = getEntity().retrieveModifiedDateAttribute();
if (
!variables._withoutAutomaticTimestamps
&& getEntity().usesAutomaticTimestamps()
&& len( modifiedDateAttribute )
&& !arguments.update.findNoCase( modifiedDateAttribute )
) {
arguments.update.append( modifiedDateAttribute );
}
}

structDelete( arguments, "force" );
Expand All @@ -557,6 +586,48 @@ component accessors="true" transientCache="false" {
return preparedAttributes;
}

/**
* Adds the configured modified timestamp to bulk mutation attributes when available.
*/
private struct function appendUpdatedTimestamp( required struct attributes ) {
var timestampAttributes = duplicate( arguments.attributes );
var modifiedDateAttribute = getEntity().retrieveModifiedDateAttribute();
if (
!variables._withoutAutomaticTimestamps
&& getEntity().usesAutomaticTimestamps()
&& len( modifiedDateAttribute )
&& !timestampAttributes.keyExists( modifiedDateAttribute )
) {
timestampAttributes[ modifiedDateAttribute ] = now();
}
return timestampAttributes;
}

/**
* Adds configured insert timestamps to literal upsert rows when available.
*/
private any function appendInsertTimestamps( required any values ) {
if ( variables._withoutAutomaticTimestamps || !getEntity().usesAutomaticTimestamps() ) {
return arguments.values;
}
if ( isArray( arguments.values ) ) {
return arguments.values.map( ( value ) => isStruct( value ) ? appendInsertTimestamps( value ) : value );
}
if (
!isStruct( arguments.values )
|| structKeyExists( arguments.values, "isBuilder" )
|| structKeyExists( arguments.values, "isQuickBuilder" )
) {
return arguments.values;
}
var timestampAttributes = appendUpdatedTimestamp( arguments.values );
var createdDateAttribute = getEntity().retrieveCreatedDateAttribute();
if ( len( createdDateAttribute ) && !timestampAttributes.keyExists( createdDateAttribute ) ) {
timestampAttributes[ createdDateAttribute ] = now();
}
return timestampAttributes;
}

/**
* Applies Quick query parameter metadata to literal upsert rows.
*/
Expand Down Expand Up @@ -2052,6 +2123,9 @@ component accessors="true" transientCache="false" {
newBuilder.set_asMemento( this.get_asMemento() );
newBuilder.set_asMementoSettings( this.get_asMementoSettings() );
newBuilder.set_entityTransformers( this.get_entityTransformers() );
if ( variables._withoutAutomaticTimestamps ) {
newBuilder.withoutAutomaticTimestamps();
}
return newBuilder;
}

Expand Down
Loading
Loading