Merge branch 'feature/create-and-modify-date-as-field-behaviors' into dev

This commit is contained in:
Tim Chamberlain
2024-02-05 16:00:08 -06:00
15 changed files with 670 additions and 131 deletions

View File

@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.actions.tables;
import java.io.Serializable; import java.io.Serializable;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -207,17 +206,6 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
return (rs); return (rs);
} }
/////////////////////////////////////////////
// set values in create date & modify date //
// todo .. better (not hard-coded names) //
/////////////////////////////////////////////
Instant now = Instant.now();
for(QRecord record : insertInput.getRecords())
{
setValueIfTableHasField(record, insertInput.getTable(), "createDate", now);
setValueIfTableHasField(record, insertInput.getTable(), "modifyDate", now);
}
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// load the backend module and its insert interface // // load the backend module and its insert interface //
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
@ -233,29 +221,6 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
/*******************************************************************************
** If the table has a field with the given name, then set the given value in the
** given record.
*******************************************************************************/
private static void setValueIfTableHasField(QRecord record, QTableMetaData table, String fieldName, Serializable value)
{
try
{
if(table.getFields().containsKey(fieldName))
{
record.setValue(fieldName, value);
}
}
catch(Exception e)
{
/////////////////////////////////////////////////
// this means field doesn't exist, so, ignore. //
/////////////////////////////////////////////////
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -277,7 +242,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
setDefaultValuesInRecords(table, insertInput.getRecords()); setDefaultValuesInRecords(table, insertInput.getRecords());
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords()); ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, insertInput.getInstance(), table, insertInput.getRecords());
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_UNIQUE_KEY_CHECKS); runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_UNIQUE_KEY_CHECKS);
setErrorsIfUniqueKeyErrors(insertInput, table); setErrorsIfUniqueKeyErrors(insertInput, table);

View File

@ -235,7 +235,7 @@ public class UpdateAction
///////////////////////////// /////////////////////////////
// run standard validators // // run standard validators //
///////////////////////////// /////////////////////////////
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), table, updateInput.getRecords()); ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.UPDATE, updateInput.getInstance(), table, updateInput.getRecords());
validatePrimaryKeysAreGiven(updateInput); validatePrimaryKeysAreGiven(updateInput);
if(oldRecordList.isPresent()) if(oldRecordList.isPresent())

View File

