diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md new file mode 100644 index 0000000..300232e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +--- + + + +## What are the steps to reproduce this issue? + +1. … +2. … +3. … + +## What happens? + +… + +## What were you expecting to happen? + +… + +## Any logs, error output, etc? + +… + +## Any other comments? + +… + +## What versions are you using? + +**Operating System:** … +**Package Version:** … diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md new file mode 100644 index 0000000..c10946f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md @@ -0,0 +1,18 @@ +--- +name: Feature Request +about: Request a new feature or enhancement +--- + + + +## Summary + + + +## Detailed Description + + + +## Possible Implementation Ideas + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a94e0f8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ +## Description + +Please include a summary of the changes and which issue(s) is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. + +**Please note that all PRs must have tests attached to them** + +IMPORTANT: Please review the [CONTRIBUTING.md](../CONTRIBUTING.md) file for detailed contributing guidelines. + +## Issues + +All PRs must have an accompanied issue. Please make sure you created it and linked it here. + + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug Fix +- [ ] Improvement +- [ ] New Feature +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +## Checklist + +- [ ] My code follows the style guidelines of this project [cfformat](../.cfformat.json) +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes diff --git a/ModuleConfig.cfc b/ModuleConfig.cfc index b8f781e..cce2260 100644 --- a/ModuleConfig.cfc +++ b/ModuleConfig.cfc @@ -35,7 +35,9 @@ component { // Don't check for getters before invoking them trustedGetters : false, // If not empty, convert all date/times to the specific timezone - convertToTimezone : "" + convertToTimezone : "", + // Verifies if values are not numeric and isBoolean() and do auto casting to Java Boolean + autoCastBooleans : true }; // Custom Declared Interceptors diff --git a/box.json b/box.json index ee8f461..148e678 100644 --- a/box.json +++ b/box.json @@ -1,6 +1,6 @@ { "name":"Mementifier : The State Maker!", - "version":"3.2.0", + "version":"3.3.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", diff --git a/changelog.md b/changelog.md index d8c3c95..b1139bb 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ---- +## [3.3.0] => 2023-MAR-17 + +### Added + +* New global settings `autoCastBooleans` which allows you to turn this feature on or off. By default we inspect if a value is not numeric and `isBoolean` and auto cast it to Java `Boolean` so it translated to a boolean in json. +* New `this.memento.autoCastBooleans` so you can turn on/off this feature at an entity level. +* New `autoCastBooleans` argument to the `getMemento()` to turn on/off this feature for that call only. + +---- + ## [3.2.0] => 2023-JAN-19 ### Fixed diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..2918a48 --- /dev/null +++ b/contributing.md @@ -0,0 +1,151 @@ +# Contributing Guide + +Hola amigo! I'm really excited that you are interested in contributing to mementifier. Before submitting your contribution, please make sure to take a moment and read through the following guidelines: + +- [Code Of Conduct](#code-of-conduct) +- [Bug Reporting](#bug-reporting) +- [Support Questions](#support-questions) +- [Pull Request Guidelines](#pull-request-guidelines) +- [Security Vulnerabilities](#security-vulnerabilities) +- [Development Setup](#development-setup) + - [Language Compatiblity](#language-compatiblity) +- [Coding Styles \& Formatting](#coding-styles--formatting) +- [CFC Docs With DocBox](#cfc-docs-with-docbox) +- [Financial Contributions](#financial-contributions) +- [Contributors](#contributors) + +## Code Of Conduct + +This project is open source, and as such, the maintainers give their free time to build and maintain the source code held within. They make the code freely available in the hope that it will be of use to other developers and/or businesses. Please be considerate towards maintainers when raising issues or presenting pull requests. **We all follow the Golden Rule: Do to others as you want them to do to you.** + +- As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. +- Participants will be tolerant of opposing views. +- Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. +- Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. +- When interpreting the words and actions of others, participants should always assume good intentions. Emotions cannot be derived from textual representations. +- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. + + +## Bug Reporting + +Please make sure also that if you submit a pull request, that you link it to the appropriate issue(s). + +If you file a bug report, your issue should contain a title, a clear description of the issue, a way to replicate the issue, and any support files that we might need to replicate your issue. The goal of a bug report is to make it easy for yourself - and others - to replicate the bug and develop a fix for it. All issues that do not contain a way to replicate will not be addressed. + +## Support Questions + +If you have any questions on usage, professional support or just ideas to bounche of the maintainers, please do not create an issue. Leverage our support channels first. + +- Ortus Community Discourse: https://community.ortussolutions.com +- Box Slack Team: http://boxteam.ortussolutions.com/ +- Professional Support: https://www.ortussolutions.com/services/support + +## Pull Request Guidelines + +- The `(master|main)` branch is just a snapshot of the latest stable release. All development should be done in dedicated branches. Do not submit PRs against the master branch. They will be closed. +- All pull requests should be sent against the `development` branch. +- It's OK to have multiple small commits as you work on the PR - GitHub will automatically squash it before merging. +- Make sure all local tests pass before submitting the merge. +- Please make sure all your pull requests have companion tests. +- Please link the Jira issue in your PR title when sending the final PR + +## Security Vulnerabilities + +If you discover a security vulnerability, please send an email to the Ortus security team at [security@ortussolutions.com](mailto:security@ortussolutions.com?subject=security) and make sure you report it to the `#security` channel in our Box Team Slack Channel. All security vulnerabilities will be promptly addressed. + +## Development Setup + +1. Fork and Star our project. +2. Make sure you have CommandBox installed: https://www.ortussolutions.com/products/commandbox#download +3. Start a CommandBox shell in the root of the project: `box` +4. Install the development dependencies: `run-script install:dependencies` +5. Hack away! Create tests under `/test-harness/specs` and run the tests! + +### Running The Test Suites + +In order to collaborate on this project you will need to do a few things in order to get the test harness ready for execution. The `test-harness` folder is where the ColdBox test app exists that consumes the module for testing. The `test-harness/tests/specs` is where all the specs for testing are located. + +#### Database + +Create a database called `mementifier` in any RDBMS you like. We have mostly used MySQL for the tests. + +This quick Docker command will get you started: + +```bash +docker run --detach \ + --publish 3306:3306 \ + --name mementifier_mysql \ + --env MYSQL_ROOT_PASSWORD=mysql \ + --env MYSQL_DATABASE=mementifier \ + mysql +``` + +#### Environment + +Copy the `.env.template` as `.env` and modify it accordingly so it can connect to your database. + +#### Dependencies + +Go into the root of `test-harness` and run a CommandBox shell: `box`. Once in the shell install the dependencies `install`. + +#### Start a Server + +Start a server, we have configured for you several CFML engines for you to test against, pick one from the list below: + +- `server start serverConfigFile=server-adobe@2018.json` +- - `server start serverConfigFile=server-adobe@2021.json` +- `server start serverConfigFile=server-lucee@5.json` + +Then you can hit the test site app at http://localhost:60299. This will create the database for you using the ColdFusion ORM. + +#### Running Tests + +You can then run the tests at http://localhost:60299/tests/runner.cfm + +### Language Compatiblity + +Please make sure your code runs on the following CFML Engines: + +- Lucee 5+ +- Adobe ColdFusion 2018+ + +## Coding Styles & Formatting + +We are big on coding styles and have included a `.cfformat.json` in the root of the project so that you can run the formatting tools and CommandBox scripts: + +```bash +# Format everything +box run-script format + +# Start a watcher, type away, save and auto-format for you +box run-script format:watch +``` + +We recommend that anytime you hack on the core you start the formatter watcher (`box run-script format:watch`). This will monitor your changes and auto-format your code for you. + +You can also see the Ortus Coding Standards you must follow here: https://github.com/Ortus-Solutions/coding-standards. + +## CFC Docs With DocBox + +All CFCs are self-documenting and we leverage [DocBox](https://docbox.ortusbooks.com/) to document the entire software. All functions must be properly documented using the DocBox syntax: https://docbox.ortusbooks.com/getting-started/annotating-your-code + + +## Financial Contributions + +You can support ColdBox and all of our Open Source initiatives at Ortus Solutions by becoming a patreon. You can also get lots of goodies and services depending on the level of contributions. + +- [Become a backer or sponsor on Patreon](https://www.patreon.com/ortussolutions) +- [One-time donations via PayPal](https://www.paypal.com/paypalme/ortussolutions) + +## Contributors + +Thank you to all the people who have already contributed to @MODULE_NAME@! We :heart: :heart: :heart: love you! + + + + + + +Made with [contributors-img](https://contrib.rocks) + + diff --git a/interceptors/Mementifier.cfc b/interceptors/Mementifier.cfc index c645621..09b71b6 100644 --- a/interceptors/Mementifier.cfc +++ b/interceptors/Mementifier.cfc @@ -117,6 +117,7 @@ component { * @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 + * @autoCastBooleans Auto cast boolean values if they are not numeric and isBoolean(). */ struct function getMemento( includes = "", @@ -128,7 +129,8 @@ component { boolean iso8601Format, string dateMask, string timeMask, - string profile = "" + string profile = "", + boolean autoCastBooleans = true ){ local.includes = duplicate( arguments.includes ); local.excludes = duplicate( arguments.excludes ); @@ -144,23 +146,25 @@ component { // Param Default Memento Settings // We do it here, because ACF caches crap! var thisMemento = { + "autoCastBooleans" : isNull( this.memento.autoCastBooleans ) ? variables.$mementifierSettings.autoCastBooleans : this.memento.autoCastBooleans, + "dateMask" : isNull( this.memento.dateMask ) ? variables.$mementifierSettings.dateMask : this.memento.dateMask, + "defaults" : isNull( this.memento.defaults ) ? {} : this.memento.defaults, "defaultIncludes" : isNull( this.memento.defaultIncludes ) ? [] : this.memento.defaultIncludes, "defaultExcludes" : isNull( this.memento.defaultExcludes ) ? [] : this.memento.defaultExcludes, - "neverInclude" : isNull( this.memento.neverInclude ) ? [] : this.memento.neverInclude, + "iso8601Format" : isNull( this.memento.iso8601Format ) ? variables.$mementifierSettings.iso8601Format : this.memento.iso8601Format, "mappers" : isNull( this.memento.mappers ) ? {} : this.memento.mappers, - "defaults" : isNull( this.memento.defaults ) ? {} : this.memento.defaults, - "trustedGetters" : isNull( this.memento.trustedGetters ) ? variables.$mementifierSettings.trustedGetters : this.memento.trustedGetters, + "neverInclude" : isNull( this.memento.neverInclude ) ? [] : this.memento.neverInclude, "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, + "profiles" : isNull( this.memento.profiles ) ? {} : this.memento.profiles, "timeMask" : isNull( this.memento.timeMask ) ? variables.$mementifierSettings.timeMask : this.memento.timeMask, - "profiles" : isNull( this.memento.profiles ) ? {} : this.memento.profiles + "trustedGetters" : isNull( this.memento.trustedGetters ) ? variables.$mementifierSettings.trustedGetters : this.memento.trustedGetters }; // Param arguments according to instance > settings chain precedence - param arguments.trustedGetters = thisMemento.trustedGetters; - param arguments.iso8601Format = thisMemento.iso8601Format; - param arguments.dateMask = thisMemento.dateMask; - param arguments.timeMask = thisMemento.timeMask; + param arguments.trustedGetters = thisMemento.trustedGetters; + param arguments.iso8601Format = thisMemento.iso8601Format; + param arguments.dateMask = thisMemento.dateMask; + param arguments.timeMask = thisMemento.timeMask; + param arguments.autoCastBooleans = thisMemento.autoCastBooleans; // Choose a profile if ( len( arguments.profile ) && thisMemento.profiles.keyExists( arguments.profile ) ) { @@ -280,6 +284,7 @@ component { continue; } + // Verify Nullness thisValue = isNull( thisValue ) ? ( arrayContainsNoCase( thisMemento.defaults.keyArray(), item ) ? ( @@ -290,6 +295,7 @@ component { if ( isNull( thisValue ) ) { result[ thisAlias ] = javacast( "null", "" ); } + // Match timestamps + date/time objects else if ( isSimpleValue( thisValue ) @@ -314,14 +320,17 @@ component { result[ thisAlias ] = thisValue; } } + // Strict Type Boolean Values - else if ( !isNumeric( thisValue ) && isBoolean( thisValue ) ) { + else if ( arguments.autoCastBooleans && !isNumeric( thisValue ) && isBoolean( thisValue ) ) { result[ thisAlias ] = javacast( "Boolean", thisValue ); } + // Simple Values else if ( isSimpleValue( thisValue ) ) { result[ thisAlias ] = thisValue; } + // Array Collections else if ( isArray( thisValue ) ) { // Map Items into result object @@ -351,7 +360,8 @@ component { trustedGetters: arguments.trustedGetters, iso8601Format : arguments.iso8601Format, dateMask : arguments.dateMask, - timeMask : arguments.timeMask + timeMask : arguments.timeMask, + autoCastBooleans : arguments.autoCastBooleans ); } else { result[ thisAlias ][ thisIndex ] = thisValue[ thisIndex ]; @@ -369,18 +379,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 arguments to the children - profile : arguments.profile, - trustedGetters: arguments.trustedGetters, - iso8601Format : arguments.iso8601Format, - dateMask : arguments.dateMask, - timeMask : arguments.timeMask + profile : arguments.profile, + trustedGetters : arguments.trustedGetters, + iso8601Format : arguments.iso8601Format, + dateMask : arguments.dateMask, + timeMask : arguments.timeMask, + autoCastBooleans : arguments.autoCastBooleans ); // Do we have a root already for this guy? @@ -389,8 +400,11 @@ component { } else { result[ thisAlias ] = thisItemMemento; } - } else { - // we don't know what to do with this item so we return as-is + } + + + // we don't know what to do with this item so we return as-is + else { result[ thisAlias ] = thisValue; } } diff --git a/readme.md b/readme.md index f30fad4..2ec73e3 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,7 @@ You can combine this module with `cffractal` (https://forgebox.io/view/cffractal ## Module Settings -Just open your `config/Coldbox.cfc` and add the following settings into the `moduleSettings` struct under the `mementifier` key: +Just open your `config/Coldbox.cfc` and add the following settings into the `moduleSettings` struct under the `mementifier` key or create a new `config/modules/mementifier.cfc` in ColdBox 7: ```js // module settings - stored in modules.name.settings @@ -29,7 +29,9 @@ moduleSettings = { // Don't check for getters before invoking them trustedGetters = false, // If not empty, convert all date/times to the specific timezone - convertToTimezone = "" + convertToTimezone = "", + // Verifies if values are not numeric and isBoolean() and do auto casting to Java Boolean + autoCastBooleans : true } } ``` @@ -74,7 +76,9 @@ this.memento = { mappers = {} ... } - } + }, + // Auto cast boolean strings to Java boolean + autoCastBooleans = true } ``` @@ -122,6 +126,18 @@ string function getAvatarLink( numeric size=40 ){ return variables.avatar.generateLink( getEmail(), arguments.size ); } ``` +##### Includes Aliasing + +You may also wish to alias properties or getters in your components to a different name in the generated memento. You may do this by using a colon with the left hand side as the name of the property or getter ( without the `get` ) and the right hand side as the alias. For example let's say we had a getter of `getLastLoginTime` but we wanted to reference it as `lastLogin` in the memento. We can do this with aliasing. + +```js +defaultIncludes = [ + "firstName", + "lastName", + "avatarLink", + "lastLoginTime:lastLogin" +] +``` ##### Nested Includes @@ -217,7 +233,8 @@ struct function getMemento( boolean iso8601Format, string dateMask, string timeMask, - string profile = "" + string profile = "", + boolean autoCastBooleans = true ) ``` @@ -339,7 +356,8 @@ struct function getMemento( boolean iso8601Format, string dateMask, string timeMask, - string profile = "" + string profile = "", + boolean autoCastBooleans = true ){ // Call mementifier var memento = this.$getMemento( argumentCollection=arguments ); @@ -422,46 +440,26 @@ function process( ){} ``` -## Running The Test Suites - -In order to collaborate on this project you will need to do a few things in order to get the test harness ready for execution. The `test-harness` folder is where the ColdBox test app exists that consumes the module for testing. The `test-harness/tests/specs` is where all the specs for testing are located. - -### Database - -Create a database called `mementifier` in any RDBMS you like. We have mostly used MySQL for the tests. - -This quick Docker command will get you started: - -```bash -docker run --detach \ - --publish 3306:3306 \ - --name mementifier_mysql \ - --env MYSQL_ROOT_PASSWORD=mysql \ - --env MYSQL_DATABASE=mementifier \ - mysql -``` - -### Environment +## Auto Cast Booleans -Copy the `.env.template` as `.env` and modify it accordingly so it can connect to your database. +By default, mementifier will evaluate if the incoming value is not numeric and `isBoolean()` and if so, convert it to a Java `Boolean` so when marshalled it will be a `true` or `false=` in the output json. However we understand this can be annoying or too broad of a stroke, so you can optionally disable it in different levels: -### Dependencies +1. Global Setting +1. Entity Level +1. `getMemento()` Level -Go into the root of `test-harness` and run a CommandBox shell: `box`. Once in the shell install the dependencies `install`. +### Global Setting -### Start a Server +You can set the `autoCastBooleans` global setting in the mementifier settings. -Start a server, we have configured for you several CFML engines for you to test against, pick one from the list below: +### Entity Level -- `server start serverConfigFile=server-adobe@2018.json` -- - `server start serverConfigFile=server-adobe@2021.json` -- `server start serverConfigFile=server-lucee@5.json` +You can set the `autoCastBooleans` property in the `this.memento` struct. -Then you can hit the test site app at http://localhost:60299. This will create the database for you using the ColdFusion ORM. +### `getMemento()` Level -### Running Tests +You can pass in the `autoCastBooleans` argument to the `getMemento()` and use that as the default. -You can then run the tests at http://localhost:60299/tests/runner.cfm ******************************************************************************** Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp diff --git a/test-harness/tests/specs/unit/interceptors/MementifierTest.cfc b/test-harness/tests/specs/unit/interceptors/MementifierTest.cfc index 0f3f9c6..a96d19b 100644 --- a/test-harness/tests/specs/unit/interceptors/MementifierTest.cfc +++ b/test-harness/tests/specs/unit/interceptors/MementifierTest.cfc @@ -21,7 +21,9 @@ component extends="coldbox.system.testing.BaseInterceptorTest" interceptor="meme // Don't check for getters before invoking them trustedGetters : false, // If not empty, convert all date/times to the specific timezone - convertToTimezone : "" + convertToTimezone : "", + // Verifies if values are not numeric and isBoolean() and do auto casting to Java Boolean + autoCastBooleans : true }; variables.interceptor.$property(