@ -22,7 +22,6 @@
package com.kingsrook.qqq.backend.core.actions.tables.helpers; package com.kingsrook.qqq.backend.core.actions.tables.helpers;
import java.io.Serializable;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -61,11 +60,6 @@ public class UpdateActionRecordSplitHelper
for(QRecord record : updateInput.getRecords()) for(QRecord record : updateInput.getRecords())
{ {
////////////////////////////////////////////
// todo .. better (not a hard-coded name) //
////////////////////////////////////////////
setValueIfTableHasField(record, table, "modifyDate", now);
List<String> updatableFields = table.getFields().values().stream() List<String> updatableFields = table.getFields().values().stream()
.map(QFieldMetaData::getName) .map(QFieldMetaData::getName)
// todo - intent here is to avoid non-updateable fields - but this // todo - intent here is to avoid non-updateable fields - but this
@ -147,29 +141,6 @@ public class UpdateActionRecordSplitHelper
/*******************************************************************************
** If the table has a field with the given name, then set the given value in the
** given record.
*******************************************************************************/
protected void setValueIfTableHasField(QRecord record, QTableMetaData table, String fieldName, Serializable value)
{
try
{
if(table.getFields().containsKey(fieldName))
{
record.setValue(fieldName, value);
}
}
catch(Exception e)
{
/////////////////////////////////////////////////
// this means field doesn't exist, so, ignore. //
/////////////////////////////////////////////////
}
}
/******************************************************************************* /*******************************************************************************
** Getter for haveAnyWithoutErrors ** Getter for haveAnyWithoutErrors
** **

View File

@ -25,12 +25,10 @@ package com.kingsrook.qqq.backend.core.actions.values;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/******************************************************************************* /*******************************************************************************
@ -42,16 +40,10 @@ public class ValueBehaviorApplier
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public static void applyFieldBehaviors(QInstance instance, QTableMetaData table, List<QRecord> recordList) public enum Action
{ {
for(QFieldMetaData field : table.getFields().values()) INSERT,
{ UPDATE
String fieldName = field.getName();
if(field.getType().equals(QFieldType.STRING) && field.getMaxLength() != null)
{
applyValueTooLongBehavior(instance, recordList, field, fieldName);
}
}
} }
@ -59,31 +51,18 @@ public class ValueBehaviorApplier
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void applyValueTooLongBehavior(QInstance instance, List<QRecord> recordList, QFieldMetaData field, String fieldName) public static void applyFieldBehaviors(Action action, QInstance instance, QTableMetaData table, List<QRecord> recordList)
{ {
ValueTooLongBehavior valueTooLongBehavior = field.getBehavior(instance, ValueTooLongBehavior.class); if(CollectionUtils.nullSafeIsEmpty(recordList))
////////////////////////////////////////////////////////////////////////////////////////////////////
// don't process PASS_THROUGH - so we don't have to iterate over the whole record list to do noop //
////////////////////////////////////////////////////////////////////////////////////////////////////
if(valueTooLongBehavior != null && !valueTooLongBehavior.equals(ValueTooLongBehavior.PASS_THROUGH))
{ {
for(QRecord record : recordList) return;
}
for(QFieldMetaData field : table.getFields().values())
{
for(FieldBehavior<?> fieldBehavior : CollectionUtils.nonNullCollection(field.getBehaviors()))
{ {
String value = record.getValueString(fieldName); fieldBehavior.apply(action, recordList, instance, table, field);
if(value != null && value.length() > field.getMaxLength())
{
switch(valueTooLongBehavior)
{
case TRUNCATE -> record.setValue(fieldName, StringUtils.safeTruncate(value, field.getMaxLength()));
case TRUNCATE_ELLIPSIS -> record.setValue(fieldName, StringUtils.safeTruncate(value, field.getMaxLength(), "..."));
case ERROR -> record.addError(new BadInputStatusMessage("The value for " + field.getLabel() + " is too long (max allowed length=" + field.getMaxLength() + ")"));
case PASS_THROUGH ->
{
}
default -> throw new IllegalStateException("Unexpected valueTooLongBehavior: " + valueTooLongBehavior);
}
}
} }
} }
} }

View File

@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
@ -94,10 +95,8 @@ public class QInstanceEnricher
private JoinGraph joinGraph; private JoinGraph joinGraph;
////////////////////////////////////////////////////////// private boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = true;
// todo - come up w/ a way for app devs to set configs! // private boolean configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate = true;
//////////////////////////////////////////////////////////
private boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = true;
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
// let an instance define mappings to be applied during name-to-label enrichments, // // let an instance define mappings to be applied during name-to-label enrichments, //
@ -464,6 +463,22 @@ public class QInstanceEnricher
} }
} }
} }
/////////////////////////////////////////////////////////////////////////
// add field behaviors for create date & modify date, if so configured //
/////////////////////////////////////////////////////////////////////////
if(configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate)
{
if("createDate".equals(field.getName()) && field.getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class) == null)
{
field.withBehavior(DynamicDefaultValueBehavior.CREATE_DATE);
}
if("modifyDate".equals(field.getName()) && field.getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class) == null)
{
field.withBehavior(DynamicDefaultValueBehavior.MODIFY_DATE);
}
}
} }
@ -1220,4 +1235,66 @@ public class QInstanceEnricher
labelMappings.clear(); labelMappings.clear();
} }
/*******************************************************************************
** Getter for configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels
*******************************************************************************/
public boolean getConfigRemoveIdFromNameWhenCreatingPossibleValueFieldLabels()
{
return (this.configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels);
}
/*******************************************************************************
** Setter for configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels
*******************************************************************************/
public void setConfigRemoveIdFromNameWhenCreatingPossibleValueFieldLabels(boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels)
{
this.configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels;
}
/*******************************************************************************
** Fluent setter for configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels
*******************************************************************************/
public QInstanceEnricher withConfigRemoveIdFromNameWhenCreatingPossibleValueFieldLabels(boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels)
{
this.configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels;
return (this);
}
/*******************************************************************************
** Getter for configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate
*******************************************************************************/
public boolean getConfigAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate()
{
return (this.configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate);
}
/*******************************************************************************
** Setter for configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate
*******************************************************************************/
public void setConfigAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate(boolean configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate)
{
this.configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate = configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate;
}
/*******************************************************************************
** Fluent setter for configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate
*******************************************************************************/
public QInstanceEnricher withConfigAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate(boolean configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate)
{
this.configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate = configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate;
return (this);
}
} }

View File

@ -692,7 +692,7 @@ public class QInstanceValidator
String prefix = "Field " + fieldName + " in table " + tableName + " "; String prefix = "Field " + fieldName + " in table " + tableName + " ";
ValueTooLongBehavior behavior = field.getBehavior(qInstance, ValueTooLongBehavior.class); ValueTooLongBehavior behavior = field.getBehaviorOrDefault(qInstance, ValueTooLongBehavior.class);
if(behavior != null && !behavior.equals(ValueTooLongBehavior.PASS_THROUGH)) if(behavior != null && !behavior.equals(ValueTooLongBehavior.PASS_THROUGH))
{ {
assertCondition(field.getMaxLength() != null, prefix + "specifies a ValueTooLongBehavior, but not a maxLength."); assertCondition(field.getMaxLength() != null, prefix + "specifies a ValueTooLongBehavior, but not a maxLength.");

View File

@ -0,0 +1,164 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
** Field behavior that sets a default value for a field dynamically.
** e.g., create-date fields get set to 'now' on insert.
** e.g., modify-date fields get set to 'now' on insert and on update.
*******************************************************************************/
public enum DynamicDefaultValueBehavior implements FieldBehavior<DynamicDefaultValueBehavior>
{
CREATE_DATE,
MODIFY_DATE,
NONE;
private static final QLogger LOG = QLogger.getLogger(ValueTooLongBehavior.class);
/*******************************************************************************
**
*******************************************************************************/
@Override
public DynamicDefaultValueBehavior getDefault()
{
return (NONE);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
{
if(this.equals(NONE))
{
return;
}
switch(this)
{
case CREATE_DATE -> applyCreateDate(action, recordList, table, field);
case MODIFY_DATE -> applyModifyDate(action, recordList, table, field);
default -> throw new IllegalStateException("Unexpected enum value: " + this);
}
}
/*******************************************************************************
**
*******************************************************************************/
private void applyCreateDate(ValueBehaviorApplier.Action action, List<QRecord> recordList, QTableMetaData table, QFieldMetaData field)
{
if(!ValueBehaviorApplier.Action.INSERT.equals(action))
{
return;
}
setCreateDateOrModifyDateOnList(recordList, table, field);
}
/*******************************************************************************
**
*******************************************************************************/
private void applyModifyDate(ValueBehaviorApplier.Action action, List<QRecord> recordList, QTableMetaData table, QFieldMetaData field)
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// check both of these (even though they're the only 2 values at the time of this writing), just in case more enum values are added in the future //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(!ValueBehaviorApplier.Action.INSERT.equals(action) && !ValueBehaviorApplier.Action.UPDATE.equals(action))
{
return;
}
setCreateDateOrModifyDateOnList(recordList, table, field);
}
/*******************************************************************************
**
*******************************************************************************/
private void setCreateDateOrModifyDateOnList(List<QRecord> recordList, QTableMetaData table, QFieldMetaData field)
{
String fieldName = field.getName();
Serializable value = getNow(table, field);
for(QRecord record : CollectionUtils.nonNullList(recordList))
{
record.setValue(fieldName, value);
}
}
/*******************************************************************************
**
*******************************************************************************/
private Serializable getNow(QTableMetaData table, QFieldMetaData field)
{
if(QFieldType.DATE_TIME.equals(field.getType()))
{
return (Instant.now());
}
else if(QFieldType.DATE.equals(field.getType()))
{
return (LocalDate.now());
}
else
{
LOG.debug("Request to apply a " + this.name() + " DynamicDefaultValueBehavior to a non-date or date-time field", logPair("table", table.getName()), logPair("field", field.getName()));
return (null);
}
}
/*******************************************************************************
**
*******************************************************************************/
private void noop()
{
}
}

View File

@ -22,10 +22,41 @@
package com.kingsrook.qqq.backend.core.model.metadata.fields; package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/******************************************************************************* /*******************************************************************************
** Interface for (expected to be?) enums which define behaviors that get applied
** to fields.
**
** At the present, these behaviors get applied before a field is stored (insert
** or update), through the ValueBehaviorApplier class.
** **
*******************************************************************************/ *******************************************************************************/
public interface FieldBehavior public interface FieldBehavior<T extends FieldBehavior<T>>
{ {
/*******************************************************************************
** In case a behavior of this type wasn't set on the field, what should the
** default of this type be?
*******************************************************************************/
T getDefault();
/*******************************************************************************
** Apply this behavior to a list of records
*******************************************************************************/
void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field);
/*******************************************************************************
** control if multiple behaviors of this type should be allowed together on a field.
*******************************************************************************/
default boolean allowMultipleBehaviorsOfThisType()
{
return (false);
}
} }

View File

@ -35,6 +35,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.github.hervian.reflection.Fun; import com.github.hervian.reflection.Fun;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager; import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QField; import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
@ -44,6 +45,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock; import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/******************************************************************************* /*******************************************************************************
@ -52,6 +54,8 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
*******************************************************************************/ *******************************************************************************/
public class QFieldMetaData implements Cloneable public class QFieldMetaData implements Cloneable
{ {
private static final QLogger LOG = QLogger.getLogger(QFieldMetaData.class);
private String name; private String name;
private String label; private String label;
private String backendName; private String backendName;
@ -73,8 +77,8 @@ public class QFieldMetaData implements Cloneable
private String possibleValueSourceName; private String possibleValueSourceName;
private QQueryFilter possibleValueSourceFilter; private QQueryFilter possibleValueSourceFilter;
private Integer maxLength; private Integer maxLength;
private Set<FieldBehavior> behaviors; private Set<FieldBehavior<?>> behaviors;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// w/ longer-term vision for FieldBehaviors // // w/ longer-term vision for FieldBehaviors //
@ -674,7 +678,7 @@ public class QFieldMetaData implements Cloneable
** Getter for behaviors ** Getter for behaviors
** **
*******************************************************************************/ *******************************************************************************/
public Set<FieldBehavior> getBehaviors() public Set<FieldBehavior<?>> getBehaviors()
{ {
return behaviors; return behaviors;
} }
@ -682,11 +686,12 @@ public class QFieldMetaData implements Cloneable
/******************************************************************************* /*******************************************************************************
** ** Get the FieldBehavior object of a given behaviorType (class) - but - if one
** isn't set, then use the default from that type.
*******************************************************************************/ *******************************************************************************/
public <T extends FieldBehavior> T getBehavior(QInstance instance, Class<T> behaviorType) public <T extends FieldBehavior<T>> T getBehaviorOrDefault(QInstance instance, Class<T> behaviorType)
{ {
for(FieldBehavior fieldBehavior : CollectionUtils.nonNullCollection(behaviors)) for(FieldBehavior<?> fieldBehavior : CollectionUtils.nonNullCollection(behaviors))
{ {
if(behaviorType.isInstance(fieldBehavior)) if(behaviorType.isInstance(fieldBehavior))
{ {
@ -701,9 +706,33 @@ public class QFieldMetaData implements Cloneable
/////////////////////////////////////////// ///////////////////////////////////////////
// return default behavior for this type // // return default behavior for this type //
/////////////////////////////////////////// ///////////////////////////////////////////
if(behaviorType.equals(ValueTooLongBehavior.class)) if(behaviorType.isEnum())
{ {
return behaviorType.cast(ValueTooLongBehavior.getDefault()); return (behaviorType.getEnumConstants()[0].getDefault());
}
return (null);
}
/*******************************************************************************
** Get the FieldBehavior object of a given behaviorType (class) - and if one
** isn't set, then return null.
*******************************************************************************/
public <T extends FieldBehavior<T>> T getBehaviorOnlyIfSet(Class<T> behaviorType)
{
if(behaviors == null)
{
return (null);
}
for(FieldBehavior<?> fieldBehavior : CollectionUtils.nonNullCollection(behaviors))
{
if(behaviorType.isInstance(fieldBehavior))
{
return (behaviorType.cast(fieldBehavior));
}
} }
return (null); return (null);
@ -715,7 +744,7 @@ public class QFieldMetaData implements Cloneable
** Setter for behaviors ** Setter for behaviors
** **
*******************************************************************************/ *******************************************************************************/
public void setBehaviors(Set<FieldBehavior> behaviors) public void setBehaviors(Set<FieldBehavior<?>> behaviors)
{ {
this.behaviors = behaviors; this.behaviors = behaviors;
} }
@ -726,7 +755,7 @@ public class QFieldMetaData implements Cloneable
** Fluent setter for behaviors ** Fluent setter for behaviors
** **
*******************************************************************************/ *******************************************************************************/
public QFieldMetaData withBehaviors(Set<FieldBehavior> behaviors) public QFieldMetaData withBehaviors(Set<FieldBehavior<?>> behaviors)
{ {
this.behaviors = behaviors; this.behaviors = behaviors;
return (this); return (this);
@ -738,12 +767,30 @@ public class QFieldMetaData implements Cloneable
** Fluent setter for behaviors ** Fluent setter for behaviors
** **
*******************************************************************************/ *******************************************************************************/
public QFieldMetaData withBehavior(FieldBehavior behavior) public QFieldMetaData withBehavior(FieldBehavior<?> behavior)
{ {
if(behavior == null)
{
LOG.debug("Skipping request to add null behavior", logPair("fieldName", getName()));
return (this);
}
if(behaviors == null) if(behaviors == null)
{ {
behaviors = new HashSet<>(); behaviors = new HashSet<>();
} }
if(!behavior.allowMultipleBehaviorsOfThisType())
{
@SuppressWarnings("unchecked")
FieldBehavior<?> existingBehaviorOfThisType = getBehaviorOnlyIfSet(behavior.getClass());
if(existingBehaviorOfThisType != null)
{
LOG.debug("Replacing a field behavior", logPair("fieldName", getName()), logPair("oldBehavior", existingBehaviorOfThisType), logPair("newBehavior", behavior));
this.behaviors.remove(existingBehaviorOfThisType);
}
}
this.behaviors.add(behavior); this.behaviors.add(behavior);
return (this); return (this);
} }

View File

@ -22,23 +22,85 @@
package com.kingsrook.qqq.backend.core.model.metadata.fields; package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/******************************************************************************* /*******************************************************************************
** Behaviors for string fields, if their value is too long.
** **
** Note: This was the first implementation of a FieldBehavior, so its test
** coverage is provided in ValueBehaviorApplierTest.
*******************************************************************************/ *******************************************************************************/
public enum ValueTooLongBehavior implements FieldBehavior public enum ValueTooLongBehavior implements FieldBehavior<ValueTooLongBehavior>
{ {
TRUNCATE, TRUNCATE,
TRUNCATE_ELLIPSIS, TRUNCATE_ELLIPSIS,
ERROR, ERROR,
PASS_THROUGH; PASS_THROUGH;
private static final QLogger LOG = QLogger.getLogger(ValueTooLongBehavior.class);
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public static FieldBehavior getDefault() @Override
public ValueTooLongBehavior getDefault()
{ {
return PASS_THROUGH; return (PASS_THROUGH);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
{
if(this.equals(PASS_THROUGH))
{
return;
}
String fieldName = field.getName();
if(!QFieldType.STRING.equals(field.getType()))
{
LOG.debug("Request to apply a ValueTooLongBehavior to a non-string field", logPair("table", table.getName()), logPair("field", fieldName));
return;
}
if(field.getMaxLength() == null)
{
LOG.debug("Request to apply a ValueTooLongBehavior to string field without a maxLength", logPair("table", table.getName()), logPair("field", fieldName));
return;
}
for(QRecord record : recordList)
{
String value = record.getValueString(fieldName);
if(value != null && value.length() > field.getMaxLength())
{
switch(this)
{
case TRUNCATE -> record.setValue(fieldName, StringUtils.safeTruncate(value, field.getMaxLength()));
case TRUNCATE_ELLIPSIS -> record.setValue(fieldName, StringUtils.safeTruncate(value, field.getMaxLength(), "..."));
case ERROR -> record.addError(new BadInputStatusMessage("The value for " + field.getLabel() + " is too long (max allowed length=" + field.getMaxLength() + ")"));
///////////////////////////////////
// PASS_THROUGH is handled above //
///////////////////////////////////
default -> throw new IllegalStateException("Unexpected enum value: " + this);
}
}
}
} }
} }

View File

@ -38,7 +38,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.statusmessages.SystemErrorStatusMessage; import com.kingsrook.qqq.backend.core.model.statusmessages.SystemErrorStatusMessage;
import com.kingsrook.qqq.backend.core.utils.ListingHash; import com.kingsrook.qqq.backend.core.utils.ListingHash;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -64,6 +63,7 @@ class UpdateActionRecordSplitHelperTest extends BaseTest
.withField(new QFieldMetaData("B", QFieldType.INTEGER)) .withField(new QFieldMetaData("B", QFieldType.INTEGER))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME))); .withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME)));
Instant now = Instant.now();
UpdateInput updateInput = new UpdateInput(tableName) UpdateInput updateInput = new UpdateInput(tableName)
.withRecord(new QRecord().withValue("id", 1).withValue("A", 1)) .withRecord(new QRecord().withValue("id", 1).withValue("A", 1))
.withRecord(new QRecord().withValue("id", 2).withValue("A", 2)) .withRecord(new QRecord().withValue("id", 2).withValue("A", 2))
@ -71,6 +71,7 @@ class UpdateActionRecordSplitHelperTest extends BaseTest
.withRecord(new QRecord().withValue("id", 4).withValue("B", 3)) .withRecord(new QRecord().withValue("id", 4).withValue("B", 3))
.withRecord(new QRecord().withValue("id", 5).withValue("B", 3)) .withRecord(new QRecord().withValue("id", 5).withValue("B", 3))
.withRecord(new QRecord().withValue("id", 6).withValue("A", 4).withValue("B", 5)); .withRecord(new QRecord().withValue("id", 6).withValue("A", 4).withValue("B", 5));
updateInput.getRecords().forEach(r -> r.setValue("modifyDate", now));
UpdateActionRecordSplitHelper updateActionRecordSplitHelper = new UpdateActionRecordSplitHelper(); UpdateActionRecordSplitHelper updateActionRecordSplitHelper = new UpdateActionRecordSplitHelper();
updateActionRecordSplitHelper.init(updateInput); updateActionRecordSplitHelper.init(updateInput);
ListingHash<List<String>, QRecord> recordsByFieldBeingUpdated = updateActionRecordSplitHelper.getRecordsByFieldBeingUpdated(); ListingHash<List<String>, QRecord> recordsByFieldBeingUpdated = updateActionRecordSplitHelper.getRecordsByFieldBeingUpdated();
@ -78,12 +79,6 @@ class UpdateActionRecordSplitHelperTest extends BaseTest
Function<Collection<QRecord>, Set<Integer>> extractIds = (records) -> Function<Collection<QRecord>, Set<Integer>> extractIds = (records) ->
records.stream().map(r -> r.getValueInteger("id")).collect(Collectors.toSet()); records.stream().map(r -> r.getValueInteger("id")).collect(Collectors.toSet());
////////////////////////////////////////
// validate that modify dates got set //
////////////////////////////////////////
updateInput.getRecords().forEach(r ->
assertThat(r.getValue("modifyDate")).isInstanceOf(Instant.class));
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
// validate the grouping of records by fields-being-updated // // validate the grouping of records by fields-being-updated //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////

View File

@ -39,7 +39,9 @@ import static org.junit.jupiter.api.Assertions.fail;
/******************************************************************************* /*******************************************************************************
** Unit test for ValueBehaviorApplier ** Unit test for ValueBehaviorApplier - and also providing coverage for
** ValueTooLongBehavior (the first implementation, which was previously in the
** class under test).
*******************************************************************************/ *******************************************************************************/
class ValueBehaviorApplierTest extends BaseTest class ValueBehaviorApplierTest extends BaseTest
{ {
@ -61,7 +63,7 @@ class ValueBehaviorApplierTest extends BaseTest
new QRecord().withValue("id", 2).withValue("firstName", "John").withValue("lastName", "Last name too long").withValue("email", "john@smith.com"), new QRecord().withValue("id", 2).withValue("firstName", "John").withValue("lastName", "Last name too long").withValue("email", "john@smith.com"),
new QRecord().withValue("id", 3).withValue("firstName", "First name too long").withValue("lastName", "Smith").withValue("email", "john.smith@emaildomainwayytolongtofit.com") new QRecord().withValue("id", 3).withValue("firstName", "First name too long").withValue("lastName", "Smith").withValue("email", "john.smith@emaildomainwayytolongtofit.com")
); );
ValueBehaviorApplier.applyFieldBehaviors(qInstance, table, recordList); ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, qInstance, table, recordList);
assertEquals("First name", getRecordById(recordList, 1).getValueString("firstName")); assertEquals("First name", getRecordById(recordList, 1).getValueString("firstName"));
assertEquals("Last na...", getRecordById(recordList, 2).getValueString("lastName")); assertEquals("Last na...", getRecordById(recordList, 2).getValueString("lastName"));
@ -93,7 +95,7 @@ class ValueBehaviorApplierTest extends BaseTest
new QRecord().withValue("id", 1).withValue("firstName", "First name too long").withValue("lastName", null).withValue("email", "john@smith.com"), new QRecord().withValue("id", 1).withValue("firstName", "First name too long").withValue("lastName", null).withValue("email", "john@smith.com"),
new QRecord().withValue("id", 2).withValue("firstName", "").withValue("lastName", "Last name too long").withValue("email", "john@smith.com") new QRecord().withValue("id", 2).withValue("firstName", "").withValue("lastName", "Last name too long").withValue("email", "john@smith.com")
); );
ValueBehaviorApplier.applyFieldBehaviors(qInstance, table, recordList); ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, qInstance, table, recordList);
assertEquals("First name too long", getRecordById(recordList, 1).getValueString("firstName")); assertEquals("First name too long", getRecordById(recordList, 1).getValueString("firstName"));
assertNull(getRecordById(recordList, 1).getValueString("lastName")); assertNull(getRecordById(recordList, 1).getValueString("lastName"));

View File

@ -29,6 +29,7 @@ import java.util.Optional;
import com.kingsrook.qqq.backend.core.BaseTest; import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
@ -493,4 +494,39 @@ class QInstanceEnricherTest extends BaseTest
return (tableMetaData); return (tableMetaData);
} }
/*******************************************************************************
**
*******************************************************************************/
@Test
void testCreateDateAndModifyDateBehaviors()
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.addTable(newTable("A", "id", "createDate", "modifyDate"));
QTableMetaData table = qInstance.getTable("A");
////////////////////////////////////////////////
// make sure behavior wasn't there by default //
////////////////////////////////////////////////
assertNull(table.getField("createDate").getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class));
assertNull(table.getField("modifyDate").getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class));
//////////////////////////////////////////////////////////////////
// make sure if config'ing off the adding of the behavior works //
//////////////////////////////////////////////////////////////////
new QInstanceEnricher(qInstance)
.withConfigAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate(false)
.enrich();
assertNull(table.getField("createDate").getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class));
assertNull(table.getField("modifyDate").getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class));
/////////////////////////////////////////////////////////////////////////////////////////////
// make sure default value for the config (e.g., in a new enricher) is to add the behavior //
/////////////////////////////////////////////////////////////////////////////////////////////
new QInstanceEnricher(qInstance).enrich();
assertEquals(DynamicDefaultValueBehavior.CREATE_DATE, table.getField("createDate").getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class));
assertEquals(DynamicDefaultValueBehavior.MODIFY_DATE, table.getField("modifyDate").getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class));
}
} }

View File

@ -0,0 +1,139 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.time.LocalDate;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
** Unit test for DynamicDefaultValueBehavior
*******************************************************************************/
class DynamicDefaultValueBehaviorTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void testCreateDateHappyPath()
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
QRecord record = new QRecord().withValue("id", 1);
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, qInstance, table, List.of(record));
assertNotNull(record.getValue("createDate"));
assertNotNull(record.getValue("modifyDate"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testModifyDateHappyPath()
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
QRecord record = new QRecord().withValue("id", 1);
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.UPDATE, qInstance, table, List.of(record));
assertNull(record.getValue("createDate"));
assertNotNull(record.getValue("modifyDate"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNone()
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
table.getField("createDate").withBehavior(DynamicDefaultValueBehavior.NONE);
table.getField("modifyDate").withBehavior(DynamicDefaultValueBehavior.NONE);
QRecord record = new QRecord().withValue("id", 1);
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, qInstance, table, List.of(record));
assertNull(record.getValue("createDate"));
assertNull(record.getValue("modifyDate"));
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.UPDATE, qInstance, table, List.of(record));
assertNull(record.getValue("createDate"));
assertNull(record.getValue("modifyDate"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDateInsteadOfDateTimeField()
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
table.getField("createDate").withType(QFieldType.DATE);
QRecord record = new QRecord().withValue("id", 1);
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, qInstance, table, List.of(record));
assertNotNull(record.getValue("createDate"));
assertThat(record.getValue("createDate")).isInstanceOf(LocalDate.class);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNonDateField()
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
table.getField("firstName").withBehavior(DynamicDefaultValueBehavior.CREATE_DATE);
QRecord record = new QRecord().withValue("id", 1);
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, qInstance, table, List.of(record));
assertNull(record.getValue("firstName"));
}
}

View File

@ -0,0 +1,71 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.fields;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for QFieldMetaData
*******************************************************************************/
class QFieldMetaDataTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldBehaviors()
{
/////////////////////////////////////////
// create field - assert default state //
/////////////////////////////////////////
QFieldMetaData field = new QFieldMetaData("createDate", QFieldType.DATE_TIME);
assertTrue(CollectionUtils.nullSafeIsEmpty(field.getBehaviors()));
assertNull(field.getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class));
assertEquals(DynamicDefaultValueBehavior.NONE, field.getBehaviorOrDefault(new QInstance(), DynamicDefaultValueBehavior.class));
//////////////////////////////////////
// add NONE behavior - assert state //
//////////////////////////////////////
field.withBehavior(DynamicDefaultValueBehavior.NONE);
assertEquals(1, field.getBehaviors().size());
assertEquals(DynamicDefaultValueBehavior.NONE, field.getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class));
assertEquals(DynamicDefaultValueBehavior.NONE, field.getBehaviorOrDefault(new QInstance(), DynamicDefaultValueBehavior.class));
/////////////////////////////////////////////////////////
// replace behavior - assert it got rid of the old one //
/////////////////////////////////////////////////////////
field.withBehavior(DynamicDefaultValueBehavior.CREATE_DATE);
assertEquals(1, field.getBehaviors().size());
assertEquals(DynamicDefaultValueBehavior.CREATE_DATE, field.getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class));
assertEquals(DynamicDefaultValueBehavior.CREATE_DATE, field.getBehaviorOrDefault(new QInstance(), DynamicDefaultValueBehavior.class));
}
}