├── .cfconfig.json ├── .cfformat.json ├── .env.example ├── .github └── workflows │ ├── cron.yml │ ├── pr.yml │ ├── prerelease.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── ModuleConfig.cfc ├── README.md ├── box.json ├── dsl └── QuickServiceDSL.cfc ├── extras └── QuickCollection.cfc ├── models ├── BaseEntity.cfc ├── BaseService.cfc ├── CBORMCompatEntity.cfc ├── CBORMCriteriaBuilderCompat.cfc ├── Casts │ ├── BooleanCast.cfc │ ├── CastsAttribute.cfc │ └── JsonCast.cfc ├── KeyTypes │ ├── AutoIncrementingKeyType.cfc │ ├── GUIDKeyType.cfc │ ├── KeyType.cfc │ ├── NullKeyType.cfc │ ├── ReturningKeyType.cfc │ ├── RowIDKeyType.cfc │ └── UUIDKeyType.cfc ├── QuickBuilder.cfc ├── QuickQB.cfc └── Relationships │ ├── BaseRelationship.cfc │ ├── BelongsTo.cfc │ ├── BelongsToMany.cfc │ ├── BelongsToThrough.cfc │ ├── Builders │ └── HasManyDeepBuilder.cfc │ ├── HasMany.cfc │ ├── HasManyDeep.cfc │ ├── HasManyThrough.cfc │ ├── HasOne.cfc │ ├── HasOneOrMany.cfc │ ├── HasOneOrManyThrough.cfc │ ├── HasOneThrough.cfc │ ├── IConcatenatableRelationship.cfc │ ├── IRelationship.cfc │ ├── PivotTable.cfc │ ├── PolymorphicBelongsTo.cfc │ ├── PolymorphicHasMany.cfc │ └── PolymorphicHasOneOrMany.cfc ├── quick300.png ├── server.boxlang.json ├── server.json └── tests ├── Application.cfc ├── index.cfm ├── resources ├── ModuleIntegrationSpec.cfc ├── app │ ├── .gitattributes │ ├── Application.cfc │ ├── config │ │ ├── .htaccess │ │ ├── Application.cfc │ │ ├── CacheBox.cfc │ │ ├── Coldbox.cfc │ │ ├── Routes.cfm │ │ └── WireBox.cfc │ ├── favicon.ico │ ├── handlers │ │ └── Main.cfc │ ├── includes │ │ ├── css │ │ │ └── bootstrap.min.css │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ ├── helpers │ │ │ └── ApplicationHelper.cfm │ │ ├── i18n │ │ │ └── i18n_goes_here.txt │ │ ├── images │ │ │ └── ColdBoxLogo2015_300.png │ │ └── js │ │ │ ├── bootstrap.min.js │ │ │ └── jquery.js │ ├── index.cfm │ ├── interceptors │ │ └── PutStuffHere.txt │ ├── layouts │ │ └── Main.cfm │ ├── lib │ │ └── drop_jars_here.txt │ ├── models │ │ ├── A.cfc │ │ ├── Actor.cfc │ │ ├── Address.cfc │ │ ├── AddressCast.cfc │ │ ├── Admin.cfc │ │ ├── B.cfc │ │ ├── BaseProduct.cfc │ │ ├── Category.cfc │ │ ├── CollectionPost.cfc │ │ ├── Comment.cfc │ │ ├── CompatUser.cfc │ │ ├── Composite.cfc │ │ ├── CompositeChild.cfc │ │ ├── Country.cfc │ │ ├── DefaultedKeyEntity.cfc │ │ ├── EagerLoadedPost.cfc │ │ ├── Empty.cfc │ │ ├── EntityWithoutAccessors.cfc │ │ ├── InternalComment.cfc │ │ ├── Jingle.cfc │ │ ├── Link.cfc │ │ ├── Office.cfc │ │ ├── Permission.cfc │ │ ├── PhoneNumber.cfc │ │ ├── Post.cfc │ │ ├── PostAlt.cfc │ │ ├── ProductBook.cfc │ │ ├── ProductMusic.cfc │ │ ├── Purchase.cfc │ │ ├── Referral.cfc │ │ ├── Role.cfc │ │ ├── Song.cfc │ │ ├── Tag.cfc │ │ ├── Team.cfc │ │ ├── Theme.cfc │ │ ├── User.cfc │ │ ├── UserFill.cfc │ │ ├── UserWithGlobalScope.cfc │ │ ├── Video.cfc │ │ ├── externalThing.cfc │ │ ├── inLeague │ │ │ ├── Child.cfc │ │ │ ├── Family.cfc │ │ │ ├── Game.cfc │ │ │ ├── Parent.cfc │ │ │ ├── PlayingField.cfc │ │ │ └── Registration.cfc │ │ ├── products │ │ │ ├── ApparelSKU.cfc │ │ │ ├── Product.cfc │ │ │ └── ProductSKU.cfc │ │ ├── revagency │ │ │ ├── TripPlannerReservationComponent.cfc │ │ │ └── TripPlannerReservationComponentCruise.cfc │ │ └── withRelationshipRegression │ │ │ ├── RMME_A.cfc │ │ │ ├── RMME_B.cfc │ │ │ └── RMME_C.cfc │ ├── modules │ │ └── tracked_modules_here.txt │ ├── modules_app │ │ └── custom_modules_here.txt │ ├── readme.md │ ├── robots.txt │ ├── tests │ │ ├── Application.cfc │ │ ├── index.cfm │ │ ├── resources │ │ │ ├── HttpAntRunner.cfc │ │ │ └── RemoteFacade.cfc │ │ ├── results │ │ │ └── results_go_here.txt │ │ ├── runner.cfm │ │ ├── specs │ │ │ ├── all_tests_go_here.txt │ │ │ ├── integration │ │ │ │ ├── MainBDDTest.cfc │ │ │ │ └── MainTest.cfc │ │ │ └── modules │ │ │ │ ├── integration │ │ │ │ └── integration_tests_go_here.txt │ │ │ │ ├── module_tests_go_here.txt │ │ │ │ └── unit │ │ │ │ └── unit_tests_go_here.txt │ │ └── test.xml │ └── views │ │ ├── _templates │ │ ├── 404.html │ │ └── generic_error.cfm │ │ └── main │ │ ├── index.cfm │ │ └── indexHelper.cfm └── database │ └── migrations │ ├── 2020_08_11_102347_create_countries_table.cfc │ ├── 2020_08_11_102455_create_offices_table.cfc │ ├── 2020_08_11_102500_create_teams_table.cfc │ ├── 2020_08_11_102508_create_permissions_table.cfc │ ├── 2020_08_11_102514_create_roles_table.cfc │ ├── 2020_08_11_102522_create_permissions_roles_table.cfc │ ├── 2020_08_11_102531_create_users_table.cfc │ ├── 2020_08_11_102538_create_externalThings_table.cfc │ ├── 2020_08_11_102549_create_roles_users_table.cfc │ ├── 2020_08_11_102557_create_my_posts_table.cfc │ ├── 2020_08_11_102605_create_videos_table.cfc │ ├── 2020_08_11_102612_create_comments_table.cfc │ ├── 2020_08_11_102613_create_internal_comments_table.cfc │ ├── 2020_08_11_102619_create_tags_table.cfc │ ├── 2020_08_11_102625_create_my_posts_tags_table.cfc │ ├── 2020_08_11_102636_create_links_table.cfc │ ├── 2020_08_11_102640_create_referrals_table.cfc │ ├── 2020_08_11_102649_create_songs_table.cfc │ ├── 2020_08_11_102650_create_jingles_table.cfc │ ├── 2020_08_11_102656_create_phone_numbers_table.cfc │ ├── 2020_08_11_102704_create_empty_table.cfc │ ├── 2020_08_11_102707_create_a_table.cfc │ ├── 2020_08_11_102710_create_b_table.cfc │ ├── 2020_08_11_102718_create_composites_table.cfc │ ├── 2020_08_11_102725_create_composite_children_table.cfc │ ├── 2020_08_11_102734_create_themes_table.cfc │ ├── 2020_08_11_102739_create_parents_table.cfc │ ├── 2020_08_11_102747_create_families_table.cfc │ ├── 2020_08_11_102757_create_family_parents_table.cfc │ ├── 2020_08_11_102803_create_children_table.cfc │ ├── 2020_08_11_102811_create_registrations_table.cfc │ ├── 2020_08_11_102821_create_playing_fields_table.cfc │ ├── 2020_08_11_102835_create_games_table.cfc │ ├── 2022_06_24_102347_create_products_table.cfc │ ├── 2022_10_17_114732_create_rmme_tables.cfc │ ├── 2023_07_18_133601_create_purchases_table.cfc │ ├── 2023_10_25_102757_add_comment_column.cfc │ ├── 2024_01_03_100700_create_products_b_table.cfc │ ├── 2024_01_03_100701_create_product_skus_table.cfc │ ├── 2024_01_03_100702_create_apparel_skus_table.cfc │ ├── 2024_05_30_203551_create_categories_table.cfc │ ├── 2024_09_24_114812_create_trip_components_table.cfc │ ├── 2024_09_24_115820_create_trip_components_cruise_table.cfc │ └── 2025_01_24_145656_create_actors_table.cfc ├── runner.cfm └── specs ├── integration ├── BaseEntity │ ├── AggregateSpec.cfc │ ├── AsQuerySpec.cfc │ ├── AttributeCasingSpec.cfc │ ├── AttributeCastsSpec.cfc │ ├── AttributeHashSpec.cfc │ ├── AttributeSpec.cfc │ ├── ChildClassSpec.cfc │ ├── CloneSpec.cfc │ ├── ColumnsSpec.cfc │ ├── CreateSpec.cfc │ ├── DeleteSpec.cfc │ ├── Events │ │ ├── InstanceReadySpec.cfc │ │ ├── PostDeleteSpec.cfc │ │ ├── PostInsertSpec.cfc │ │ ├── PostLoadSpec.cfc │ │ ├── PostSaveSpec.cfc │ │ ├── PostUpdateSpec.cfc │ │ ├── PreDeleteSpec.cfc │ │ ├── PreInsertSpec.cfc │ │ ├── PreLoadSpec.cfc │ │ ├── PreSaveSpec.cfc │ │ └── PreUpdateSpec.cfc │ ├── FillSpec.cfc │ ├── GUIDPrimaryKeySpec.cfc │ ├── GetSpec.cfc │ ├── GlobalScopeSpec.cfc │ ├── HydrateSpec.cfc │ ├── IsDirtySpec.cfc │ ├── MementoSpec.cfc │ ├── MetadataSpec.cfc │ ├── NullValuesSpec.cfc │ ├── QuerySpec.cfc │ ├── ReadOnlyEntitySpec.cfc │ ├── ReadOnlyPropertySpec.cfc │ ├── Relationships │ │ ├── AsQueryEagerLoadingSpec.cfc │ │ ├── BelongsToManySpec.cfc │ │ ├── BelongsToSpec.cfc │ │ ├── BelongsToThroughSpec.cfc │ │ ├── EagerLoadingSpec.cfc │ │ ├── HasManyDeepBuilderSpec.cfc │ │ ├── HasManyDeepSpec.cfc │ │ ├── HasManySpec.cfc │ │ ├── HasManyThroughSpec.cfc │ │ ├── HasOneSpec.cfc │ │ ├── HasOneThroughSpec.cfc │ │ ├── OrderingByRelationshipsSpec.cfc │ │ ├── PolymorphicBelongsToSpec.cfc │ │ ├── PolymorphicHasManySpec.cfc │ │ ├── QueryingRelationshipsSpec.cfc │ │ ├── RelationshipLoadingSpec.cfc │ │ ├── RelationshipsAggregatesSpec.cfc │ │ └── WithDefaultSpec.cfc │ ├── SaveSpec.cfc │ ├── ScopeSpec.cfc │ ├── SubqueriesSpec.cfc │ ├── UUIDPrimaryKeySpec.cfc │ ├── UpdateAllSpec.cfc │ └── UpdateSpec.cfc ├── BaseServiceSpec.cfc ├── CBORMCompatEntitySpec.cfc ├── GoodErrorMessagesSpec.cfc ├── ModuleCanBeActivedSpec.cfc ├── QuickCollectionSpec.cfc └── Unitlike.cfc └── performance └── EntityCreationSpec.cfc /.cfformat.json: -------------------------------------------------------------------------------- 1 | { 2 | "array.empty_padding": false, 3 | "array.padding": true, 4 | "array.multiline.min_length": 40, 5 | "array.multiline.element_count": 2, 6 | "array.multiline.leading_comma.padding": true, 7 | "array.multiline.leading_comma": false, 8 | "alignment.consecutive.assignments": true, 9 | "alignment.consecutive.properties": true, 10 | "alignment.consecutive.params": true, 11 | "brackets.padding": true, 12 | "comment.asterisks": "align", 13 | "binary_operators.padding": true, 14 | "for_loop_semicolons.padding": true, 15 | "function_call.empty_padding": false, 16 | "function_call.padding": true, 17 | "function_call.multiline.leading_comma.padding": true, 18 | "function_call.casing.builtin": "cfdocs", 19 | "function_call.casing.userdefined": "camel", 20 | "function_call.multiline.element_count": 3, 21 | "function_call.multiline.leading_comma": false, 22 | "function_call.multiline.min_length": 40, 23 | "function_declaration.padding": true, 24 | "function_declaration.empty_padding": false, 25 | "function_declaration.multiline.leading_comma": false, 26 | "function_declaration.multiline.leading_comma.padding": true, 27 | "function_declaration.multiline.element_count": 3, 28 | "function_declaration.multiline.min_length": 40, 29 | "function_declaration.group_to_block_spacing": "spaced", 30 | "function_anonymous.empty_padding": false, 31 | "function_anonymous.group_to_block_spacing": "spaced", 32 | "function_anonymous.multiline.element_count": 3, 33 | "function_anonymous.multiline.leading_comma": false, 34 | "function_anonymous.multiline.leading_comma.padding": true, 35 | "function_anonymous.multiline.min_length": 40, 36 | "function_anonymous.padding": true, 37 | "indent_size": 4, 38 | "keywords.block_to_keyword_spacing": "spaced", 39 | "keywords.group_to_block_spacing": "spaced", 40 | "keywords.padding_inside_group": true, 41 | "keywords.spacing_to_block": "spaced", 42 | "keywords.spacing_to_group": true, 43 | "keywords.empty_group_spacing": false, 44 | "max_columns": 120, 45 | "metadata.multiline.element_count": 3, 46 | "metadata.multiline.min_length": 40, 47 | "newline":"\n", 48 | "property.multiline.element_count": 3, 49 | "property.multiline.min_length": 40, 50 | "parentheses.padding": true, 51 | "strings.quote": "double", 52 | "strings.convertNestedQuotes": false, 53 | "strings.attributes.quote": "double", 54 | "struct.separator": " : ", 55 | "struct.padding": true, 56 | "struct.empty_padding": false, 57 | "struct.multiline.leading_comma": false, 58 | "struct.multiline.leading_comma.padding": true, 59 | "struct.multiline.element_count": 2, 60 | "struct.multiline.min_length": 40, 61 | "tab_indent": true 62 | } 63 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DB_HOST= 2 | DB_PORT= 3 | DB_NAME= 4 | DB_USER= 5 | DB_PASSWORD= 6 | DB_CLASS=com.mysql.jdbc.Driver 7 | DB_BUNDLEVERSION=8.0.19 8 | DB_BUNDLENAME=com.mysql.cj 9 | -------------------------------------------------------------------------------- /.github/workflows/cron.yml: -------------------------------------------------------------------------------- 1 | name: Cron 2 | 3 | on: 4 | schedule: 5 | - cron: 0 0 * * 1 6 | 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-latest 10 | name: Tests 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | cfengine: ["lucee@5", "lucee@6", "lucee@be", "adobe@2023", "adobe@2025", "adobe@be", "boxlang@be"] 15 | coldbox: ["coldbox@7", "coldbox@be"] 16 | javaVersion: ["openjdk8", "openjdk11"] 17 | services: 18 | mysql: 19 | image: mysql:5.7 20 | env: 21 | MYSQL_RANDOM_ROOT_PASSWORD: yes 22 | MYSQL_USER: quick 23 | MYSQL_PASSWORD: quick 24 | MYSQL_DATABASE: quick 25 | ports: 26 | - 3306 27 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 28 | steps: 29 | - name: Checkout Repository 30 | uses: actions/checkout@v2 31 | 32 | - name: Setup Java JDK 33 | uses: actions/setup-java@v1.4.3 34 | with: 35 | java-version: 11 36 | 37 | - name: Set Up CommandBox 38 | uses: Ortus-Solutions/setup-commandbox@v2.0.1 39 | with: 40 | install: commandbox-boxlang 41 | 42 | - name: Install dependencies 43 | run: | 44 | box install 45 | box config set modules.commandbox-dotenv.checkEnvPreServerStart=false 46 | box install ${{ matrix.coldbox }} --noSave 47 | 48 | - name: Start server 49 | env: 50 | DB_HOST: localhost 51 | DB_PORT: ${{ job.services.mysql.ports[3306] }} 52 | DB_NAME: quick 53 | DB_USER: quick 54 | DB_PASSWORD: quick 55 | run: | 56 | if [[ "${{ matrix.cfengine }}" == *"boxlang"* ]] ; then 57 | box server start cfengine=${{ matrix.cfengine }} javaVersion=openjdk21_jdk 58 | box run-script bx-modules:install 59 | box server restart 60 | else 61 | box server start cfengine=${{ matrix.cfengine }} javaVersion=${{ matrix.javaVersion }} 62 | fi 63 | 64 | # Install Adobe 2021 cfpm modules 65 | if [[ "${{ matrix.cfengine }}" == "adobe@2021" ]] ; then 66 | box run-script install:2021 67 | fi 68 | 69 | - name: Run TestBox Tests 70 | env: 71 | DB_HOST: localhost 72 | DB_PORT: ${{ job.services.mysql.ports[3306] }} 73 | DB_NAME: quick 74 | DB_USER: quick 75 | DB_PASSWORD: quick 76 | run: box testbox run -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PRs and Branches 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - "main" 7 | - "master" 8 | - "development" 9 | pull_request: 10 | branches: 11 | - main 12 | - master 13 | - development 14 | 15 | jobs: 16 | tests: 17 | runs-on: ubuntu-latest 18 | name: Tests 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | cfengine: ["lucee@5", "adobe@2023", "adobe@2025", "boxlang@be"] 23 | coldbox: ["coldbox@7", "coldbox@be"] 24 | services: 25 | mysql: 26 | image: mysql:5.7 27 | env: 28 | MYSQL_RANDOM_ROOT_PASSWORD: yes 29 | MYSQL_USER: quick 30 | MYSQL_PASSWORD: quick 31 | MYSQL_DATABASE: quick 32 | ports: 33 | - 3306 34 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 35 | steps: 36 | - name: Checkout Repository 37 | uses: actions/checkout@v2 38 | 39 | - name: Setup Java JDK 40 | uses: actions/setup-java@v1.4.3 41 | with: 42 | java-version: 11 43 | 44 | - name: Set Up CommandBox 45 | uses: Ortus-Solutions/setup-commandbox@v2.0.1 46 | with: 47 | install: commandbox-boxlang 48 | 49 | - name: Install dependencies 50 | run: | 51 | box install 52 | box config set modules.commandbox-dotenv.checkEnvPreServerStart=false 53 | box install ${{ matrix.coldbox }} --noSave 54 | 55 | - name: Start server 56 | env: 57 | DB_HOST: localhost 58 | DB_PORT: ${{ job.services.mysql.ports[3306] }} 59 | DB_NAME: quick 60 | DB_USER: quick 61 | DB_PASSWORD: quick 62 | run: | 63 | if [[ "${{ matrix.cfengine }}" == *"boxlang"* ]] ; then 64 | box server start cfengine=${{ matrix.cfengine }} javaVersion=openjdk21_jdk 65 | box run-script bx-modules:install 66 | box server restart 67 | else 68 | box server start cfengine=${{ matrix.cfengine }} 69 | fi 70 | 71 | # Install Adobe 2021 cfpm modules 72 | if [[ "${{ matrix.cfengine }}" == "adobe@2021" ]] ; then 73 | box run-script install:2021 74 | fi 75 | 76 | - name: Run TestBox Tests 77 | env: 78 | DB_HOST: localhost 79 | DB_PORT: ${{ job.services.mysql.ports[3306] }} 80 | DB_NAME: quick 81 | DB_USER: quick 82 | DB_PASSWORD: quick 83 | run: box testbox run 84 | 85 | format: 86 | runs-on: ubuntu-latest 87 | name: Format 88 | steps: 89 | - name: Checkout Repository 90 | uses: actions/checkout@v2 91 | 92 | - name: Setup Java JDK 93 | uses: actions/setup-java@v1.4.3 94 | with: 95 | java-version: 11 96 | 97 | - name: Set Up CommandBox 98 | uses: Ortus-Solutions/setup-commandbox@v2.0.1 99 | with: 100 | install: commandbox-boxlang 101 | 102 | - name: Install CFFormat 103 | run: box install commandbox-cfformat 104 | 105 | - name: Run CFFormat 106 | run: box run-script format 107 | 108 | - name: Commit Format Changes 109 | uses: stefanzweifel/git-auto-commit-action@v4 110 | with: 111 | commit_message: Apply cfformat changes -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /testbox 2 | /tests/results 3 | /tests/resources/app/coldbox 4 | /node_modules 5 | /modules 6 | 7 | .vscode 8 | !.engine/ 9 | .engine/* 10 | !.engine/WEB-INF/ 11 | .engine/WEB-INF/* 12 | !.engine/WEB-INF/lib 13 | .engine/WEB-INF/lib/* 14 | !.engine/WEB-INF/lib/h2-1.4.196.jar 15 | 16 | .env 17 | .tmp 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Eric Peterson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ModuleConfig.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | this.name = "quick"; 4 | this.author = "Eric Peterson"; 5 | this.webUrl = "https://github.com/coldbox-modules/quick"; 6 | this.dependencies = [ "qb", "str", "mementifier" ]; 7 | this.cfmapping = "quick"; 8 | 9 | function configure() { 10 | settings = { 11 | "defaultGrammar" : "AutoDiscover@qb", 12 | "defaultQueryOptions" : {}, 13 | "preventDuplicateJoins" : true, 14 | "preventLazyLoading" : false, 15 | "lazyLoadingViolationCallback" : ( entity, relationName ) => { 16 | throw( 17 | type = "QuickLazyLoadingException", 18 | message = "Attempted to lazy load the [#arguments.relationName#] relationship on the entity [#arguments.entity.mappingName()#] but lazy loading is disabled. This is usually caused by the N+1 problem and is a sign that you are missing an eager load." 19 | ); 20 | }, 21 | "metadataCache" : { 22 | "name" : "quickMeta", 23 | "provider" : "coldbox.system.cache.providers.CacheBoxColdBoxProvider", 24 | "properties" : { 25 | "objectDefaultTimeout" : 0, // no timeout 26 | "useLastAccessTimeouts" : false, // no last access timeout 27 | "maxObjects" : 300, 28 | "objectStore" : "ConcurrentStore" 29 | } 30 | } 31 | }; 32 | 33 | interceptorSettings = { 34 | customInterceptionPoints : [ 35 | "quickInstanceReady", 36 | "quickPreLoad", 37 | "quickPostLoad", 38 | "quickPreSave", 39 | "quickPostSave", 40 | "quickPreInsert", 41 | "quickPostInsert", 42 | "quickPreUpdate", 43 | "quickPostUpdate", 44 | "quickPreDelete", 45 | "quickPostDelete" 46 | ] 47 | }; 48 | 49 | binder.map( "quick.models.BaseEntity" ).to( "#moduleMapping#.models.BaseEntity" ); 50 | 51 | binder.getInjector().registerDSL( "quickService", "#moduleMapping#.dsl.QuickServiceDSL" ); 52 | } 53 | 54 | function onLoad() { 55 | binder 56 | .map( alias = "QuickQB@quick", force = true ) 57 | .to( "#moduleMapping#.models.QuickQB" ) 58 | .initArg( name = "grammar", dsl = settings.defaultGrammar ) 59 | .initArg( name = "preventDuplicateJoins", value = settings.preventDuplicateJoins ) 60 | .initArg( name = "defaultOptions", value = settings.defaultQueryOptions ) 61 | .initArg( name = "utils", dsl = "QueryUtils@qb" ) 62 | .initArg( name = "sqlCommenter", ref = "ColdBoxSQLCommenter@qb" ) 63 | .initArg( name = "returnFormat", value = "array" ); 64 | 65 | if ( isSimpleValue( settings.metadataCache ) ) { 66 | settings.metadataCache = { "name" : settings.metadataCache }; 67 | } 68 | 69 | if ( settings.metadataCache.name != "quickMeta" ) { 70 | binder.map( "cachebox:quickMeta" ).toDSL( "cachebox:#settings.metadataCache.name#" ); 71 | } else { 72 | wirebox.getCachebox().createCache( argumentCollection = settings.metadataCache ); 73 | } 74 | } 75 | 76 | function onUnload() { 77 | wirebox 78 | .getCachebox() 79 | .getCache( settings.metadataCache.name ) 80 | .clearAll(); 81 | } 82 | 83 | 84 | /** 85 | * This interceptor ensures that the `_mapping` property for a Quick entity 86 | * is correctly set to the mapping used in WireBox. 87 | * 88 | * @event The current request context 89 | * @interceptData The intercept data for `afterInstanceAutowire`. 90 | * { mapping, target, targetID, injector } 91 | */ 92 | function beforeInstanceAutowire( event, interceptData ) { 93 | if ( structKeyExists( interceptData.target, "isQuickEntity" ) ) { 94 | interceptData.target.set_mapping( interceptData.targetID ); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"quick", 3 | "version":"11.3.0", 4 | "author":"Ortus Solutions", 5 | "location":"forgeboxStorage", 6 | "homepage":"https://github.com/coldbox-modules/quick", 7 | "documentation":"https://quick.ortusbooks.com", 8 | "repository":{ 9 | "type":"git", 10 | "URL":"https://github.com/coldbox-modules/quick" 11 | }, 12 | "license":[ 13 | { 14 | "type":"MIT", 15 | "URL":"https://github.com/coldbox-modules/quick/LICENSE" 16 | } 17 | ], 18 | "bugs":"https://github.com/coldbox-modules/quick/issues", 19 | "slug":"quick", 20 | "shortDescription":"A ColdBox ORM Engine", 21 | "description":"A ColdBox ORM Engine", 22 | "scripts":{ 23 | "format":"cfformat run dsl/**/*.cfc,extras/**/*.cfc,models/**/*.cfc,tests/specs/**/*.cfc --overwrite", 24 | "format:check":"cfformat check dsl/**/*.cfc,extras/**/*.cfc,models/**/*.cfc,tests/specs/**/*.cfc --verbose", 25 | "format:watch":"cfformat watch dsl/**/*.cfc,extras/**/*.cfc,models/**/*.cfc,tests/specs/**/*.cfc", 26 | "generateAPIDocs":"rm .tmp --recurse --force && docbox generate mapping=quick excludes=test|/modules|ModuleConfig|QuickCollection strategy-outputDir=.tmp/apidocs strategy-projectTitle=Quick", 27 | "install:2021":"cfpm install document,feed,mysql,zip", 28 | "bx-modules:install":"install bx-compat-cfml@be,bx-esapi,bx-mysql" 29 | }, 30 | "type":"modules", 31 | "dependencies":{ 32 | "qb":"^11.0.2", 33 | "str":"^4.0.0", 34 | "mementifier":"^3.0.0" 35 | }, 36 | "devDependencies":{ 37 | "coldbox":"v7.3.0", 38 | "testbox":"be", 39 | "cfcollection":"^3.6.4", 40 | "cfmigrations":"^4.0.0" 41 | }, 42 | "installPaths":{ 43 | "testbox":"testbox/", 44 | "coldbox":"tests/resources/app/coldbox/", 45 | "qb":"modules/qb/", 46 | "str":"modules/str/", 47 | "cbvalidation":"modules/cbvalidation/", 48 | "cfcollection":"modules/cfcollection/", 49 | "mementifier":"modules/mementifier/", 50 | "cfmigrations":"modules/cfmigrations/" 51 | }, 52 | "testbox":{ 53 | "reporter":"json", 54 | "runner":"http://127.0.0.1:52482/tests/runner.cfm", 55 | "verbose":"false", 56 | "watchDelay":"1000" 57 | }, 58 | "ignore":[ 59 | "**/.*", 60 | "test", 61 | "tests", 62 | "server.json", 63 | "quick300.png" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /dsl/QuickServiceDSL.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * Processes WireBox DSL's starting with "quickService:" 3 | */ 4 | component { 5 | 6 | /** 7 | * Creates the Quick Service DSL Processor. 8 | * 9 | * @injector The WireBox injector. 10 | * 11 | * @return QuickServiceDSL 12 | */ 13 | public QuickServiceDSL function init( required Injector injector ) { 14 | variables.injector = arguments.injector; 15 | return this; 16 | } 17 | 18 | /** 19 | * Creates a Quick BaseService from the dsl. 20 | * The portion after the colon is used as the entity mapping. 21 | * 22 | * @definition The dsl struct definition. 23 | * 24 | * @return BaseService or extending component. 25 | */ 26 | public any function process( required struct definition ) { 27 | return variables.injector.getInstance( 28 | name = "BaseService@quick", 29 | initArguments = { entity : variables.injector.getInstance( listRest( arguments.definition.dsl, ":" ) ) } 30 | ); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /extras/QuickCollection.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * QuickCollection extends CFCollection with some nice additions. 3 | * However, since CFCollection has some performance issues on some engines 4 | * due to supporting old engines, it is included in the extras. 5 | */ 6 | component extends="cfcollection.models.Collection" { 7 | 8 | /** 9 | * Returns a new QuickCollection for the passed in data. 10 | * 11 | * @data The data to collect. 12 | * 13 | * @return QuickCollection 14 | */ 15 | public QuickCollection function collect( required any data ) { 16 | return new QuickCollection( arguments.data ); 17 | } 18 | 19 | /** 20 | * Eager loads the given relation or array of relations. 21 | * Nested relations can be loaded using dot-notation ("posts.comments"). 22 | * The current collection (now with the relation loaded) is returned. 23 | * 24 | * @relationName The relation to load. It can be passed a single relation 25 | * or an array of relations. Nested relations can be loaded 26 | * using dot-notation ("posts.comments"). 27 | * 28 | * @return QuickCollection 29 | */ 30 | public QuickCollection function load( required any relationName ) { 31 | if ( this.empty() ) { 32 | return this; 33 | } 34 | 35 | if ( !isArray( arguments.relationName ) ) { 36 | arguments.relationName = [ arguments.relationName ]; 37 | } 38 | 39 | for ( var relation in arguments.relationName ) { 40 | variables.eagerLoadRelation( relation ); 41 | } 42 | 43 | return this; 44 | } 45 | 46 | /** 47 | * Returns an array of each item's mementos. 48 | * 49 | * @return [any] 50 | */ 51 | public array function getMemento() { 52 | return this 53 | .map( function( entity ) { 54 | return arguments.entity.$renderData(); 55 | } ) 56 | .get(); 57 | } 58 | 59 | /** 60 | * ColdBox magic method to return the result of the `getMemento` call 61 | * when returning a QuickCollection directly from a handler. 62 | * 63 | * @return [any] 64 | */ 65 | function $renderData() { 66 | return variables.getMemento(); 67 | } 68 | 69 | /** 70 | * Eager loads a single relation for the entities in the collection. 71 | * This is useful if you later want to eager load based on some condition 72 | * rather than when retrieving the results initially. 73 | * 74 | * @relationName The relation to load. 75 | * 76 | * @return void 77 | */ 78 | private void function eagerLoadRelation( required string relationName ) { 79 | var relation = invoke( get( 1 ), arguments.relationName ).resetQuery(); 80 | var hasMatches = relation.addEagerConstraints( get(), get( 1 ) ); 81 | variables.collection = relation.match( 82 | relation.initRelation( get(), arguments.relationName ), 83 | hasMatches ? relation.getEager() : [], 84 | arguments.relationName 85 | ); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /models/BaseService.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is a thin wrapper around an entity. It is useful in situations 3 | * where you want to inject a component as a singleton. It would not be 4 | * needed if CFML engines had 1) static support and 2) onMissingStaticMethod 5 | * 6 | * Most services used will be created using the custom WireBox DSL: 7 | * 8 | * ``` 9 | * property name="userService" inject="quickService:User"; 10 | * ``` 11 | */ 12 | component transientCache="false" { 13 | 14 | /** 15 | * The WireBox injector. Used to inject other entities. 16 | */ 17 | property name="wirebox" inject="wirebox"; 18 | 19 | /** 20 | * The entity component that calls will be proxied to. 21 | */ 22 | property name="entity"; 23 | 24 | /** 25 | * Returns a new BaseService with the given entity. 26 | * 27 | * @entity Either a WireBox mapping to an entity or an instance of an entity. 28 | * 29 | * @return quick.models.BaseService 30 | */ 31 | public any function init( required any entity ) { 32 | variables.entity = arguments.entity; 33 | return this; 34 | } 35 | 36 | /** 37 | * Ensures the entity is a component instance if a mapping was passed in. 38 | */ 39 | public void function onDIComplete() { 40 | if ( isSimpleValue( variables.entity ) ) { 41 | variables.entity = wirebox.getInstance( variables.entity ); 42 | } 43 | } 44 | 45 | /** 46 | * Forwards on method calls to a fresh version of the entity. 47 | * 48 | * @missingMethodName The method name being called. 49 | * @missingMethodArguments The method arguments sent. 50 | * 51 | * @return any 52 | */ 53 | public any function onMissingMethod( required string missingMethodName, struct missingMethodArguments = {} ) { 54 | return invoke( 55 | variables.entity.newEntity(), 56 | missingMethodName, 57 | missingMethodArguments 58 | ); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /models/Casts/BooleanCast.cfc: -------------------------------------------------------------------------------- 1 | component singleton { 2 | 3 | /** 4 | * Casts the given value from the database to the target cast type. 5 | * 6 | * @entity The entity with the attribute being casted. 7 | * @key The attribute alias name. 8 | * @value The value of the attribute. 9 | * 10 | * @return The casted attribute. 11 | */ 12 | public any function get( 13 | required any entity, 14 | required string key, 15 | any value 16 | ) { 17 | return isNull( arguments.value ) ? false : booleanFormat( arguments.value ); 18 | } 19 | 20 | /** 21 | * Returns the value to assign to the key before saving to the database. 22 | * 23 | * @entity The entity with the attribute being casted. 24 | * @key The attribute alias name. 25 | * @value The value of the attribute. 26 | * 27 | * @return The value to save to the database. A struct of values 28 | * can be returned if the cast value affects multiple attributes. 29 | */ 30 | public any function set( 31 | required any entity, 32 | required string key, 33 | any value 34 | ) { 35 | return arguments.value ? 1 : 0; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /models/Casts/CastsAttribute.cfc: -------------------------------------------------------------------------------- 1 | interface displayname="CastsAttribute" { 2 | 3 | /** 4 | * Casts the given value from the database to the target cast type. 5 | * 6 | * @entity The entity with the attribute being casted. 7 | * @key The attribute alias name. 8 | * @value The value of the attribute. 9 | * 10 | * @return The casted attribute. 11 | */ 12 | public any function get( 13 | required any entity, 14 | required string key, 15 | any value 16 | ); 17 | 18 | /** 19 | * Returns the value to assign to the key before saving to the database. 20 | * 21 | * @entity The entity with the attribute being casted. 22 | * @key The attribute alias name. 23 | * @value The value of the attribute. 24 | * 25 | * @return The value to save to the database. A struct of values 26 | * can be returned if the cast value affects multiple attributes. 27 | */ 28 | public any function set( 29 | required any entity, 30 | required string key, 31 | any value 32 | ); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /models/Casts/JsonCast.cfc: -------------------------------------------------------------------------------- 1 | component singleton { 2 | 3 | /** 4 | * Casts the given value from the database to the target cast type. 5 | * 6 | * @entity The entity with the attribute being casted. 7 | * @key The attribute alias name. 8 | * @value The value of the attribute. 9 | * 10 | * @return The casted attribute. 11 | */ 12 | public any function get( 13 | required any entity, 14 | required string key, 15 | any value 16 | ) { 17 | if ( isNull( arguments.value ) ) { 18 | return javacast( "null", "" ); 19 | } 20 | 21 | if ( isJSON( arguments.value ) ) { 22 | return deserializeJSON( arguments.value ); 23 | } 24 | 25 | return arguments.value; 26 | } 27 | 28 | /** 29 | * Returns the value to assign to the key before saving to the database. 30 | * 31 | * @entity The entity with the attribute being casted. 32 | * @key The attribute alias name. 33 | * @value The value of the attribute. 34 | * 35 | * @return The value to save to the database. A struct of values 36 | * can be returned if the cast value affects multiple attributes. 37 | */ 38 | public any function set( 39 | required any entity, 40 | required string key, 41 | any value 42 | ) { 43 | return isNull( arguments.value ) ? javacast( "null", "" ) : serializeJSON( arguments.value ); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /models/KeyTypes/AutoIncrementingKeyType.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * Handles the key being set and returned by the database under the 3 | * `generated_key` key in the query result. 4 | */ 5 | component implements="KeyType" { 6 | 7 | /** 8 | * Does nothing as the key will be set by the database. 9 | * 10 | * @entity The entity that is being inserted. 11 | * @builder The builder that is doing the inserting. 12 | * 13 | * @return void 14 | */ 15 | public void function preInsert( required any entity, required any builder ) { 16 | if ( arguments.entity.keyNames().len() > 1 ) { 17 | throw( 18 | type = "InvalidKeyLength", 19 | message = "AutoIncrementingKeyType cannot be used with composite primary keys." 20 | ); 21 | } 22 | return; 23 | } 24 | 25 | /** 26 | * Sets the returned `generated_key` as the key value in the database. 27 | * 28 | * @entity The entity that was inserted. 29 | * @result The result of the queryExecute call. 30 | * 31 | * @return void 32 | */ 33 | public void function postInsert( required any entity, required struct result ) { 34 | var keyName = arguments.entity.keyNames()[ 1 ]; 35 | var generatedKey = arguments.result.result.keyExists( keyName ) ? arguments.result.result[ keyName ] : arguments.result.result.keyExists( 36 | "generated_key" 37 | ) ? arguments.result.result[ "generated_key" ] : arguments.result.result[ "generatedKey" ]; 38 | arguments.entity.assignAttribute( keyName, int( val( generatedKey ) ) ); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /models/KeyTypes/GUIDKeyType.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * Sets the primary key to a random GUID before inserting it into the database. 3 | */ 4 | component implements="KeyType" { 5 | 6 | /** 7 | * Sets the primary keys to random GUIDs. 8 | * 9 | * @entity The entity that is being inserted. 10 | * @builder The builder that is doing the inserting. 11 | * 12 | * @return void 13 | */ 14 | public void function preInsert( required any entity, required any builder ) { 15 | arguments.entity 16 | .keyNames() 17 | .each( function( keyName ) { 18 | if ( entity.isNullAttribute( keyName ) ) { 19 | entity.assignAttribute( keyName, createGUID() ); 20 | } 21 | } ); 22 | } 23 | 24 | /** 25 | * Does nothing as the key was set before inserting into the database 26 | * and the database should not have modified it. 27 | * 28 | * @entity The entity that was inserted. 29 | * @result The result of the queryExecute call. 30 | * 31 | * @return void 32 | */ 33 | public void function postInsert( required any entity, required struct result ) { 34 | return; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /models/KeyTypes/KeyType.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines the logic to interact with primary keys when creating entities. 3 | */ 4 | interface displayname="KeyType" { 5 | 6 | /** 7 | * Called to handle any tasks before inserting into the database. 8 | * Receives the entity as the only argument. 9 | * 10 | * @entity The entity that is being inserted. 11 | * @builder The builder that is doing the inserting. 12 | * 13 | * @return void 14 | */ 15 | public void function preInsert( required any entity, required any builder ); 16 | 17 | /** 18 | * Called to handle any tasks after inserting into the database. 19 | * Receives the entity and the queryExecute result as arguments. 20 | * 21 | * @entity The entity that was inserted. 22 | * @result The result of the queryExecute call. 23 | * 24 | * @return void 25 | */ 26 | public void function postInsert( required any entity, required struct result ); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /models/KeyTypes/NullKeyType.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * The NullKeyType expects the key to be handled completely by the developer. 3 | * It does nothing pre- or post- insert. 4 | */ 5 | component implements="KeyType" { 6 | 7 | /** 8 | * Does nothing as the key should already be set at this point. 9 | * 10 | * @entity The entity that is being inserted. 11 | * @builder The builder that is doing the inserting. 12 | * 13 | * @return void 14 | */ 15 | public void function preInsert( required any entity, required any builder ) { 16 | return; 17 | } 18 | 19 | /** 20 | * Does nothing as the key should not have been altered by the database. 21 | * 22 | * @entity The entity that was inserted. 23 | * @result The result of the queryExecute call. 24 | * 25 | * @return void 26 | */ 27 | public void function postInsert( required any entity, required struct result ) { 28 | return; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /models/KeyTypes/ReturningKeyType.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * Handles the key being set and returned by the database using the 3 | * `RETURNING` functionality of select databases. 4 | */ 5 | component implements="KeyType" { 6 | 7 | /** 8 | * Adds a RETURNING clause for the primary key to the query. 9 | * 10 | * @entity The entity that is being inserted. 11 | * 12 | * @return void 13 | */ 14 | public void function preInsert( required any entity, required any builder ) { 15 | arguments.builder.returning( arguments.entity.keyColumns() ); 16 | } 17 | 18 | /** 19 | * Sets the primary key equal to the returned value. 20 | * 21 | * @entity The entity that was inserted. 22 | * @result The result of the queryExecute call. 23 | * 24 | * @return void 25 | */ 26 | public void function postInsert( required any entity, required struct result ) { 27 | arguments.entity 28 | .keyColumns() 29 | .each( function( keyColumn ) { 30 | entity.assignAttribute( keyColumn, result.query[ keyColumn ] ); 31 | } ); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /models/KeyTypes/RowIDKeyType.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * Handles the key being set by the database but not returned in the response. 3 | * It queries the database based on the ROWID returned to get the actual primary key. 4 | */ 5 | component implements="KeyType" { 6 | 7 | /** 8 | * Does nothing as the key will be set by the database. 9 | * 10 | * @entity The entity that is being inserted. 11 | * @builder The builder that is doing the inserting. 12 | * 13 | * @return void 14 | */ 15 | public void function preInsert( required any entity, required any builder ) { 16 | if ( arguments.entity.keyNames().len() > 1 ) { 17 | throw( type = "InvalidKeyLength", message = "RowIDKeyType cannot be used with composite primary keys." ); 18 | } 19 | return; 20 | } 21 | 22 | /** 23 | * Sets the primary key after fetching the record by the ROWID 24 | * 25 | * @entity The entity that was inserted. 26 | * @result The result of the queryExecute call. 27 | * 28 | * @return void 29 | */ 30 | public void function postInsert( required any entity, required struct result ) { 31 | var keyName = arguments.entity.keyNames()[ 1 ]; 32 | var rowID = arguments.result.result.keyExists( keyName ) ? arguments.result.result[ keyName ] : arguments.result.result.keyExists( 33 | "generated_key" 34 | ) ? arguments.result.result[ "generated_key" ] : arguments.result.result[ "generatedKey" ]; 35 | var generatedKey = arguments.entity 36 | .newQuery() 37 | .retrieveQuery() 38 | .where( "ROWID", rowID ) 39 | .value( keyName ); 40 | arguments.entity.assignAttribute( keyName, generatedKey ); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /models/KeyTypes/UUIDKeyType.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * Sets the primary key to a random UUID before inserting it into the database. 3 | */ 4 | component implements="KeyType" { 5 | 6 | /** 7 | * Sets the primary keys to random UUIDs. 8 | * 9 | * @entity The entity that is being inserted. 10 | * @builder The builder that is doing the inserting. 11 | * 12 | * @return void 13 | */ 14 | public void function preInsert( required any entity, required any builder ) { 15 | arguments.entity 16 | .keyNames() 17 | .each( function( keyName ) { 18 | if ( entity.isNullAttribute( keyName ) ) { 19 | entity.assignAttribute( keyName, createUUID() ); 20 | } 21 | } ); 22 | } 23 | 24 | /** 25 | * Does nothing as the key was set before inserting into the database 26 | * and the database should not have modified it. 27 | * 28 | * @entity The entity that was inserted. 29 | * @result The result of the queryExecute call. 30 | * 31 | * @return void 32 | */ 33 | public void function postInsert( required any entity, required struct result ) { 34 | return; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /models/Relationships/HasMany.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a hasMany relationship. 3 | * 4 | * This is a relationship where the parent entity has zero or more of the related 5 | * entity. The inverse of this relationship is a `belongsTo` relationship. 6 | * 7 | * For instance, a `User` may have zero or more `Post` associated to them. 8 | * This would be modeled in Quick by adding a method to the `User` entity 9 | * that returns a `HasMany` relationship instance. 10 | * 11 | * ``` 12 | * function posts() { 13 | * returns hasMany( "Post" ); 14 | * } 15 | * ``` 16 | */ 17 | component extends="quick.models.Relationships.HasOneOrMany" accessors="true" { 18 | 19 | /** 20 | * Returns the result of the relationship. 21 | * 22 | * @doc_generic quick.models.BaseEntity 23 | * @return [quick.models.BaseEntity] 24 | */ 25 | public array function getResults() { 26 | return variables.relationshipBuilder.get(); 27 | } 28 | 29 | /** 30 | * Initializes the relation to the null value for each entity in an array. 31 | * 32 | * @entities The entities to initialize the relation. 33 | * @relation The name of the relation to initialize. 34 | * 35 | * @doc_generic quick.models.BaseEntity 36 | * @return [quick.models.BaseEntity] 37 | */ 38 | public array function initRelation( required array entities, required string relation ) { 39 | return arguments.entities.map( function( entity ) { 40 | if ( structKeyExists( arguments.entity, "isQuickEntity" ) ) { 41 | arguments.entity.assignRelationship( relation, [] ); 42 | } else { 43 | arguments.entity[ relation ] = []; 44 | } 45 | return arguments.entity; 46 | } ); 47 | } 48 | 49 | /** 50 | * Matches the array of entity results to an array of entities for a relation. 51 | * Any matched records are populated into the matched entity's relation. 52 | * 53 | * @entities The entities being eager loaded. 54 | * @results The relationship results. 55 | * @relation The relation name being loaded. 56 | * 57 | * @doc_generic quick.models.BaseEntity 58 | * @return [quick.models.BaseEntity] 59 | */ 60 | public array function match( 61 | required array entities, 62 | required array results, 63 | required string relation 64 | ) { 65 | return matchMany( argumentCollection = arguments ); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /models/Relationships/HasManyThrough.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a hasOne relationship. 3 | * 4 | * This is a relationship where the parent entity has exactly zero or one of 5 | * the related entity. The inverse of this relationship is also a `hasOne` 6 | * relationship. 7 | * 8 | * For instance, a `User` may have zero or one `UserProfile` associated to them. 9 | * This would be modeled in Quick by adding a method to the `User` entity 10 | * that returns a `HasOne` relationship instance. 11 | * 12 | * ``` 13 | * function profile() { 14 | * returns hasOne( "UserProfile" ); 15 | * } 16 | * ``` 17 | */ 18 | component extends="quick.models.Relationships.HasOneOrManyThrough" { 19 | 20 | /** 21 | * Returns the result of the relationship. 22 | * 23 | * @doc_generic quick.models.BaseEntity 24 | * @return [quick.models.BaseEntity] 25 | */ 26 | public array function getResults() { 27 | return variables.relationshipBuilder.get(); 28 | } 29 | 30 | /** 31 | * Initializes the relation to the null value for each entity in an array. 32 | * 33 | * @entities The entities to initialize the relation. 34 | * @relation The name of the relation to initialize. 35 | * 36 | * @doc_generic quick.models.BaseEntity 37 | * @return [quick.models.BaseEntity] 38 | */ 39 | public array function initRelation( required array entities, required string relation ) { 40 | return arguments.entities.map( function( entity ) { 41 | if ( structKeyExists( arguments.entity, "isQuickEntity" ) ) { 42 | arguments.entity.assignRelationship( relation, [] ); 43 | } else { 44 | arguments.entity[ relation ] = []; 45 | } 46 | return arguments.entity; 47 | } ); 48 | } 49 | 50 | /** 51 | * Matches the array of entity results to an array of entities for a relation. 52 | * Any matched records are populated into the matched entity's relation. 53 | * 54 | * @entities The entities being eager loaded. 55 | * @results The relationship results. 56 | * @relation The relation name being loaded. 57 | * 58 | * @doc_generic quick.models.BaseEntity 59 | * @return [quick.models.BaseEntity] 60 | */ 61 | public array function match( 62 | required array entities, 63 | required array results, 64 | required string relation 65 | ) { 66 | var dictionary = buildDictionary( arguments.results ); 67 | arguments.entities.each( function( entity ) { 68 | var key = variables.closestToParent 69 | .getLocalKeys() 70 | .map( function( localKey ) { 71 | return structKeyExists( entity, "isQuickEntity" ) ? entity.retrieveAttribute( localKey ) : entity[ 72 | localKey 73 | ]; 74 | } ) 75 | .toList(); 76 | if ( structKeyExists( dictionary, key ) ) { 77 | if ( structKeyExists( entity, "isQuickEntity" ) ) { 78 | entity.assignRelationship( relation, dictionary[ key ] ); 79 | } else { 80 | entity[ relation ] = dictionary[ key ]; 81 | } 82 | } 83 | } ); 84 | return arguments.entities; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /models/Relationships/IConcatenatableRelationship.cfc: -------------------------------------------------------------------------------- 1 | interface displayname="IConcatenatableRelationship" { 2 | 3 | public struct function appendToDeepRelationship( 4 | required array through, 5 | required array foreignKeys, 6 | required array localKeys, 7 | required numeric position 8 | ); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /models/Relationships/PivotTable.cfc: -------------------------------------------------------------------------------- 1 | component accessors="true" { 2 | 3 | property name="table" type="string"; 4 | property name="alias" type="string"; 5 | 6 | /** 7 | * Used to quickly identify QueryBuilder instances 8 | * instead of resorting to `isInstanceOf` which is slow. 9 | */ 10 | this.isBuilder = true; 11 | 12 | /** 13 | * Used to quickly identify QuickBuilder instances 14 | * instead of resorting to `isInstanceOf` which is slow. 15 | */ 16 | this.isQuickBuilder = true; 17 | 18 | /** 19 | * Used to quickly identify PivotTable instances 20 | * instead of resorting to `isInstanceOf` which is slow. 21 | */ 22 | this.isPivotTable = true; 23 | 24 | public string function tableName() { 25 | return variables.table; 26 | } 27 | 28 | public string function tableAlias() { 29 | return listLen( variables.table, " " ) > 1 ? listLast( variables.table, " " ) : variables.table; 30 | } 31 | 32 | public PivotTable function withAlias( required string alias ) { 33 | variables.alias = arguments.alias; 34 | return this; 35 | } 36 | 37 | public string function qualifyColumn( required string columnName ) { 38 | if ( findNoCase( ".", columnName ) > 0 ) { 39 | return columnName; 40 | } 41 | 42 | return isNull( variables.alias ) ? "#variables.table#.#arguments.columnName#" : "#variables.alias#.#arguments.columnName#"; 43 | } 44 | 45 | public array function getWheres() { 46 | return []; 47 | } 48 | 49 | public struct function getRawBindings() { 50 | return { "where" : [] }; 51 | } 52 | 53 | public PivotTable function getEntity() { 54 | return this; 55 | } 56 | 57 | public struct function getAliasMap() { 58 | return {}; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /quick300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/quick300.png -------------------------------------------------------------------------------- /server.boxlang.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"quick-boxlang", 3 | "app":{ 4 | "cfengine":"lucee@6" 5 | }, 6 | "JVM":{ 7 | "javaVersion":"openjdk21" 8 | }, 9 | "web":{ 10 | "http":{ 11 | "port":"52485" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server.json: -------------------------------------------------------------------------------- 1 | { 2 | "app":{ 3 | "cfengine":"adobe@2023" 4 | }, 5 | "JVM":{ 6 | "javaVersion":"openjdk21" 7 | }, 8 | "web":{ 9 | "http":{ 10 | "port":"52482" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Application.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | this.name = "ColdBoxTestingSuite" & hash(getCurrentTemplatePath()); 4 | this.sessionManagement = true; 5 | this.setClientCookies = true; 6 | this.sessionTimeout = createTimeSpan( 0, 0, 15, 0 ); 7 | this.applicationTimeout = createTimeSpan( 0, 0, 15, 0 ); 8 | this.timezone = "UTC"; 9 | 10 | // Turn on/off white space management 11 | this.whiteSpaceManagement = "smart"; 12 | 13 | testsPath = getDirectoryFromPath( getCurrentTemplatePath() ); 14 | this.mappings[ "/tests" ] = testsPath; 15 | rootPath = REReplaceNoCase( this.mappings[ "/tests" ], "tests(\\|/)", "" ); 16 | this.mappings[ "/root" ] = rootPath; 17 | this.mappings[ "/testingModuleRoot" ] = listDeleteAt( rootPath, listLen( rootPath, '\/' ), "\/" ); 18 | this.mappings[ "/quick" ] = listDeleteAt( rootPath, listLen( rootPath, '\/' ), "\/" ); 19 | this.mappings[ "/qb" ] = rootPath & "/modules/qb"; 20 | this.mappings[ "/app" ] = testsPath & "resources/app"; 21 | this.mappings[ "/coldbox" ] = testsPath & "resources/app/coldbox"; 22 | this.mappings[ "/testbox" ] = rootPath & "/testbox"; 23 | 24 | this.datasource = "quick"; 25 | 26 | function onApplicationStart() { 27 | param url.reloadDatabase = true; 28 | } 29 | 30 | function onRequestStart() { 31 | setting requestTimeout="180"; 32 | 33 | // New ColdBox Virtual Application Starter 34 | request.coldBoxVirtualApp = new coldbox.system.testing.VirtualApp( appMapping = "/app" ); 35 | 36 | // If hitting the runner or specs, prep our virtual app and database 37 | if ( getBaseTemplatePath().replace( expandPath( "/tests" ), "" ).reFindNoCase( "(runner|specs)" ) ) { 38 | request.coldBoxVirtualApp.startup(); 39 | } 40 | 41 | // ORM Reload for fresh results 42 | if( structKeyExists( url, "fwreinit" ) || structKeyExists( url, "reloadDatabase" )){ 43 | if( structKeyExists( server, "lucee" ) ){ 44 | pagePoolClear(); 45 | } 46 | request.coldBoxVirtualApp.restart(); 47 | } 48 | 49 | return true; 50 | } 51 | 52 | public void function onRequestEnd( required targetPage ) { 53 | request.coldBoxVirtualApp.shutdown(); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /tests/resources/ModuleIntegrationSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="coldbox.system.testing.BaseTestCase" appMapping="/app" { 2 | 3 | /*********************************** LIFE CYCLE Methods ***********************************/ 4 | 5 | function beforeAll() { 6 | super.beforeAll(); 7 | 8 | getController().getModuleService().registerAndActivateModule( "quick", "testingModuleRoot" ); 9 | 10 | param url.reloadDatabase = false; 11 | param request.reloadDatabase = false; 12 | if ( url.reloadDatabase && !request.reloadDatabase ) { 13 | refreshDatabase(); 14 | request.reloadDatabase = true; 15 | } 16 | } 17 | 18 | /** 19 | * @beforeEach 20 | */ 21 | function setupIntegrationTest() { 22 | setup(); 23 | } 24 | 25 | /** 26 | * @aroundEach 27 | */ 28 | function useDatabaseTransactions( spec ) { 29 | transaction action="begin" { 30 | try { 31 | arguments.spec.body(); 32 | } catch ( any e ) { 33 | rethrow; 34 | } finally { 35 | transaction action="rollback"; 36 | } 37 | } 38 | } 39 | 40 | private void function refreshDatabase() { 41 | getController().getModuleService().registerAndActivateModule( "cfmigrations", "testingModuleRoot" ); 42 | var migrationManager = getWireBox().getInstance( "QBMigrationManager@cfmigrations" ); 43 | var migrationService = application.wirebox.getInstance( "MigrationService@cfmigrations" ); 44 | migrationService.setMigrationsDirectory( "/tests/resources/database/migrations" ); 45 | migrationService.setSeedsDirectory( "/tests/resources/database/seeds" ); 46 | migrationService.setSeedEnvironments( [ "development", "testing" ] ); 47 | migrationService.setManager( 48 | migrationManager 49 | .setDefaultGrammar( "MySQLGrammar@qb" ) 50 | .setDatasource( "quick" ) 51 | .setSchema( "quick" ) 52 | ); 53 | migrationService.install(); 54 | migrationService.reset(); 55 | migrationService.runAllMigrations( "up" ); 56 | } 57 | 58 | function shutdownColdBox() { 59 | getColdBoxVirtualApp().shutdown(); 60 | } 61 | 62 | /** 63 | * Returns an array of the unique items of an array. 64 | * 65 | * @items An array of items. 66 | * 67 | * @doc_generic any 68 | * @return [any] 69 | */ 70 | public array function arrayUnique( required array items ) { 71 | return arraySlice( createObject( "java", "java.util.HashSet" ).init( arguments.items ).toArray(), 1 ); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /tests/resources/app/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain -------------------------------------------------------------------------------- /tests/resources/app/Application.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2005-2007 ColdBox Framework by Luis Majano and Ortus Solutions, Corp 3 | * www.ortussolutions.com 4 | * --- 5 | */ 6 | component{ 7 | // Application properties 8 | this.name = hash( getCurrentTemplatePath() ); 9 | this.sessionManagement = true; 10 | this.sessionTimeout = createTimeSpan(0,0,30,0); 11 | this.setClientCookies = true; 12 | 13 | // COLDBOX STATIC PROPERTY, DO NOT CHANGE UNLESS THIS IS NOT THE ROOT OF YOUR COLDBOX APP 14 | COLDBOX_APP_ROOT_PATH = getDirectoryFromPath( getCurrentTemplatePath() ); 15 | // The web server mapping to this application. Used for remote purposes or static purposes 16 | COLDBOX_APP_MAPPING = ""; 17 | // COLDBOX PROPERTIES 18 | COLDBOX_CONFIG_FILE = ""; 19 | // COLDBOX APPLICATION KEY OVERRIDE 20 | COLDBOX_APP_KEY = ""; 21 | 22 | this.datasource = "quick"; 23 | 24 | // application start 25 | public boolean function onApplicationStart(){ 26 | application.cbBootstrap = new coldbox.system.Bootstrap( COLDBOX_CONFIG_FILE, COLDBOX_APP_ROOT_PATH, COLDBOX_APP_KEY, COLDBOX_APP_MAPPING ); 27 | application.cbBootstrap.loadColdbox(); 28 | return true; 29 | } 30 | 31 | // application end 32 | public void function onApplicationEnd( struct appScope ){ 33 | arguments.appScope.cbBootstrap.onApplicationEnd( arguments.appScope ); 34 | } 35 | 36 | // request start 37 | public boolean function onRequestStart( string targetPage ){ 38 | // Process ColdBox Request 39 | application.cbBootstrap.onRequestStart( arguments.targetPage ); 40 | 41 | return true; 42 | } 43 | 44 | public void function onSessionStart(){ 45 | application.cbBootStrap.onSessionStart(); 46 | } 47 | 48 | public void function onSessionEnd( struct sessionScope, struct appScope ){ 49 | arguments.appScope.cbBootStrap.onSessionEnd( argumentCollection=arguments ); 50 | } 51 | 52 | public boolean function onMissingTemplate( template ){ 53 | return application.cbBootstrap.onMissingTemplate( argumentCollection=arguments ); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /tests/resources/app/config/.htaccess: -------------------------------------------------------------------------------- 1 | #apache access file to protect the config.xml.cfm file. Delete this if you do not use apache. 2 | authtype Basic 3 | deny from all 4 | Options -Indexes -------------------------------------------------------------------------------- /tests/resources/app/config/Application.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a protection Application cfm for the config file. You do not 3 | * need to modify this file 4 | */ 5 | component{ 6 | abort; 7 | } -------------------------------------------------------------------------------- /tests/resources/app/config/CacheBox.cfc: -------------------------------------------------------------------------------- 1 | component{ 2 | 3 | /** 4 | * Configure CacheBox for ColdBox Application Operation 5 | */ 6 | function configure(){ 7 | 8 | // The CacheBox configuration structure DSL 9 | cacheBox = { 10 | // LogBox config already in coldbox app, not needed 11 | // logBoxConfig = "coldbox.system.web.config.LogBox", 12 | 13 | // The defaultCache has an implicit name "default" which is a reserved cache name 14 | // It also has a default provider of cachebox which cannot be changed. 15 | // All timeouts are in minutes 16 | defaultCache = { 17 | objectDefaultTimeout = 120, //two hours default 18 | objectDefaultLastAccessTimeout = 30, //30 minutes idle time 19 | useLastAccessTimeouts = true, 20 | reapFrequency = 5, 21 | freeMemoryPercentageThreshold = 0, 22 | evictionPolicy = "LRU", 23 | evictCount = 1, 24 | maxObjects = 300, 25 | objectStore = "ConcurrentStore", //guaranteed objects 26 | coldboxEnabled = true 27 | }, 28 | 29 | // Register all the custom named caches you like here 30 | caches = { 31 | // Named cache for all coldbox event and view template caching 32 | template = { 33 | provider = "coldbox.system.cache.providers.CacheBoxColdBoxProvider", 34 | properties = { 35 | objectDefaultTimeout = 120, 36 | objectDefaultLastAccessTimeout = 30, 37 | useLastAccessTimeouts = true, 38 | freeMemoryPercentageThreshold = 0, 39 | reapFrequency = 5, 40 | evictionPolicy = "LRU", 41 | evictCount = 2, 42 | maxObjects = 300, 43 | objectStore = "ConcurrentSoftReferenceStore" //memory sensitive 44 | } 45 | } 46 | } 47 | }; 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /tests/resources/app/config/Routes.cfm: -------------------------------------------------------------------------------- 1 |  2 | // Allow unique URL or combination of URLs, we recommend both enabled 3 | setUniqueURLS(false); 4 | // Auto reload configuration, true in dev makes sense to reload the routes on every request 5 | //setAutoReload(false); 6 | // Sets automatic route extension detection and places the extension in the rc.format variable 7 | // setExtensionDetection(true); 8 | // The valid extensions this interceptor will detect 9 | // setValidExtensions('xml,json,jsont,rss,html,htm'); 10 | // If enabled, the interceptor will throw a 406 exception that an invalid format was detected or just ignore it 11 | // setThrowOnInvalidExtension(true); 12 | 13 | // Base URL 14 | if( len(getSetting('AppMapping') ) lte 1){ 15 | setBaseURL("http://#cgi.HTTP_HOST#/index.cfm"); 16 | } 17 | else{ 18 | setBaseURL("http://#cgi.HTTP_HOST#/#getSetting('AppMapping')#/index.cfm"); 19 | } 20 | 21 | // Your Application Routes 22 | addRoute(pattern=":handler/:action?"); 23 | 24 | 25 | /** Developers can modify the CGI.PATH_INFO value in advance of the SES 26 | interceptor to do all sorts of manipulations in advance of route 27 | detection. If provided, this function will be called by the SES 28 | interceptor instead of referencing the value CGI.PATH_INFO. 29 | 30 | This is a great place to perform custom manipulations to fix systemic 31 | URL issues your Web site may have or simplify routes for i18n sites. 32 | 33 | @Event The ColdBox RequestContext Object 34 | **/ 35 | function PathInfoProvider(Event){ 36 | /* Example: 37 | var URI = CGI.PATH_INFO; 38 | if (URI eq "api/foo/bar") 39 | { 40 | Event.setProxyRequest(true); 41 | return "some/other/value/for/your/routes"; 42 | } 43 | */ 44 | return CGI.PATH_INFO; 45 | } 46 | -------------------------------------------------------------------------------- /tests/resources/app/config/WireBox.cfc: -------------------------------------------------------------------------------- 1 | component extends="coldbox.system.ioc.config.Binder"{ 2 | 3 | /** 4 | * Configure WireBox, that's it! 5 | */ 6 | function configure(){ 7 | 8 | // The WireBox configuration structure DSL 9 | wireBox = { 10 | // Scope registration, automatically register a wirebox injector instance on any CF scope 11 | // By default it registeres itself on application scope 12 | scopeRegistration = { 13 | enabled = true, 14 | scope = "application", // server, cluster, session, application 15 | key = "wireBox" 16 | }, 17 | 18 | // DSL Namespace registrations 19 | customDSL = { 20 | // namespace = "mapping name" 21 | }, 22 | 23 | // Custom Storage Scopes 24 | customScopes = { 25 | // annotationName = "mapping name" 26 | }, 27 | 28 | // Package scan locations 29 | scanLocations = [], 30 | 31 | // Stop Recursions 32 | stopRecursions = [], 33 | 34 | // Parent Injector to assign to the configured injector, this must be an object reference 35 | parentInjector = "", 36 | 37 | // Register all event listeners here, they are created in the specified order 38 | listeners = [ 39 | // { class="", name="", properties={} } 40 | ] 41 | }; 42 | 43 | mapDirectory( "app.models" ); 44 | 45 | // Map Bindings below 46 | map( "Country@something" ) 47 | .to( "app.models.Country" ); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /tests/resources/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/favicon.ico -------------------------------------------------------------------------------- /tests/resources/app/handlers/Main.cfc: -------------------------------------------------------------------------------- 1 | component extends="coldbox.system.EventHandler"{ // Default Action function index(event,rc,prc){ prc.welcomeMessage = "Welcome to ColdBox!"; event.setView("main/index"); } // Do something function doSomething(event,rc,prc){ setNextEvent("main.index"); } /************************************** IMPLICIT ACTIONS *********************************************/ function onAppInit(event,rc,prc){ } function onRequestStart(event,rc,prc){ } function onRequestEnd(event,rc,prc){ } function onSessionStart(event,rc,prc){ } function onSessionEnd(event,rc,prc){ var sessionScope = event.getValue("sessionReference"); var applicationScope = event.getValue("applicationReference"); } function onException(event,rc,prc){ //Grab Exception From private request collection, placed by ColdBox Exception Handling var exception = prc.exception; //Place exception handler below: } function onMissingTemplate(event,rc,prc){ //Grab missingTemplate From request collection, placed by ColdBox var missingTemplate = event.getValue("missingTemplate"); } } -------------------------------------------------------------------------------- /tests/resources/app/includes/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/includes/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /tests/resources/app/includes/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/includes/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /tests/resources/app/includes/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/includes/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /tests/resources/app/includes/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/includes/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /tests/resources/app/includes/helpers/ApplicationHelper.cfm: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /tests/resources/app/includes/i18n/i18n_goes_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/includes/i18n/i18n_goes_here.txt -------------------------------------------------------------------------------- /tests/resources/app/includes/images/ColdBoxLogo2015_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/includes/images/ColdBoxLogo2015_300.png -------------------------------------------------------------------------------- /tests/resources/app/index.cfm: -------------------------------------------------------------------------------- 1 |  2 | 9 | 10 | -------------------------------------------------------------------------------- /tests/resources/app/interceptors/PutStuffHere.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/interceptors/PutStuffHere.txt -------------------------------------------------------------------------------- /tests/resources/app/lib/drop_jars_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/lib/drop_jars_here.txt -------------------------------------------------------------------------------- /tests/resources/app/models/A.cfc: -------------------------------------------------------------------------------- 1 | component table="a" extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="name"; 5 | 6 | function b() { 7 | return hasMany( "b" ); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /tests/resources/app/models/Actor.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="name"; 5 | property name="createdDate" column="created_date"; 6 | property name="modifiedDate" column="modified_date"; 7 | 8 | function keyType() { 9 | return variables._wirebox.getInstance( "GUIDKeyType@quick" ); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tests/resources/app/models/Address.cfc: -------------------------------------------------------------------------------- 1 | component accessors="true" { 2 | 3 | property name="streetOne"; 4 | property name="streetTwo"; 5 | property name="city"; 6 | property name="state"; 7 | property name="zip"; 8 | 9 | function fullStreet() { 10 | var street = [ getStreetOne(), getStreetTwo() ]; 11 | return street.filter( function( part ) { 12 | return !isNull( part ) && part != ""; 13 | } ).toList( chr( 10 ) ); 14 | } 15 | 16 | function formatted() { 17 | return fullStreet() & chr( 10 ) & "#getCity()#, #getState()# #getZip()#"; 18 | } 19 | 20 | function getMemento() { 21 | return { 22 | "streetOne": variables.streetOne, 23 | "streetTwo": variables.streetTwo, 24 | "city": variables.city, 25 | "state": variables.state, 26 | "zip": variables.zip 27 | }; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tests/resources/app/models/AddressCast.cfc: -------------------------------------------------------------------------------- 1 | component singleton { 2 | 3 | property name="wirebox" inject="wirebox"; 4 | 5 | /** 6 | * Casts the given value from the database to the target cast type. 7 | * 8 | * @entity The entity with the attribute being casted. 9 | * @key The attribute alias name. 10 | * @value The value of the attribute. 11 | * @attributes The struct of attributes for the entity. 12 | * 13 | * @return The casted attribute. 14 | */ 15 | public any function get( 16 | required any entity, 17 | required string key, 18 | any value 19 | ) { 20 | return wirebox.getInstance( dsl = "Address" ) 21 | .setStreetOne( entity.retrieveAttribute( "streetOne" ) ) 22 | .setStreetTwo( entity.retrieveAttribute( "streetTwo" ) ) 23 | .setCity( entity.retrieveAttribute( "city" ) ) 24 | .setState( entity.retrieveAttribute( "state" ) ) 25 | .setZip( entity.retrieveAttribute( "zip" ) ); 26 | } 27 | 28 | /** 29 | * Returns the value to assign to the key before saving to the database. 30 | * 31 | * @entity The entity with the attribute being casted. 32 | * @key The attribute alias name. 33 | * @value The value of the attribute. 34 | * @attributes The struct of attributes for the entity. 35 | * 36 | * @return The value to save to the database. A struct of values 37 | * can be returned if the cast value affects multiple attributes. 38 | */ 39 | public any function set( 40 | required any entity, 41 | required string key, 42 | any value 43 | ) { 44 | return { 45 | "streetOne": arguments.value.getStreetOne(), 46 | "streetTwo": arguments.value.getStreetTwo(), 47 | "city": arguments.value.getCity(), 48 | "state": arguments.value.getState(), 49 | "zip": arguments.value.getZip() 50 | }; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /tests/resources/app/models/Admin.cfc: -------------------------------------------------------------------------------- 1 | component extends="User" table="users" accessors="true" { 2 | 3 | function applyGlobalScopes( qb ) { 4 | qb.withLatestPostId(); 5 | qb.ofType( "admin" ); 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /tests/resources/app/models/B.cfc: -------------------------------------------------------------------------------- 1 | component table="b" extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="a_id"; 5 | property name="name"; 6 | 7 | function a() { 8 | return belongsTo( "a" ); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /tests/resources/app/models/BaseProduct.cfc: -------------------------------------------------------------------------------- 1 | component 2 | extends="quick.models.BaseEntity" 3 | accessors="true" 4 | discriminatorColumn="type" 5 | singleTableInheritance="true" 6 | table="products" 7 | { 8 | 9 | property name="id"; 10 | property name="name"; 11 | property name="userId" column="user_id"; 12 | property name="type"; 13 | property name="metadata" casts="JsonCast@quick"; 14 | 15 | 16 | variables._discriminators = [ 17 | "ProductBook", 18 | "ProductMusic" 19 | ]; 20 | 21 | 22 | function creator() { 23 | return belongsTo( "User", "user_id" ); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /tests/resources/app/models/Category.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="parentId"; 5 | 6 | function parent() { 7 | return belongsTo( "Category", "parentId", "id" ).withDefault(); // Return a new entity if no match found. 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /tests/resources/app/models/CollectionPost.cfc: -------------------------------------------------------------------------------- 1 | component table="my_posts" extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="post_pk"; 4 | property name="userId" column="user_id"; 5 | property name="body"; 6 | property name="createdDate" column="created_date"; 7 | property name="modifiedDate" column="modified_date"; 8 | 9 | variables._key = "post_pk"; 10 | 11 | function author() { 12 | return belongsTo( "User", "user_id" ); 13 | } 14 | 15 | function tags() { 16 | return belongsToMany( "Tag", "my_posts_tags", "custom_post_pk", "tag_id" ); 17 | } 18 | 19 | function comments() { 20 | return polymorphicHasMany( "Comment", "commentable" ); 21 | } 22 | 23 | function scopeLatest( query ) { 24 | return query.orderBy( "created_date", "desc" ); 25 | } 26 | 27 | function newCollection( array entities = [] ) { 28 | return variables._wirebox.getInstance( 29 | name = "extras.QuickCollection", 30 | initArguments = { 31 | "collection" = arguments.entities 32 | } 33 | ); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /tests/resources/app/models/Comment.cfc: -------------------------------------------------------------------------------- 1 | component 2 | extends="quick.models.BaseEntity" 3 | discriminatorColumn="designation" 4 | accessors="true" 5 | { 6 | 7 | property name="id"; 8 | property name="body"; 9 | property name="commentableId" column="commentable_id"; 10 | property name="commentableType" column="commentable_type"; 11 | property name="userId" column="user_id"; 12 | property name="createdDate" column="created_date"; 13 | property name="modifiedDate" column="modified_date"; 14 | property name="sentimentAnalysis" casts="JsonCast@quick"; 15 | 16 | variables._discriminators = [ 17 | "InternalComment" 18 | ]; 19 | 20 | 21 | function commentable() { 22 | return polymorphicBelongsTo( "commentable" ); 23 | } 24 | 25 | function author() { 26 | return belongsTo( "User", "user_id" ); 27 | } 28 | 29 | function tags() { 30 | return hasManyThrough( [ "commentable", "tags" ] ); 31 | } 32 | 33 | // chose this sql function as it present in both mysql and sql server 34 | function scopeAddUpperBody(qb){ 35 | qb.selectRaw( "UPPER(body) as upperBody" ); 36 | appendVirtualAttribute( "upperBody" ); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /tests/resources/app/models/CompatUser.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.CBORMCompatEntity" table="users" accessors="true" { 2 | 3 | property name="id"; 4 | property name="username"; 5 | property name="firstName" column="first_name"; 6 | property name="lastName" column="last_name"; 7 | property name="password"; 8 | property name="countryId" column="country_id"; 9 | property name="createdDate" column="created_date"; 10 | property name="modifiedDate" column="modified_date"; 11 | property name="email" column="email" update=false insert=true; 12 | property name="type"; 13 | 14 | property name="posts" 15 | singularname="post" 16 | cfc="models.Post" 17 | fieldtype="one-to-many" 18 | fkcolumn="user_id" 19 | inverse="true" 20 | lazy="extra"; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /tests/resources/app/models/Composite.cfc: -------------------------------------------------------------------------------- 1 | component table="composites" extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="a"; 4 | property name="b"; 5 | 6 | variables._key = [ "a", "b" ]; 7 | 8 | function keyType() { 9 | return variables._wirebox.getInstance( "NullKeyType@quick" ); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tests/resources/app/models/CompositeChild.cfc: -------------------------------------------------------------------------------- 1 | component table="composite_children" extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="composite_a"; 5 | property name="composite_b"; 6 | 7 | function parent() { 8 | return belongsTo( 9 | "Composite", 10 | [ "composite_a", "composite_b" ], 11 | [ "a", "b" ] 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/resources/app/models/DefaultedKeyEntity.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id" default="1"; 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tests/resources/app/models/EagerLoadedPost.cfc: -------------------------------------------------------------------------------- 1 | component extends="Post" accessors="true" { 2 | 3 | variables._with = [ "comments" ]; 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tests/resources/app/models/Empty.cfc: -------------------------------------------------------------------------------- 1 | component table="empty" extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tests/resources/app/models/EntityWithoutAccessors.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" { 2 | 3 | property name="id"; 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tests/resources/app/models/InternalComment.cfc: -------------------------------------------------------------------------------- 1 | component 2 | accessors="true" 3 | extends="Comment" 4 | table="internalComments" 5 | joincolumn="FK_comment" 6 | discriminatorValue="internal" 7 | { 8 | property name="reason"; 9 | } -------------------------------------------------------------------------------- /tests/resources/app/models/Jingle.cfc: -------------------------------------------------------------------------------- 1 | component 2 | accessors="true" 3 | extends="Song" 4 | table="jingles" 5 | joincolumn="FK_song" 6 | { 7 | property name="catchiness"; 8 | } -------------------------------------------------------------------------------- /tests/resources/app/models/Link.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="wirebox" inject="wirebox" persistent="false"; 4 | 5 | property name="link_id" column="link_id"; 6 | property name="url" column="link_url"; 7 | property name="createdDate" column="created_date" readonly="true"; 8 | 9 | variables._key = "link_id"; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /tests/resources/app/models/Office.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="name"; 5 | 6 | function teams() { 7 | return hasMany( "Team" ); 8 | } 9 | 10 | function users() { 11 | return hasManyThrough( [ "teams", "users" ] ); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /tests/resources/app/models/Permission.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="name"; 5 | 6 | function roles() { 7 | return belongsToMany( "Role" ); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /tests/resources/app/models/PhoneNumber.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property 4 | name ="number" 5 | sqltype ="cf_sql_varchar" 6 | convertToNull="false"; 7 | property 8 | name ="active" 9 | casts ="BooleanCast@quick" 10 | sqltype="CF_SQL_BIT"; 11 | property 12 | name ="confirmed" 13 | casts ="BooleanCast@quick" 14 | sqltype="CF_SQL_BIT"; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /tests/resources/app/models/Post.cfc: -------------------------------------------------------------------------------- 1 | component 2 | table ="my_posts" 3 | extends ="quick.models.BaseEntity" 4 | accessors="true" 5 | { 6 | 7 | property name="post_pk"; 8 | property name="user_id"; 9 | property name="body"; 10 | property name="createdDate" column="created_date"; 11 | property name="modifiedDate" column="modified_date"; 12 | property name="publishedDate" column="published_date"; 13 | 14 | variables._key = "post_pk"; 15 | 16 | function author() { 17 | return belongsTo( "User", "user_id" ); 18 | } 19 | 20 | function authorWithEmptyDefault() { 21 | return belongsTo( "User", "user_id" ).withDefault(); 22 | } 23 | 24 | function authorWithDefaultAttributes() { 25 | return belongsTo( "User", "user_id" ).withDefault( { 26 | "firstName" : "Guest", 27 | "lastName" : "User" 28 | } ); 29 | } 30 | 31 | function authorWithCalllbackConfiguredDefault() { 32 | return belongsTo( "User", "user_id" ).withDefault( function( user, post ) { 33 | user.setUsername( post.getBody() ); 34 | } ); 35 | } 36 | 37 | function tags() { 38 | return belongsToMany( 39 | "Tag", 40 | "my_posts_tags", 41 | "custom_post_pk", 42 | "tag_id" 43 | ); 44 | } 45 | 46 | function comments() { 47 | return polymorphicHasMany( "Comment", "commentable" ); 48 | } 49 | 50 | function country() { 51 | return belongsToThrough( [ "author", "country" ] ); 52 | } 53 | 54 | function commentingUsers() { 55 | return hasManyThrough( [ "comments", "author" ] ).distinct(); 56 | } 57 | 58 | function scopeLatest( qb ) { 59 | return qb.orderBy( "created_date", "desc" ); 60 | } 61 | 62 | function scopePublished( qb ) { 63 | qb.whereNotNull( "publishedDate" ); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /tests/resources/app/models/PostAlt.cfc: -------------------------------------------------------------------------------- 1 | component 2 | table ="my_posts" 3 | extends ="quick.models.BaseEntity" 4 | accessors="true" 5 | { 6 | 7 | property name="id" column="post_pk"; 8 | property name="userId" column="user_id"; 9 | property name="body"; 10 | property name="createdDate" column="created_date"; 11 | property name="modifiedDate" column="modified_date"; 12 | property name="publishedDate" column="published_date"; 13 | 14 | function author() { 15 | return belongsTo( "User", "user_id" ); 16 | } 17 | 18 | function authorWithEmptyDefault() { 19 | return belongsTo( "User", "user_id" ).withDefault(); 20 | } 21 | 22 | function authorWithDefaultAttributes() { 23 | return belongsTo( "User", "user_id" ).withDefault( { 24 | "firstName" : "Guest", 25 | "lastName" : "User" 26 | } ); 27 | } 28 | 29 | function authorWithCalllbackConfiguredDefault() { 30 | return belongsTo( "User", "user_id" ).withDefault( function( user, post ) { 31 | user.setUsername( post.getBody() ); 32 | } ); 33 | } 34 | 35 | function tags() { 36 | return belongsToMany( 37 | "Tag", 38 | "my_posts_tags", 39 | "custom_post_pk", 40 | "tag_id" 41 | ); 42 | } 43 | 44 | function comments() { 45 | return polymorphicHasMany( "Comment", "commentable" ); 46 | } 47 | 48 | function country() { 49 | return belongsToThrough( [ "author", "country" ] ); 50 | } 51 | 52 | function commentingUsers() { 53 | return hasManyThrough( [ "comments", "author" ] ); 54 | } 55 | 56 | function scopeLatest( qb ) { 57 | return qb.orderBy( "created_date", "desc" ); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /tests/resources/app/models/ProductBook.cfc: -------------------------------------------------------------------------------- 1 | component 2 | extends="BaseProduct" 3 | accessors="true" 4 | discriminatorValue="book" 5 | { 6 | 7 | property name="isbn"; 8 | 9 | } -------------------------------------------------------------------------------- /tests/resources/app/models/ProductMusic.cfc: -------------------------------------------------------------------------------- 1 | component 2 | extends="BaseProduct" 3 | accessors="true" 4 | discriminatorValue="music" 5 | { 6 | 7 | property name="artist"; 8 | 9 | } -------------------------------------------------------------------------------- /tests/resources/app/models/Purchase.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="userId"; 5 | property name="price"; 6 | property name="quantity"; 7 | property name="description"; 8 | property name="createdDate"; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /tests/resources/app/models/Referral.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" readonly="true" accessors="true" { 2 | 3 | property name="id"; 4 | property name="type"; 5 | property name="createdDate" column="created_date"; 6 | property name="modifiedDate" column="modified_date"; 7 | 8 | function setType( type ) { 9 | return assignAttribute( "type", ucase( arguments.type ) ); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tests/resources/app/models/Role.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="name"; 5 | 6 | function permissions() { 7 | return belongsToMany( "Permission" ); 8 | } 9 | 10 | function users() { 11 | return belongsToMany( "User" ); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /tests/resources/app/models/Song.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="title" nullValue="REALLY_NULL"; 5 | property name="downloadUrl" column="download_url"; 6 | property name="createdDate" column="created_date"; 7 | property name="modifiedDate" column="modified_date"; 8 | 9 | function instanceReady( eventData ) { 10 | request.instanceReadyCalled = eventData; 11 | } 12 | 13 | function preLoad( eventData ) { 14 | request.preLoadCalled = eventData; 15 | } 16 | 17 | function postLoad( eventData ) { 18 | request.postLoadCalled = eventData; 19 | } 20 | 21 | function preInsert( eventData ) { 22 | request.preInsertCalled = { 23 | "entity": arguments.eventData.entity.getMemento(), 24 | "isLoaded": arguments.eventData.entity.isLoaded() 25 | }; 26 | } 27 | 28 | function postInsert( eventData ) { 29 | request.postInsertCalled = { 30 | "entity": eventData.entity.getMemento(), 31 | "isLoaded": eventData.entity.isLoaded() 32 | }; 33 | } 34 | 35 | function preUpdate( eventData ) { 36 | param request.preUpdateCalled = []; 37 | arrayAppend( request.preUpdateCalled, { 38 | "entity": eventData.entity.getMemento() 39 | } ); 40 | } 41 | 42 | function postUpdate( eventData ) { 43 | param request.postUpdateCalled = []; 44 | arrayAppend( request.postUpdateCalled, { 45 | "entity": eventData.entity.getMemento() 46 | } ); 47 | } 48 | 49 | function preSave( eventData ) { 50 | request.preSaveCalled = { 51 | "entity": arguments.eventData.entity.getMemento(), 52 | "isLoaded": arguments.eventData.entity.isLoaded() 53 | }; 54 | } 55 | 56 | function postSave( eventData ) { 57 | request.postSaveCalled = { 58 | "entity": arguments.eventData.entity.getMemento(), 59 | "isLoaded": arguments.eventData.entity.isLoaded() 60 | }; 61 | } 62 | 63 | function preDelete( eventData ) { 64 | param request.preDeleteCalled = []; 65 | arrayAppend( request.preDeleteCalled, { 66 | "entity": eventData.entity.getMemento() 67 | } ); 68 | } 69 | 70 | function postDelete( eventData ) { 71 | param request.postDeleteCalled = []; 72 | arrayAppend( request.postDeleteCalled, { 73 | "entity": eventData.entity.getMemento() 74 | } ); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /tests/resources/app/models/Tag.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="name"; 5 | 6 | function posts() { 7 | return belongsToMany( "Post", "my_posts_tags", "tag_id", "custom_post_pk" ); 8 | } 9 | 10 | function users() { 11 | return hasManyDeep( 12 | relationName = "User AS u", 13 | through = [ "my_posts_tags", "Post" ], 14 | foreignKeys = [ "tag_id", "post_pk", "id" ], 15 | localKeys = [ "id", "custom_post_pk", "user_id" ] 16 | ).distinct().orderBy( "u.id" ); 17 | } 18 | 19 | function usersBuilder() { 20 | return newHasManyDeepBuilder() 21 | .throughPivotTable( "my_posts_tags", "tag_id", "id" ) 22 | .throughEntity( "Post", "post_pk", "custom_post_pk" ) 23 | .toRelated( "User as u", "id", "user_id" ) 24 | .distinct().orderBy( "u.id" ); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tests/resources/app/models/Team.cfc: -------------------------------------------------------------------------------- 1 | component table="teams" extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="name"; 5 | property name="officeId"; 6 | 7 | function users() { 8 | return hasMany( "User", "team_id" ); 9 | } 10 | 11 | function office() { 12 | return belongsTo( "Office" ); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tests/resources/app/models/Theme.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="slug"; 5 | property name="version" sqltype="cf_sql_varchar"; 6 | property name="config" casts="JsonCast@quick"; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /tests/resources/app/models/UserFill.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="fileroot"; 5 | property name="fullName" update="false"; 6 | property name="email"; 7 | property name="password"; 8 | property name="firstName"; 9 | property name="lastName"; 10 | property name="aboutMe"; 11 | property name="createdDate" readonly="true"; 12 | property name="updatedDate"; 13 | property name="lastLogin"; 14 | property name="avatarID"; 15 | property name="headerID"; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /tests/resources/app/models/UserWithGlobalScope.cfc: -------------------------------------------------------------------------------- 1 | component extends="User" table="users" accessors="true" { 2 | 3 | function scopeWithCountryName( qb ) { 4 | qb.addSubselect( "countryName", "country.name" ); 5 | } 6 | 7 | function scopeWithTeamName( qb ) { 8 | qb.addSubselect( "teamName", "team.name" ); 9 | } 10 | 11 | function applyGlobalScopes( qb ) { 12 | qb.withCountryName(); 13 | qb.withTeamName(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/resources/app/models/Video.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="id"; 4 | property name="url"; 5 | property name="title"; 6 | property name="description"; 7 | property name="createdDate" column="created_date"; 8 | property name="modifiedDate" column="modified_date"; 9 | 10 | function comments() { 11 | return polymorphicHasMany( "Comment", "commentable" ); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /tests/resources/app/models/externalThing.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" table="externalThings" accessors="true" { 2 | 3 | property name="thingID"; 4 | property name="externalID"; // the external vendor foreign key 5 | property name="userID"; // our userID that created this thingID 6 | property name="value"; 7 | 8 | variables._key = "thingID"; 9 | 10 | function users() { return hasMany( relationName = "User", foreignKey = "externalID", localKey = "externalID" ); } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tests/resources/app/models/inLeague/Child.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="childID"; 4 | property name="familyID"; 5 | property name="firstName"; 6 | property name="lastName"; 7 | 8 | variables._key = "childID"; 9 | 10 | function family() { 11 | return belongsTo( 12 | relationName = "Family", 13 | foreignKey = "familyID", 14 | localKey = "familyID" 15 | ); 16 | } 17 | 18 | function parent1() { 19 | return hasOneThrough( [ "family", "parent1" ] ); 20 | } 21 | 22 | function parent2() { 23 | return hasOneThrough( [ "family", "parent2" ] ); 24 | } 25 | 26 | function registration() { 27 | return hasMany( 28 | relationName = "Registration", 29 | foreignKey = "childID", 30 | localKey = "childID" 31 | ); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /tests/resources/app/models/inLeague/Family.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="familyID"; 4 | property name="parent1ID"; 5 | property name="parent2ID"; 6 | 7 | variables._key = "familyID"; 8 | 9 | function parent1() { 10 | return hasOne( 11 | relationName = "Parent", 12 | foreignKey = "ID", 13 | localKey = "parent1ID" 14 | ); 15 | } 16 | 17 | function parent2() { 18 | return hasOne( 19 | relationName = "Parent", 20 | foreignKey = "ID", 21 | localKey = "parent2ID" 22 | ); 23 | } 24 | 25 | function parents() { 26 | return belongsToMany( 27 | relationName = "Parent", 28 | table = "family_parents", 29 | foreignPivotKey = "familyID", 30 | relatedPivotKey = "parentID", 31 | parentKey = "familyID", 32 | relatedKey = "ID" 33 | ); 34 | } 35 | 36 | function children() { 37 | return hasMany( 38 | relationName = "Child", 39 | foreignKey = "familyID", 40 | localKey = "familyID" 41 | ); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /tests/resources/app/models/inLeague/Game.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="ID"; 4 | property name="fieldID"; 5 | property name="clientID"; 6 | 7 | variables._key = "ID"; 8 | 9 | function field() { 10 | return belongsTo( 11 | relationName = "PlayingField", 12 | foreignKey = [ "fieldID", "clientID" ], 13 | localKey = [ "fieldID", "clientID" ] 14 | ); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /tests/resources/app/models/inLeague/Parent.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="ID"; 4 | property name="firstname"; 5 | property name="lastname"; 6 | 7 | variables._key = "ID"; 8 | 9 | function families() { 10 | return belongsToMany( 11 | relationName = "Family", 12 | table = "family_parents", 13 | foreignPivotKey = "parentID", 14 | relatedPivotKey = "familyID", 15 | parentKey = "ID", 16 | relatedKey = "familyID" 17 | ); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /tests/resources/app/models/inLeague/PlayingField.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="fieldID"; 4 | property name="clientID"; 5 | property name="fieldName"; 6 | 7 | variables._key = [ "fieldID", "clientID" ]; 8 | 9 | function keyType() { 10 | return variables._wirebox.getInstance( "NullKeyType@quick" ); 11 | } 12 | 13 | function games() { 14 | return hasMany( 15 | relationName = "Game", 16 | foreignKey = [ "fieldID", "clientID" ], 17 | localKey = [ "fieldID", "clientID" ] 18 | ); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /tests/resources/app/models/inLeague/Registration.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="registrationID"; 4 | property name="childID"; 5 | 6 | variables._key = "registrationID"; 7 | 8 | function child() { 9 | return belongsTo( 10 | relationName = "Child", 11 | foreignKey = "childID", 12 | localKey = "childID" 13 | ); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /tests/resources/app/models/products/ApparelSKU.cfc: -------------------------------------------------------------------------------- 1 | component extends="ProductSKU" accessors="true" table="apparel_skus" joinColumn="id" discriminatorValue="apparel" { 2 | 3 | property name="id"; 4 | property name="cost"; 5 | property name="color"; 6 | property name="size1"; 7 | property name="size1Description"; 8 | property name="size1Index"; 9 | property name="createdDate"; 10 | property name="modifiedDate"; 11 | property name="deletedDate"; 12 | 13 | function keyType(){ 14 | return variables._wirebox.getInstance( "UUIDKeyType@quick" ); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /tests/resources/app/models/products/Product.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" table="products_b" { 2 | 3 | property name="id"; 4 | property name="name"; 5 | property name="itemNumber"; 6 | property name="createdDate"; 7 | property name="modifiedDate"; 8 | property name="deletedDate"; 9 | 10 | function keyType(){ 11 | return variables._wirebox.getInstance( "UUIDKeyType@quick" ); 12 | } 13 | 14 | function skus(){ 15 | return hasMany( "ProductSKU" ); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /tests/resources/app/models/products/ProductSKU.cfc: -------------------------------------------------------------------------------- 1 | component extends="quick.models.BaseEntity" accessors="true" table="product_skus" discriminatorColumn="designation" { 2 | 3 | property name="id"; 4 | property name="productId"; 5 | property name="designation"; 6 | property name="createdDate"; 7 | property name="modifiedDate"; 8 | property name="deletedDate"; 9 | 10 | variables._discriminators = [ 11 | "ApparelSKU" 12 | ]; 13 | 14 | function keyType(){ 15 | return variables._wirebox.getInstance( "UUIDKeyType@quick" ); 16 | } 17 | 18 | function product(){ 19 | return belongsTo( "Product@core" ); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /tests/resources/app/models/revagency/TripPlannerReservationComponent.cfc: -------------------------------------------------------------------------------- 1 | component 2 | extends ="quick.models.BaseEntity" 3 | table ="trip_planner_reservation_component" 4 | discriminatorColumn="type" 5 | accessors ="true" 6 | { 7 | 8 | property name="id" column="componentID"; 9 | property name="reservationId" column="reservationID"; 10 | property name="tripComponentId" column="tripComponentID"; 11 | property name="type" column="type"; 12 | property 13 | name ="isActive" 14 | column="isActive" 15 | type ="boolean" 16 | casts ="BooleanCast@quick"; 17 | property 18 | name ="createdDate" 19 | column="createdDate" 20 | type ="date"; 21 | property 22 | name ="createdByAgentId" 23 | column="createdByAgentId" 24 | hint ="if the record was created by an agent, the agent identifier, otherwise null"; 25 | property 26 | name ="createdByMemberId" 27 | column="createdByMemberId" 28 | hint ="if the record was created by a member, the member identifier, otherwise null"; 29 | property 30 | name ="modifiedDate" 31 | column="modifiedDate" 32 | type ="date"; 33 | property 34 | name ="modifiedByAgentId" 35 | column="modifiedByAgentId" 36 | hint ="the agent identifier of the last agent to modify this record the record - not necessarily the most recent modification"; 37 | property 38 | name ="modifiedByMemberId" 39 | column="modifiedByMemberId" 40 | hint ="the member identifier of the last member to modify this record the record - not necessarily the most recent modification"; 41 | property 42 | name ="lastModifiedBy" 43 | column="lastModifiedBy" 44 | hint ="was the last person to modify this record"; 45 | 46 | variables._discriminators = [ "TripPlannerReservationComponentCruise" ]; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /tests/resources/app/models/revagency/TripPlannerReservationComponentCruise.cfc: -------------------------------------------------------------------------------- 1 | component 2 | extends ="TripPlannerReservationComponent" 3 | table ="trip_planner_reservation_component_cruise" 4 | joinColumn ="reservationComponentID" 5 | discriminatorValue="cruise" 6 | accessors ="true" 7 | { 8 | 9 | property name="id" column="cruiseComponentID"; 10 | property name="reservationComponentID" column="componentID"; 11 | property name="confirmationNumber" column="confirmationNumber"; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/resources/app/models/withRelationshipRegression/RMME_A.cfc: -------------------------------------------------------------------------------- 1 | component table="RMME_A" extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="ID_A" type="numeric" sqltype="integer"; 4 | 5 | variables._key = "ID_A"; 6 | 7 | function B() { 8 | return hasMany( 9 | relationName = "RMME_B", 10 | foreignKey = "ID_A", 11 | localKey="ID_A" 12 | ).with( [ "C" ] ); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /tests/resources/app/models/withRelationshipRegression/RMME_B.cfc: -------------------------------------------------------------------------------- 1 | component table="RMME_B" extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="ID_A" type="numeric" sqltype="integer"; 4 | property name="ID_B" type="numeric" sqltype="integer"; 5 | property name="ID_C" type="numeric" sqltype="integer"; 6 | 7 | variables._key = "ID_B"; 8 | 9 | function C() { 10 | return belongsTo( relationName = "RMME_C", foreignKey = "ID_C", localKey = "ID_C" ); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /tests/resources/app/models/withRelationshipRegression/RMME_C.cfc: -------------------------------------------------------------------------------- 1 | component table="RMME_C" extends="quick.models.BaseEntity" accessors="true" { 2 | 3 | property name="ID_B" type="numeric" sqltype="integer"; 4 | property name="ID_C" type="numeric" sqltype="integer"; 5 | 6 | variables._key = "ID_C"; 7 | 8 | } -------------------------------------------------------------------------------- /tests/resources/app/modules/tracked_modules_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/modules/tracked_modules_here.txt -------------------------------------------------------------------------------- /tests/resources/app/modules_app/custom_modules_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/modules_app/custom_modules_here.txt -------------------------------------------------------------------------------- /tests/resources/app/readme.md: -------------------------------------------------------------------------------- 1 | # Advanced Template 2 | 3 | An advanced template with all the bells and whistles in script format 4 | 5 | ## License 6 | Apache License, Version 2.0. 7 | 8 | ## Important Links 9 | 10 | Source Code 11 | - https://github.com/coldbox-templates/advanced-script 12 | 13 | ## Quick Installation 14 | 15 | Each application templates contains a `box.json` so it can leverage [CommandBox](http://www.ortussolutions.com/products/commandbox) for its dependencies. 16 | Just go into each template directory and type: 17 | 18 | ``` 19 | box install 20 | ``` 21 | 22 | This will setup all the needed dependencies for each application template. You can then type: 23 | 24 | ``` 25 | box server start 26 | ``` 27 | 28 | And run the application. 29 | 30 | --- 31 | 32 | ###THE DAILY BREAD 33 | > "I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" Jn 14:1-12 -------------------------------------------------------------------------------- /tests/resources/app/robots.txt: -------------------------------------------------------------------------------- 1 |  2 | User-agent: Slurp 3 | Crawl-delay: 100 4 | Disallow: 5 | 6 | User-agent: gsa-crawler-www 7 | Crawl-delay: 100 8 | 9 | User-agent: Googlebot 10 | Crawl-delay: 100 11 | 12 | User-agent: Mediapartners-Google 13 | Disallow: 14 | 15 | User-agent: Yahoo-NewsCrawler 16 | Disallow: 17 | 18 | User-Agent: msnbot 19 | Crawl-delay: 100 20 | Disallow: 21 | 22 | User-Agent: * 23 | Disallow: /config/ 24 | Disallow: /handlers/ 25 | Disallow: /includes/ 26 | Disallow: /interceptors/ 27 | Disallow: /layouts/ 28 | Disallow: /logs/ 29 | Disallow: /models/ 30 | Disallow: /modules/ 31 | Disallow: /views/ 32 | Allow: / -------------------------------------------------------------------------------- /tests/resources/app/tests/Application.cfc: -------------------------------------------------------------------------------- 1 | /** 2 | ******************************************************************************** 3 | Copyright 2005-2007 ColdBox Framework by Luis Majano and Ortus Solutions, Corp 4 | www.ortussolutions.com 5 | ******************************************************************************** 6 | */ 7 | component{ 8 | 9 | // APPLICATION CFC PROPERTIES 10 | this.name = "ColdBoxTestingSuite" & hash(getCurrentTemplatePath()); 11 | this.sessionManagement = true; 12 | this.sessionTimeout = createTimeSpan( 0, 0, 15, 0 ); 13 | this.applicationTimeout = createTimeSpan( 0, 0, 15, 0 ); 14 | this.setClientCookies = true; 15 | 16 | // Create testing mapping 17 | this.mappings[ "/tests" ] = getDirectoryFromPath( getCurrentTemplatePath() ); 18 | // Map back to its root 19 | rootPath = REReplaceNoCase( this.mappings[ "/tests" ], "tests(\\|/)", "" ); 20 | this.mappings["/root"] = rootPath; 21 | 22 | } -------------------------------------------------------------------------------- /tests/resources/app/tests/resources/HttpAntRunner.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/resources/app/tests/resources/RemoteFacade.cfc: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /tests/resources/app/tests/results/results_go_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/tests/results/results_go_here.txt -------------------------------------------------------------------------------- /tests/resources/app/tests/runner.cfm: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/resources/app/tests/specs/all_tests_go_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/tests/specs/all_tests_go_here.txt -------------------------------------------------------------------------------- /tests/resources/app/tests/specs/modules/integration/integration_tests_go_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/tests/specs/modules/integration/integration_tests_go_here.txt -------------------------------------------------------------------------------- /tests/resources/app/tests/specs/modules/module_tests_go_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/tests/specs/modules/module_tests_go_here.txt -------------------------------------------------------------------------------- /tests/resources/app/tests/specs/modules/unit/unit_tests_go_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coldbox-modules/quick/9db4e0926665704818188de2603ac590ed2b2b13/tests/resources/app/tests/specs/modules/unit/unit_tests_go_here.txt -------------------------------------------------------------------------------- /tests/resources/app/views/_templates/404.html: -------------------------------------------------------------------------------- 1 |  3 | 4 | 5 | 6 | 7 | 8 | The page you requested does not exist 9 | 22 | 23 | 24 | 25 |
26 |

The page you requested does not exist.

27 |

You may have mistyped the address or the page may have moved. Please check your address again.

28 |
29 | 30 | -------------------------------------------------------------------------------- /tests/resources/app/views/_templates/generic_error.cfm: -------------------------------------------------------------------------------- 1 |  2 |

An Unhandled Exception Occurred

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 37 | 38 | 39 | 40 | 41 | 42 |
An unhandled exception has occurred. Please look at the diagnostic information below:
Type#exception.getType()#
Message#exception.getMessage()#
Detail#exception.getDetail()#
Extended Info#exception.getExtendedInfo()#
Message#exception.getMessage()#
Tag Context 31 | 32 | 33 | 34 | #variables.tagCtx['template']# (#variables.tagCtx['line']#)
35 |
36 |
Stack Trace#exception.getStackTrace()#
43 |
44 | -------------------------------------------------------------------------------- /tests/resources/app/views/main/indexHelper.cfm: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102347_create_countries_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "countries", function( t ) { 5 | t.uuid( "id" ).primaryKey(); 6 | t.string( "name" ); 7 | t.timestamp( "created_date" ).withCurrent(); 8 | t.timestamp( "modified_date" ).withCurrent(); 9 | } ); 10 | 11 | qb.table( "countries" ).insert( [ 12 | { 13 | "id": "02B84D66-0AA0-F7FB-1F71AFC954843861", 14 | "name": "United States", 15 | "created_date": "2017-07-28 02:07:00", 16 | "modified_date": "2017-07-28 02:07:00" 17 | }, 18 | { 19 | "id": "02BA2DB0-EB1E-3F85-5F283AB5E45608C6", 20 | "name": "Argentina", 21 | "created_date": "2017-07-29 03:07:00", 22 | "modified_date": "2017-07-29 03:07:00" 23 | } 24 | ] ); 25 | } 26 | 27 | function down( schema, qb ) { 28 | schema.drop( "countries" ); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102455_create_offices_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "offices", function( t ) { 5 | t.increments( "id" ); 6 | t.string( "name" ); 7 | } ); 8 | 9 | qb.table( "offices" ).insert( [ 10 | { 11 | "id": 1, 12 | "name": "Acme" 13 | }, 14 | { 15 | "id": 2, 16 | "name": "Scranton" 17 | } 18 | ] ); 19 | } 20 | 21 | function down( schema, qb ) { 22 | schema.drop( "offices" ); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102500_create_teams_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "teams", function( t ) { 5 | t.increments( "id" ); 6 | t.string( "name" ); 7 | t.unsignedInteger( "officeId" ); 8 | } ); 9 | 10 | qb.table( "teams" ).insert( [ 11 | { 12 | "id": 1, 13 | "name": "Engineering", 14 | "officeId": 1 15 | }, 16 | { 17 | "id": 2, 18 | "name": "Management", 19 | "officeId": 1 20 | }, 21 | { 22 | "id": 3, 23 | "name": "Liabilities", 24 | "officeId": 2 25 | } 26 | ] ); 27 | } 28 | 29 | function down( schema, qb ) { 30 | schema.drop( "teams" ); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102508_create_permissions_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "permissions", function( t ) { 5 | t.increments( "id" ); 6 | t.string( "name" ); 7 | } ); 8 | 9 | qb.table( "permissions" ).insert( [ 10 | { 11 | "id": 1, 12 | "name": "MANAGE_USERS" 13 | }, 14 | { 15 | "id": 2, 16 | "name": "APPROVE_POSTS" 17 | } 18 | ] ); 19 | } 20 | 21 | function down( schema, qb ) { 22 | schema.drop( "permissions" ); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102514_create_roles_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "roles", function( t ) { 5 | t.increments( "id" ); 6 | t.string( "name" ); 7 | } ); 8 | 9 | qb.table( "roles" ).insert( [ 10 | { "id": 1, "name": "ADMIN" }, 11 | { "id": 2, "name": "MODERATOR" }, 12 | { "id": 3, "name": "USER" } 13 | ] ); 14 | } 15 | 16 | function down( schema, qb ) { 17 | schema.drop( "roles" ); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102522_create_permissions_roles_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "permissions_roles", function( t ) { 5 | t.unsignedInteger( "permissionId" ); 6 | t.unsignedInteger( "roleId" ); 7 | t.primaryKey( [ "permissionId", "roleId" ] ); 8 | } ); 9 | 10 | qb.table( "permissions_roles" ).insert( [ 11 | { 12 | "permissionId": 1, 13 | "roleId": 1 14 | }, 15 | { 16 | "permissionId": 2, 17 | "roleId": 1 18 | }, 19 | { 20 | "permissionId": 2, 21 | "roleId": 2 22 | } 23 | ] ); 24 | } 25 | 26 | function down( schema, qb ) { 27 | schema.drop( "permissions_roles" ); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102538_create_externalThings_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, query ) { 4 | schema.create( "externalThings", function( table ) { 5 | table.increments( "thingId" ); 6 | table.unsignedInteger( "userId" ); 7 | table.string( "externalId" ); 8 | table.string( "value" ).nullable(); 9 | } ); 10 | } 11 | 12 | function down( schema, query ) { 13 | schema.drop( "externalThings" ); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102549_create_roles_users_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "roles_users", function( t ) { 5 | t.unsignedInteger( "roleId" ); 6 | t.unsignedInteger( "userId" ); 7 | t.primaryKey( [ "roleId", "userId" ] ); 8 | } ); 9 | 10 | qb.table( "roles_users" ).insert( [ 11 | { "roleId": 1, "userId": 1 }, 12 | { "roleId": 3, "userId": 1 }, 13 | { "roleId": 3, "userId": 2 }, 14 | { "roleId": 3, "userId": 3 }, 15 | { "roleId": 2, "userId": 4 }, 16 | { "roleId": 3, "userId": 4 } 17 | ] ); 18 | } 19 | 20 | function down( schema, qb ) { 21 | schema.drop( "roles_users" ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102557_create_my_posts_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "my_posts", function( t ) { 5 | t.increments( "post_pk" ); 6 | t.unsignedInteger( "user_id" ).nullable(); 7 | t.text( "body" ); 8 | t.timestamp( "created_date" ).withCurrent(); 9 | t.timestamp( "modified_date" ).withCurrent(); 10 | t.timestamp( "published_date" ).nullable(); 11 | } ); 12 | 13 | qb.table( "my_posts" ).insert( [ 14 | { 15 | "post_pk": 1245, 16 | "user_id": 1, 17 | "body": "My awesome post body", 18 | "created_date": "2017-07-28 02:07:00", 19 | "modified_date": "2017-07-28 02:07:00", 20 | "published_date": "2017-07-28 02:07:00" 21 | }, 22 | { 23 | "post_pk": 523526, 24 | "user_id": 1, 25 | "body": "My second awesome post body", 26 | "created_date": "2017-07-28 02:07:36", 27 | "modified_date": "2017-07-28 02:07:36", 28 | "published_date": { "value": "", "null": true } 29 | }, 30 | { 31 | "post_pk": 7777, 32 | "user_id": { "value": "", "null": true }, 33 | "body": "My post with no author", 34 | "created_date": "2017-07-30 07:00:22", 35 | "modified_date": "2017-07-30 07:00:22", 36 | "published_date": { "value": "", "null": true } 37 | }, 38 | { 39 | "post_pk": 321, 40 | "user_id": 4, 41 | "body": "My post with a different author", 42 | "created_date": "2017-08-28 14:22:22", 43 | "modified_date": "2017-08-28 14:22:22", 44 | "published_date": "2017-08-28 14:22:22" 45 | } 46 | ] ); 47 | } 48 | 49 | function down( schema, qb ) { 50 | schema.drop( "my_posts" ); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102605_create_videos_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "videos", function( t ) { 5 | t.increments( "id" ); 6 | t.string( "url" ); 7 | t.string( "title" ); 8 | t.string( "description" ); 9 | t.timestamp( "created_date" ).withCurrent(); 10 | t.timestamp( "modified_date" ).withCurrent(); 11 | } ); 12 | 13 | qb.table( "videos" ).insert( [ 14 | { 15 | "id": 1, 16 | "url": "https://www.youtube.com/watch?v=JDzIypmP0eo", 17 | "title": "Building KiteTail with Adam Wathan", 18 | "description": "Awesome live coding experience", 19 | "created_date": "2017-06-28 02:07:36", 20 | "modified_date": "2017-06-30 12:17:24" 21 | }, 22 | { 23 | "id": 1245, 24 | "url": "https://www.youtube.com/watch?v=BgAlQuqzl8o", 25 | "title": "Cello Wars", 26 | "description": "Star Wars Cello Parody", 27 | "created_date": "2017-07-02 04:14:22", 28 | "modified_date": "2017-07-02 04:14:22" 29 | } 30 | ] ); 31 | } 32 | 33 | function down( schema, query ) { 34 | schema.drop( "videos" ); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102612_create_comments_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "comments", function( t ) { 5 | t.increments( "id" ); 6 | t.text( "body" ); 7 | t.unsignedInteger( "commentable_id" ); 8 | t.string( "commentable_type" ); 9 | t.string( "designation" ).default( "public" ); 10 | t.unsignedInteger( "user_id" ); 11 | t.timestamp( "created_date" ).withCurrent(); 12 | t.timestamp( "modified_date" ).withCurrent(); 13 | } ); 14 | 15 | qb.table( "comments" ).insert( [ 16 | { 17 | "id": 1, 18 | "body": "I thought this post was great", 19 | "commentable_id": 1245, 20 | "commentable_type": "Post", 21 | "designation": "public", 22 | "user_id": 1, 23 | "created_date": "2017-07-02 04:14:22", 24 | "modified_date": "2017-07-02 04:14:22" 25 | }, 26 | { 27 | "id": 2, 28 | "body": "I thought this post was not so good", 29 | "commentable_id": 321, 30 | "commentable_type": "Post", 31 | "designation": "public", 32 | "user_id": 2, 33 | "created_date": "2017-07-04 04:14:22", 34 | "modified_date": "2017-07-04 04:14:22" 35 | }, 36 | { 37 | "id": 3, 38 | "body": "What a great video! So fun!", 39 | "commentable_id": 1245, 40 | "commentable_type": "Video", 41 | "designation": "public", 42 | "user_id": 1, 43 | "created_date": "2017-07-02 04:14:22", 44 | "modified_date": "2017-07-02 04:14:22" 45 | } 46 | ] ); 47 | } 48 | 49 | function down( schema, qb ) { 50 | schema.drop( "comments" ); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102613_create_internal_comments_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "internalComments", function( t ) { 5 | t.unsignedInteger( "FK_comment" ) 6 | .references( "id" ) 7 | .onTable( "comments" ) 8 | .onUpdate( "CASCADE" ) 9 | .onDelete( "CASCADE" ); 10 | t.text( "reason" ); 11 | t.primaryKey( "FK_comment" ); 12 | } ); 13 | 14 | qb.newQuery().table( "comments" ).insert( [ 15 | { 16 | "id": 4, 17 | "body": "This is an internal comment. It is very, very private.", 18 | "commentable_id": 1245, 19 | "commentable_type": "Post", 20 | "designation": "internal", 21 | "user_id": 1, 22 | "created_date": "2017-07-02 04:14:22", 23 | "modified_date": "2017-07-02 04:14:22" 24 | } 25 | ] ); 26 | 27 | qb.newQuery().table( "internalComments" ).insert( [ 28 | { 29 | "FK_comment": 4, 30 | "reason": "Utra private, ya know?" 31 | } 32 | ] ); 33 | } 34 | 35 | function down( schema, qb ) { 36 | schema.drop( "internalComments" ); 37 | qb.table( "comments" ).where( "id", 4 ).delete(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102619_create_tags_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "tags", function( t ) { 5 | t.increments( "id" ); 6 | t.string( "name" ); 7 | } ); 8 | 9 | qb.table( "tags" ) 10 | .insert( [ 11 | { "id" : 1, "name" : "programming" }, 12 | { "id" : 2, "name" : "music" }, 13 | { "id" : 3, "name" : "gaming" } 14 | ] ); 15 | } 16 | 17 | function down( schema, qb ) { 18 | schema.drop( "tags" ); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102625_create_my_posts_tags_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "my_posts_tags", function( t ) { 5 | t.unsignedInteger( "custom_post_pk" ); 6 | t.unsignedInteger( "tag_id" ); 7 | t.primaryKey( [ "custom_post_pk", "tag_id" ] ); 8 | } ); 9 | 10 | qb.table( "my_posts_tags" ) 11 | .insert( [ 12 | { 13 | "custom_post_pk" : 1245, 14 | "tag_id" : 1 15 | }, 16 | { 17 | "custom_post_pk" : 1245, 18 | "tag_id" : 2 19 | }, 20 | { 21 | "custom_post_pk" : 523526, 22 | "tag_id" : 1 23 | }, 24 | { 25 | "custom_post_pk" : 523526, 26 | "tag_id" : 2 27 | }, 28 | { 29 | "custom_post_pk" : 523526, 30 | "tag_id" : 3 31 | }, 32 | { "custom_post_pk" : 321, "tag_id" : 2 } 33 | ] ); 34 | } 35 | 36 | function down( schema, qb ) { 37 | schema.drop( "my_posts_tags" ); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102636_create_links_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "links", function( t ) { 5 | t.increments( "link_id" ); 6 | t.string( "link_url" ); 7 | t.timestamp( "created_date" ).withCurrent(); 8 | t.timestamp( "modified_date" ).withCurrent(); 9 | } ); 10 | 11 | qb.table( "links" ).insert( [ 12 | { 13 | "link_id": 1, 14 | "link_url": "http://example.com/some-link", 15 | "created_date": "2017-07-28 02:07:00" 16 | } 17 | ] ); 18 | } 19 | 20 | function down( schema, qb ) { 21 | schema.drop( "links" ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102640_create_referrals_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "referrals", function( t ) { 5 | t.increments( "id" ); 6 | t.string( "type" ); 7 | t.timestamp( "created_date" ).withCurrent(); 8 | t.timestamp( "modified_date" ).withCurrent(); 9 | } ); 10 | 11 | qb.table( "referrals" ).insert( [ 12 | { 13 | "id": 1, 14 | "type": "external", 15 | "created_date": "2017-07-28 02:07:00", 16 | "modified_date": "2017-07-28 02:07:00" 17 | } 18 | ] ); 19 | } 20 | 21 | function down( schema, qb ) { 22 | schema.drop( "referrals" ); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102649_create_songs_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "songs", function( t ) { 5 | t.increments( "id" ); 6 | t.string( "title" ).nullable(); 7 | t.string( "download_url" ); 8 | 9 | t.raw( "`created_date` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)" ); 10 | t.raw( "`modified_date` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)" ); 11 | 12 | //proposed qb pull request https://github.com/coldbox-modules/qb/pull/282: 13 | //t.timestamp( "created_date", 6 ).withCurrent( 6 ); 14 | //t.timestamp( "modified_date", 6 ).withCurrent( 6 ); 15 | } ); 16 | 17 | qb.table( "songs" ).insert( [ 18 | { 19 | "id": 1, 20 | "title": "Ode to Joy", 21 | "download_url": "https://open.spotify.com/track/4Nd5HJn4EExnLmHtClk4QV", 22 | "created_date": "2017-07-28 02:07:00", 23 | "modified_date": "2017-07-28 02:07:00" 24 | }, 25 | { 26 | "id": 2, 27 | "title": "Open Arms", 28 | "download_url": "https://open.spotify.com/track/1m2INxep6LfNa25OEg5jZl", 29 | "created_date": "2017-07-28 02:07:00", 30 | "modified_date": "2017-07-28 02:07:00" 31 | } 32 | ] ); 33 | } 34 | 35 | function down( schema, qb ) { 36 | schema.drop( "songs" ); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102650_create_jingles_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "jingles", function( t ) { 5 | t.unsignedInteger( "FK_song" ) 6 | .references( "id" ) 7 | .onTable( "songs" ) 8 | .onUpdate( "CASCADE" ) 9 | .onDelete( "CASCADE" ); 10 | t.integer( "catchiness" ); 11 | t.primaryKey( "FK_song" ); 12 | } ); 13 | 14 | qb.newQuery().table( "songs" ).insert( [ 15 | { 16 | "id": 3, 17 | "title": "I Wish I Was an Oscar Mayer Weiner", 18 | "download_url": "https://open.spotify.com/track/2wyg2ln6p4gEkdqM2mueLn?si=kWBpdUz1TLymdmTro-xjtw", 19 | "created_date": "2017-07-28 02:07:00", 20 | "modified_date": "2017-07-28 02:07:00" 21 | } 22 | ] ); 23 | 24 | qb.newQuery().table( "jingles" ).insert( [ 25 | { 26 | "FK_song": 3, 27 | "catchiness": 3 28 | } 29 | ] ); 30 | } 31 | 32 | function down( schema, qb ) { 33 | schema.drop( "jingles" ); 34 | qb.table( "songs" ).where( "id", 3 ).delete(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102656_create_phone_numbers_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "phone_numbers", function( t ) { 5 | t.increments( "id" ); 6 | t.string( "number" ); 7 | t.boolean( "active" ); 8 | t.boolean( "confirmed" ).nullable(); 9 | } ); 10 | 11 | qb.table( "phone_numbers" ) 12 | .insert( [ 13 | { 14 | "id" : 1, 15 | "number" : "323-232-3232", 16 | "active" : 1, 17 | "confirmed" : 1 18 | }, 19 | { 20 | "id" : 2, 21 | "number" : "545-454-5454", 22 | "active" : 0, 23 | "confirmed" : 0 24 | }, 25 | { 26 | "id" : 3, 27 | "number" : "878-787-8787", 28 | "active" : 1, 29 | "confirmed" : { "value" : "", "null" : true } 30 | } 31 | ] ); 32 | } 33 | 34 | function down( schema, qb ) { 35 | schema.drop( "phone_numbers" ); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102704_create_empty_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, query ) { 4 | schema.create( "empty", function( table ) { 5 | table.increments( "id" ); 6 | } ); 7 | } 8 | 9 | function down( schema, query ) { 10 | schema.drop( "empty" ); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102707_create_a_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, query ) { 4 | schema.create( "a", function( table ) { 5 | table.increments( "id" ); 6 | table.string( "name" ); 7 | } ); 8 | } 9 | 10 | function down( schema, query ) { 11 | schema.drop( "a" ); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102710_create_b_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, query ) { 4 | schema.create( "b", function( table ) { 5 | table.increments( "id" ); 6 | table.unsignedInteger( "a_id" ).nullable(); 7 | table.string( "name" ); 8 | } ); 9 | } 10 | 11 | function down( schema, query ) { 12 | schema.drop( "b" ); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102718_create_composites_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "composites", function( t ) { 5 | t.unsignedInteger( "a" ); 6 | t.unsignedInteger( "b" ); 7 | t.primaryKey( [ "a", "b" ] ); 8 | } ); 9 | 10 | qb.table( "composites" ).insert( [ 11 | { "a": 1, "b": 1 }, 12 | { "a": 1, "b": 2 } 13 | ] ); 14 | } 15 | 16 | function down( schema, qb ) { 17 | schema.drop( "composites" ); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102725_create_composite_children_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "composite_children", function( t ) { 5 | t.increments( "id" ); 6 | t.unsignedInteger( "composite_a" ); 7 | t.unsignedInteger( "composite_b" ); 8 | } ); 9 | 10 | qb.table( "composite_children" ).insert( [ 11 | { 12 | "id": 1, 13 | "composite_a": 1, 14 | "composite_b": 2 15 | }, 16 | { 17 | "id": 2, 18 | "composite_a": 2, 19 | "composite_b": 2 20 | } 21 | ] ); 22 | } 23 | 24 | function down( schema, qb ) { 25 | schema.drop( "composite_children" ); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102734_create_themes_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "themes", function( t ) { 5 | t.increments( "id" ); 6 | t.string( "slug" ); 7 | t.string( "version" ); 8 | t.text( "config" ).nullable(); 9 | } ); 10 | 11 | qb.table( "themes" ).insert( [ 12 | { 13 | "id": 1, 14 | "slug": "theme-a", 15 | "version": "1.0.0" 16 | } 17 | ] ); 18 | } 19 | 20 | function down( schema, query ) { 21 | schema.drop( "themes" ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102739_create_parents_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "parents", function( t ) { 5 | t.increments( "ID" ); 6 | t.string( "firstname" ); 7 | t.string( "lastname" ); 8 | } ); 9 | 10 | qb.table( "parents" ).insert( [ 11 | { 12 | "ID": 1, 13 | "firstName": "Amy", 14 | "lastName": "Pond" 15 | }, 16 | { 17 | "ID": 2, 18 | "firstName": "Rory", 19 | "lastName": "Williams" 20 | } 21 | ] ); 22 | } 23 | 24 | function down( schema, qb ) { 25 | schema.drop( "parents" ); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102747_create_families_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "families", function( t ) { 5 | t.increments( "familyID" ); 6 | t.unsignedInteger( "parent1ID" ).nullable(); 7 | t.unsignedInteger( "parent2ID" ).nullable(); 8 | } ); 9 | 10 | qb.table( "families" ).insert( [ 11 | { 12 | "familyID": 1, 13 | "parent1ID": 1, 14 | "parent2ID": 2 15 | } 16 | ] ); 17 | } 18 | 19 | function down( schema, qb ) { 20 | schema.drop( "families" ); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102757_create_family_parents_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, query ) { 4 | schema.create( "family_parents", function( table ) { 5 | table.unsignedInteger( "parentID" ); 6 | table.unsignedInteger( "familyID" ); 7 | table.primaryKey( [ "parentID", "familyID" ] ); 8 | } ); 9 | } 10 | 11 | function down( schema, query ) { 12 | schema.drop( "family_parents" ); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102803_create_children_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "children", function( t ) { 5 | t.increments( "childID" ); 6 | t.unsignedInteger( "familyID" ); 7 | t.string( "firstname" ); 8 | t.string( "lastname" ); 9 | } ); 10 | 11 | qb.table( "children" ).insert( [ 12 | { 13 | "childID": 1, 14 | "familyID": 1, 15 | "firstName": "River", 16 | "lastName": "Song" 17 | } 18 | ] ); 19 | } 20 | 21 | function down( schema, query ) { 22 | schema.drop( "children" ); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102811_create_registrations_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "registrations", function( t ) { 5 | t.increments( "registrationID" ); 6 | t.unsignedInteger( "childID" ); 7 | } ); 8 | 9 | qb.table( "registrations" ).insert( [ 10 | { "registrationID": 1, "childID": 1 } 11 | ] ); 12 | } 13 | 14 | function down( schema, qb ) { 15 | schema.drop( "registrations" ); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102821_create_playing_fields_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "playing_fields", function( t ) { 5 | t.unsignedInteger( name = "fieldID", autoIncrement = true ); 6 | t.unsignedInteger( "clientID" ).nullable(); 7 | t.string( "fieldName" ); 8 | t.primaryKey( [ "fieldID", "clientID" ] ); 9 | t.uuid( "country_id" ).nullable(); 10 | } ); 11 | 12 | qb.table( "playing_fields" ).insert( [ 13 | { 14 | "fieldID": 1, 15 | "clientID": 1, 16 | "fieldName": "First Field", 17 | "country_id": "02B84D66-0AA0-F7FB-1F71AFC954843861" // United States 18 | }, 19 | { 20 | "fieldID": 1, 21 | "clientID": 2, 22 | "fieldName": "Second Field", 23 | "country_id": "02BA2DB0-EB1E-3F85-5F283AB5E45608C6" // Argentina 24 | } 25 | ] ); 26 | } 27 | 28 | function down( schema, qb ) { 29 | schema.drop( "playing_fields" ); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2020_08_11_102835_create_games_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "games", function( t ) { 5 | t.increments( "ID" ); 6 | t.unsignedInteger( "fieldID" ).nullable(); 7 | t.unsignedInteger( "clientID" ).nullable(); 8 | } ); 9 | 10 | qb.table( "games" ).insert( [ 11 | { 12 | "ID": 1, 13 | "fieldID": 1, 14 | "clientID": 2 15 | } 16 | ] ); 17 | } 18 | 19 | function down( schema, qb ) { 20 | schema.drop( "games" ); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2022_06_24_102347_create_products_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "products", function( table ) { 5 | table.increments( "ID" ); 6 | table.string( "name" ).nullable(); 7 | table.string( "type" ).nullable(); // ( books|music ) 8 | table.string( "isbn" ).nullable(); // books only 9 | table.string( "artist" ).nullable(); // music only 10 | table.text( "metadata" ).nullable(); 11 | table.unsignedInteger( "user_id" ); // relationship to user 12 | } ); 13 | 14 | qb.newQuery().table( "products" ).insert( { 15 | "ID": 1, 16 | "name": "The Lord Of The Rings", 17 | "type": "book", 18 | "isbn": "9780544003415", 19 | "user_id": 2, 20 | "metadata": "{}" 21 | } ); 22 | 23 | qb.newQuery().table( "products" ).insert( { 24 | "ID": 2, 25 | "name": "Jeremy", 26 | "type": "music", 27 | "artist": "Pearl Jam", 28 | "user_id": 1, 29 | "metadata": "{}" 30 | } ); 31 | } 32 | 33 | function down( schema, query ) { 34 | schema.drop( "products" ); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2022_10_17_114732_create_rmme_tables.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "RMME_A", function( table ) { 5 | table.unsignedInteger( "ID_A" ).nullable(); 6 | table.unsignedInteger( "ID_B" ).nullable(); 7 | table.primaryKey( "ID_A" ); 8 | } ); 9 | 10 | schema.create( "RMME_B", function( table ) { 11 | table.unsignedInteger( "ID_A" ).nullable(); 12 | table.unsignedInteger( "ID_B" ).nullable(); 13 | table.unsignedInteger( "ID_C" ).nullable(); 14 | table.primaryKey( "ID_B" ); 15 | } ); 16 | 17 | schema.create( "RMME_C", function( table ) { 18 | table.unsignedInteger( "ID_B" ).nullable(); 19 | table.unsignedInteger( "ID_C" ).nullable(); 20 | table.primaryKey( "ID_C" ); 21 | } ); 22 | 23 | qb.newQuery().table( "RMME_A" ).insert( { "ID_A": 1, "ID_B": 1 } ); 24 | qb.newQuery().table( "RMME_B" ).insert( { "ID_A": 1, "ID_B": 1, "ID_C": 1 } ); 25 | qb.newQuery().table( "RMME_C" ).insert( { "ID_B": 1, "ID_C": 1 } ); 26 | } 27 | 28 | function down( schema, query ) { 29 | schema.drop( "products" ); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2023_07_18_133601_create_purchases_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "purchases", function( t ) { 5 | t.increments( "id" ); 6 | t.unsignedInteger( "userId" ); 7 | t.unsignedInteger( "price" ); 8 | t.unsignedInteger( "quantity" ); 9 | t.datetime( "createdDate" ).withCurrent(); 10 | } ); 11 | 12 | qb.newQuery().table( "purchases" ).insert( [ 13 | { "userId": 1, "price": 100, "quantity": 3 }, 14 | { "userId": 1, "price": 50, "quantity": 2 }, 15 | { "userId": 1, "price": 30, "quantity": 1 }, 16 | { "userId": 4, "price": 40, "quantity": 1 }, 17 | { "userId": 4, "price": 20, "quantity": 1 }, 18 | { "userId": 5, "price": 50, "quantity": 3 } 19 | ] ) 20 | } 21 | 22 | function down( schema, query ) { 23 | schema.drop( "purchases" ); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2023_10_25_102757_add_comment_column.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, query ) { 4 | 5 | var defaultJson = '{ "analyzed": true, "magnitude": 0.8, "score": 0.6 }'; 6 | 7 | schema.alter( "comments", function( table ) { 8 | table.addColumn( table.string( "sentimentAnalysis" ).nullable() ); 9 | } ); 10 | 11 | 12 | query.from( "comments" ) 13 | .update( { "sentimentAnalysis" = defaultJson } ); 14 | 15 | } 16 | 17 | function down( schema, query ) { 18 | schema.alter( "comments", function( table ) { 19 | table.dropColumn( "sentimentAnalysis" ); 20 | } ); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2024_01_03_100700_create_products_b_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "products_b", function( table ) { 5 | table.uuid( "id" ).primaryKey(); 6 | table.timestamp( "createdDate" ).withCurrent(); 7 | table.timestamp( "modifiedDate" ).withCurrent(); 8 | table.timestamp( "deletedDate" ).nullable(); 9 | table.string( "name" ); 10 | table.string( "itemNumber" ); 11 | } ); 12 | 13 | qb.newQuery().table( "products_b" ).insert( { 14 | "id": "BDC3F099-0FBF-4334-AFEAEFFD06C8AAD8", 15 | "name": "Test Product A", 16 | "itemNumber": "A14124GSD423" 17 | } ); 18 | } 19 | 20 | function down( schema, qb ) { 21 | schema.drop( "products_b" ); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /tests/resources/database/migrations/2024_01_03_100701_create_product_skus_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "product_skus", function( table ) { 5 | table.uuid( "id" ).primaryKey(); 6 | table.timestamp( "createdDate" ).withCurrent(); 7 | table.timestamp( "modifiedDate" ).withCurrent(); 8 | table.timestamp( "deletedDate" ).nullable(); 9 | table.uuid( "productId" ).references( "id" ).onTable( "products_b" ); 10 | table.string( "designation" ); 11 | } ); 12 | 13 | qb.newQuery().table( "product_skus" ).insert( [ 14 | { 15 | "id": "E9B52E1B-66BB-4ACE-B8306808B4E64EA3", 16 | "productId": "BDC3F099-0FBF-4334-AFEAEFFD06C8AAD8", 17 | "designation": "apparel" 18 | }, 19 | { 20 | "id": "E917DAFA-AD36-4601-9F11A0B54C3B5502", 21 | "productId": "BDC3F099-0FBF-4334-AFEAEFFD06C8AAD8", 22 | "designation": "product" 23 | } 24 | ] ); 25 | } 26 | 27 | function down( schema, qb ) { 28 | schema.drop( "product_skus" ); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /tests/resources/database/migrations/2024_01_03_100702_create_apparel_skus_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "apparel_skus", function( table ) { 5 | table.uuid( "id" ).primaryKey(); 6 | table.decimal( "cost", 10, 2 ); 7 | table.string( "color" ); 8 | table.string( "size1" ); 9 | table.string( "size1Description" ); 10 | table.integer( "size1Index" ).default( 0 ); 11 | } ); 12 | 13 | qb.newQuery().table( "apparel_skus" ).insert( { 14 | "id": "E9B52E1B-66BB-4ACE-B8306808B4E64EA3", 15 | "cost": 10.00, 16 | "color": "black", 17 | "size1": "S", 18 | "size1Description": "Small" 19 | } ); 20 | } 21 | 22 | function down( schema, qb ) { 23 | schema.drop( "apparel_skus" ); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /tests/resources/database/migrations/2024_05_30_203551_create_categories_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "categories", function( table ) { 5 | table.unsignedInteger( "id" ).primaryKey(); 6 | table.unsignedInteger( "parentId" ).nullable(); 7 | } ); 8 | 9 | qb.newQuery().table( "categories" ).insert( [ 10 | { "id": 1, "parentId": { "value": 0, "null": true, "nulls": true } }, 11 | { "id": 2, "parentId": 1 } 12 | ] ); 13 | } 14 | 15 | function down( schema, qb ) { 16 | schema.drop( "categories" ); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /tests/resources/database/migrations/2024_09_24_114812_create_trip_components_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "trip_planner_reservation_component", function( table ) { 5 | table.unsignedInteger( "componentID" ); 6 | table.unsignedInteger( "reservationID" ); 7 | table.unsignedInteger( "tripComponentID" ); 8 | table.string( "type" ); 9 | table.bit( "isActive" ); 10 | table.datetime( "createdDate" ).withCurrent(); 11 | table.unsignedInteger( "createdByAgentId" ).nullable(); 12 | table.unsignedInteger( "createdByMemberId" ).nullable(); 13 | table.datetime( "modifiedDate" ).withCurrent(); 14 | table.unsignedInteger( "modifiedByAgentId" ).nullable(); 15 | table.unsignedInteger( "modifiedByMemberId" ).nullable(); 16 | table.unsignedInteger( "lastModifiedBy" ).nullable(); 17 | } ); 18 | 19 | qb.newQuery() 20 | .table( "trip_planner_reservation_component" ) 21 | .insert( [ 22 | { 23 | "componentID" : 4, 24 | "reservationID" : 5, 25 | "tripComponentID" : 2, 26 | "type" : "cruise", 27 | "isActive" : 1, 28 | "createdDate" : now(), 29 | "modifiedDate" : now() 30 | } 31 | ] ); 32 | } 33 | 34 | function down( schema, qb ) { 35 | schema.drop( "trip_planner_reservation_component" ); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2024_09_24_115820_create_trip_components_cruise_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "trip_planner_reservation_component_cruise", function( table ) { 5 | table.unsignedInteger( "cruiseComponentID" ); 6 | table.unsignedInteger( "componentID" ); 7 | table.string( "confirmationNumber" ); 8 | } ); 9 | 10 | qb.newQuery() 11 | .table( "trip_planner_reservation_component_cruise" ) 12 | .insert( [ 13 | { 14 | "cruiseComponentID" : 11, 15 | "componentID" : 4, 16 | "confirmationNumber" : "ABC123" 17 | } 18 | ] ); 19 | } 20 | 21 | function down( schema, qb ) { 22 | schema.drop( "trip_planner_reservation_component_cruise" ); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/resources/database/migrations/2025_01_24_145656_create_actors_table.cfc: -------------------------------------------------------------------------------- 1 | component { 2 | 3 | function up( schema, qb ) { 4 | schema.create( "actors", function( t ) { 5 | t.guid( "id" ).primaryKey(); 6 | t.string( "name" ); 7 | t.timestamp( "created_date" ).withCurrent(); 8 | t.timestamp( "modified_date" ).withCurrent(); 9 | } ); 10 | 11 | qb.table( "actors" ).insert( [ 12 | { 13 | "id": "5B8A472F-56E8-4BD6-A03D-6157662937E3", 14 | "name": "Tom Anks", 15 | "created_date": createDateTime( 2017, 07, 28, 02, 07, 00 ), 16 | "modified_date": createDateTime( 2017, 07, 28, 02, 07, 00 ) 17 | } 18 | ] ); 19 | } 20 | 21 | function down( schema, qb ) { 22 | schema.drop( "actors" ); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/runner.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/AggregateSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Aggregate Spec", function() { 5 | it( "can retrieve aggregates with no issues", function() { 6 | expect( getInstance( "User" ).count() ).toBe( 5 ); 7 | expect( getInstance( "User" ).whereUsername( "elpete" ).count() ).toBe( 1 ); 8 | } ); 9 | } ); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/AttributeCasingSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Attributes Casing Spec", function() { 5 | it( "defaults to no transformation", function() { 6 | var post = getInstance( "Post" ).find( 1245 ); 7 | 8 | expect( post.retrieveAttributesData() ).toHaveKey( "post_pk" ); 9 | expect( post.retrieveAttributesData() ).notToHaveKey( "PostPk" ); 10 | 11 | expect( post.getPost_Pk() ).notToBeNull(); 12 | 13 | post.setCreatedDate( now() ); 14 | 15 | expect( post.retrieveAttributesData() ).toHaveKey( "created_date" ); 16 | } ); 17 | 18 | it( "converts stores all attributes internally as snake case when the `attributecasing` metadata property is set to `snake`", function() { 19 | var user = getInstance( "User" ).find( 1 ); 20 | 21 | expect( user.retrieveAttributesData() ).toHaveKey( "first_name" ); 22 | expect( user.retrieveAttributesData() ).notToHaveKey( "firstName" ); 23 | 24 | expect( function() { 25 | user.getFirstName(); 26 | } ).notToThrow(); 27 | 28 | expect( function() { 29 | user.getFirstName(); 30 | } ).notToThrow(); 31 | 32 | user.setCreatedDate( now() ); 33 | 34 | expect( user.retrieveAttributesData() ).notToHaveKey( "createdDate" ); 35 | expect( user.retrieveAttributesData() ).toHaveKey( "created_date" ); 36 | } ); 37 | } ); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/AttributeHashSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Attribute Hash Spec", function() { 5 | it( "can compute attributes hash", function() { 6 | var user = getInstance( "User" ).find( 1 ); 7 | var hash = user.computeAttributesHash( user.retrieveAttributesData() ); 8 | expect( hash ).toBeTypeOf( "string" ); 9 | } ); 10 | 11 | it( "can compute attributes hash correctly", function() { 12 | var passwordHash = hash( "password" ) 13 | var user = getInstance( "User" ).populate( 14 | { 15 | "username" : "JaneDoe", 16 | "first_name" : "Jane", 17 | "last_name" : "Doe", 18 | "password" : passwordHash, 19 | "non-existant-property" : "any-value" 20 | }, 21 | true 22 | ); 23 | 24 | var expectedHash = hash( "first_name=Jane&last_name=Doe&password=#passwordHash#&username=JaneDoe" ); 25 | var hash = user.computeAttributesHash( user.retrieveAttributesData() ); 26 | expect( expectedHash ).toBe( hash, "The computeAttributesHash method does not return the correct hash" ); 27 | } ); 28 | } ); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/CloneSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Clone Spec", function() { 5 | it( "can clone with matching attributes", function() { 6 | var user = getInstance( "User" ).find( 1 ); 7 | var clonedUser = user.clone(); 8 | 9 | var hashA = user.computeAttributesHash( user.retrieveAttributesData() ); 10 | var hashB = user.computeAttributesHash( clonedUser.retrieveAttributesData() ); 11 | 12 | expect( hashA ).toBe( hashB, "The cloned entity should have an identical hash" ); 13 | } ); 14 | 15 | it( "can clone an entity as not loaded", function() { 16 | var user = getInstance( "User" ).find( 1 ); 17 | expect( user.isLoaded() ).toBeTrue( "The user instance should be found and loaded, but was not." ); 18 | var clonedUser = user.clone(); 19 | expect( clonedUser.isLoaded() ).toBeFalse( "The cloned user instance should be not be marked as loaded, but was." ); 20 | } ); 21 | 22 | it( "can clone an entity as loaded", function() { 23 | var user = getInstance( "User" ).find( 1 ); 24 | expect( user.isLoaded() ).toBeTrue( "The user instance should be found and loaded, but was not." ); 25 | var clonedUser = user.clone( markLoaded = true ); 26 | expect( clonedUser.isLoaded() ).toBeTrue( "The cloned user instance should be marked as loaded, but was not." ); 27 | } ); 28 | 29 | it( "can clone a QuickBuilder instance", function() { 30 | var userBuilder = getInstance( "User" ).orderBy( "id" ); 31 | var userBuilder2 = userBuilder.clone(); 32 | userBuilder2.clearOrders(); 33 | expect( userBuilder.getOrders() ).toBe( [ 34 | { 35 | "column" : "users.id", 36 | "direction" : "asc" 37 | } 38 | ] ); 39 | expect( userBuilder2.getOrders() ).toBe( [] ); 40 | } ); 41 | } ); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/ColumnsSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Columns", function() { 5 | it( "can access the attributes by their alias", function() { 6 | var link = getInstance( "Link" ).findOrFail( 1 ); 7 | 8 | var configuredAttributes = link.get_Attributes(); 9 | expect( configuredAttributes ).toBeStruct(); 10 | 11 | var attributeNames = link.retrieveAttributeNames( asColumnNames = true ); 12 | arraySort( attributeNames, "textnocase" ); 13 | expect( attributeNames ).toBeArray(); 14 | expect( attributeNames ).toHaveLength( 3 ); 15 | expect( attributeNames ).toBe( [ 16 | "created_date", 17 | "link_id", 18 | "link_url" 19 | ] ); 20 | 21 | expect( link.getLink_Id() ).toBe( 1 ); 22 | expect( link.getLink_Id() ).toBe( link.retrieveAttributesData()[ "link_id" ] ); 23 | expect( link.getUrl() ).toBe( "http://example.com/some-link" ); 24 | expect( link.getUrl() ).toBe( link.retrieveAttributesData()[ "link_url" ] ); 25 | } ); 26 | 27 | it( "ignores non-persistent attributes", function() { 28 | expect( function() { 29 | var link = getInstance( "Link" ).findOrFail( 1 ); 30 | } ).notToThrow(); 31 | } ); 32 | 33 | it( "translates attributes to their column names", function() { 34 | expect( function() { 35 | getInstance( "Link" ).create( { url : "https://example.com" } ); 36 | } ).notToThrow(); 37 | } ); 38 | 39 | it( "translates attributes to their column names when applying query restrictions", function() { 40 | var john = getInstance( "User" ).where( "firstName", "John" ).first(); 41 | expect( john ).notToBeNull(); 42 | expect( john.getId() ).toBe( 2 ); 43 | expect( john.getUsername() ).toBe( "johndoe" ); 44 | 45 | var bindings = getInstance( "User" ).where( "firstName", "firstName" ).getBindings(); 46 | expect( bindings ).toBeArray(); 47 | expect( bindings ).toHaveLength( 1 ); 48 | expect( bindings[ 1 ] ).toBeStruct(); 49 | expect( bindings[ 1 ].value ).toBe( "firstName" ); 50 | } ); 51 | } ); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/CreateSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Create Spec", function() { 5 | it( "can create and return a model that is already saved in the database", function() { 6 | var user = getInstance( "User" ).create( { 7 | "username" : "JaneDoe", 8 | "first_name" : "Jane", 9 | "last_name" : "Doe", 10 | "password" : hash( "password" ) 11 | } ); 12 | expect( user.isLoaded() ).toBeTrue(); 13 | expect( 14 | user.newEntity() 15 | .where( "username", "JaneDoe" ) 16 | .first() 17 | ).notToBeNull(); 18 | } ); 19 | 20 | it( "can ignore non-existant properties", function() { 21 | var user = getInstance( "User" ).create( 22 | { 23 | "username" : "JaneDoe", 24 | "first_name" : "Jane", 25 | "last_name" : "Doe", 26 | "password" : hash( "password" ), 27 | "non-existant-property" : "any-value" 28 | }, 29 | true 30 | ); 31 | expect( user.isLoaded() ).toBeTrue(); 32 | expect( 33 | user.newEntity() 34 | .where( "username", "JaneDoe" ) 35 | .first() 36 | ).notToBeNull(); 37 | } ); 38 | } ); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/DeleteSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Delete Spec", function() { 5 | it( "can delete an entity", function() { 6 | var user = getInstance( "User" ).where( "username", "johndoe" ).firstOrFail(); 7 | user.delete(); 8 | 9 | expect( getInstance( "User" ).count() ).toBe( 4 ); 10 | expect( function() { 11 | getInstance( "User" ).where( "username", "johndoe" ).firstOrFail(); 12 | } ).toThrow( type = "EntityNotFound" ); 13 | } ); 14 | 15 | it( "can delete multiple entities at once", function() { 16 | getInstance( "User" ).deleteAll(); 17 | expect( getInstance( "User" ).count() ).toBe( 0 ); 18 | } ); 19 | 20 | it( "can delete off of a query", function() { 21 | getInstance( "User" ).whereUsername( "johndoe" ).deleteAll(); 22 | expect( getInstance( "User" ).count() ).toBe( 4 ); 23 | expect( function() { 24 | getInstance( "User" ).whereUsername( "johndoe" ).firstOrFail(); 25 | } ).toThrow( type = "EntityNotFound" ); 26 | } ); 27 | 28 | it( "can delete multiple ids at once", function() { 29 | getInstance( "User" ).deleteAll( [ 2 ] ); 30 | expect( getInstance( "User" ).count() ).toBe( 4 ); 31 | expect( function() { 32 | getInstance( "User" ).where( "username", "johndoe" ).firstOrFail(); 33 | } ).toThrow( type = "EntityNotFound" ); 34 | } ); 35 | 36 | // https://github.com/coldbox-modules/quick/issues/124 37 | it( "can deleteAll off of a hasMany relationship", () => { 38 | var elpete = getInstance( "User" ).findOrFail( 1 ); 39 | elpete.posts().deleteAll(); 40 | elpete.purchases().deleteAll(); 41 | } ); 42 | } ); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Events/InstanceReadySpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function beforeAll() { 4 | super.beforeAll(); 5 | controller 6 | .getInterceptorService() 7 | .registerInterceptor( interceptorObject = this, interceptorName = "InstanceReadySpec" ); 8 | } 9 | 10 | function afterAll() { 11 | controller.getInterceptorService().unregister( "InstanceReadySpec" ); 12 | super.afterAll(); 13 | } 14 | 15 | function run() { 16 | describe( "instanceReady spec", function() { 17 | beforeEach( function() { 18 | variables.interceptData = {}; 19 | } ); 20 | 21 | it( "announces an instanceReady interception point", function() { 22 | var song = getInstance( "Song" ).findOrFail( 1 ); 23 | expect( variables ).toHaveKey( "quickInstanceReadyCalled" ); 24 | expect( variables.quickInstanceReadyCalled ).toBeStruct(); 25 | expect( variables.quickInstanceReadyCalled ).toHaveKey( "entity" ); 26 | structDelete( variables, "quickInstanceReadyCalled" ); 27 | } ); 28 | 29 | it( "calls any preLoad method on the component", function() { 30 | var song = getInstance( "Song" ).findOrFail( 1 ); 31 | expect( request ).toHaveKey( "instanceReadyCalled" ); 32 | expect( request.instanceReadyCalled ).toBeStruct(); 33 | expect( request.instanceReadyCalled ).toHaveKey( "entity" ); 34 | structDelete( request, "instanceReadyCalled" ); 35 | } ); 36 | } ); 37 | } 38 | 39 | function quickInstanceReady( 40 | event, 41 | interceptData, 42 | buffer, 43 | rc, 44 | prc 45 | ) { 46 | variables.quickInstanceReadyCalled = arguments.interceptData; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Events/PostDeleteSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function beforeAll() { 4 | super.beforeAll(); 5 | controller 6 | .getInterceptorService() 7 | .registerInterceptor( interceptorObject = this, interceptorName = "PostDeleteSpec" ); 8 | } 9 | 10 | function afterAll() { 11 | controller.getInterceptorService().unregister( "PostDeleteSpec" ); 12 | super.afterAll(); 13 | } 14 | 15 | function run() { 16 | describe( "postDelete spec", function() { 17 | it( "announces a quickPostDelete interception point", function() { 18 | structDelete( request, "quickPostDeleteCalled" ); 19 | var song = getInstance( "Song" ).findOrFail( 1 ); 20 | 21 | song.delete(); 22 | 23 | expect( request ).toHaveKey( "quickPostDeleteCalled" ); 24 | expect( request.quickPostDeleteCalled ).toBeArray(); 25 | expect( request.quickPostDeleteCalled ).toHaveLength( 1 ); 26 | expect( request.quickPostDeleteCalled[ 1 ] ).toBeStruct(); 27 | expect( request.quickPostDeleteCalled[ 1 ] ).toHaveKey( "entity" ); 28 | expect( request.quickPostDeleteCalled[ 1 ].entity.id ).toBe( 1 ); 29 | structDelete( request, "quickPostDeleteCalled" ); 30 | } ); 31 | 32 | it( "calls any postDelete method on the component", function() { 33 | structDelete( request, "postDeleteCalled" ); 34 | var song = getInstance( "Song" ).findOrFail( 1 ); 35 | 36 | song.delete(); 37 | 38 | expect( request ).toHaveKey( "postDeleteCalled" ); 39 | expect( request.postDeleteCalled ).toBeArray(); 40 | expect( request.postDeleteCalled ).toHaveLength( 1 ); 41 | expect( request.postDeleteCalled[ 1 ] ).toBeStruct(); 42 | expect( request.postDeleteCalled[ 1 ] ).toHaveKey( "entity" ); 43 | expect( request.postDeleteCalled[ 1 ].entity.id ).toBe( 1 ); 44 | structDelete( request, "postDeleteCalled" ); 45 | } ); 46 | } ); 47 | } 48 | 49 | function quickPostDelete( 50 | event, 51 | interceptData, 52 | buffer, 53 | rc, 54 | prc 55 | ) { 56 | param request.quickPostDeleteCalled = []; 57 | arrayAppend( request.quickPostDeleteCalled, { "entity" : arguments.interceptData.entity.getMemento() } ); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Events/PostInsertSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function beforeAll() { 4 | super.beforeAll(); 5 | controller 6 | .getInterceptorService() 7 | .registerInterceptor( interceptorObject = this, interceptorName = "PostInsertSpec" ); 8 | } 9 | 10 | function afterAll() { 11 | controller.getInterceptorService().unregister( "PostInsertSpec" ); 12 | super.afterAll(); 13 | } 14 | 15 | function run() { 16 | describe( "postInsert spec", function() { 17 | beforeEach( function() { 18 | variables.interceptData = {}; 19 | } ); 20 | 21 | it( "announces a quickPostInsert interception point", function() { 22 | var song = getInstance( "Song" ).create( { 23 | title : "Rainbow Connection", 24 | download_url : "https://open.spotify.com/track/1SJ4ycWow4yz6z4oFz8NAG" 25 | } ); 26 | expect( variables ).toHaveKey( "quickPostInsertCalled" ); 27 | expect( variables.quickPostInsertCalled ).toBeStruct(); 28 | expect( variables.quickPostInsertCalled ).toHaveKey( "entity" ); 29 | expect( variables.quickPostInsertCalled.entity.title ).toBe( "Rainbow Connection" ); 30 | expect( variables.quickPostInsertCalled.isLoaded ).toBeTrue(); 31 | structDelete( variables, "quickPostInsertCalled" ); 32 | } ); 33 | 34 | it( "calls any postInsert method on the component", function() { 35 | var song = getInstance( "Song" ).create( { 36 | title : "Rainbow Connection", 37 | download_url : "https://open.spotify.com/track/1SJ4ycWow4yz6z4oFz8NAG" 38 | } ); 39 | expect( request ).toHaveKey( "postInsertCalled" ); 40 | expect( request.postInsertCalled ).toBeStruct(); 41 | expect( request.postInsertCalled ).toHaveKey( "entity" ); 42 | expect( request.postInsertCalled.entity.title ).toBe( "Rainbow Connection" ); 43 | expect( request.postInsertCalled.isLoaded ).toBeTrue(); 44 | structDelete( request, "postInsertCalled" ); 45 | } ); 46 | } ); 47 | } 48 | 49 | function quickPostInsert( 50 | event, 51 | interceptData, 52 | buffer, 53 | rc, 54 | prc 55 | ) { 56 | variables.quickPostInsertCalled = { 57 | "entity" : arguments.interceptData.entity.getMemento(), 58 | "isLoaded" : arguments.interceptData.entity.isLoaded() 59 | }; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Events/PostLoadSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function beforeAll() { 4 | super.beforeAll(); 5 | controller 6 | .getInterceptorService() 7 | .registerInterceptor( interceptorObject = this, interceptorName = "PostLoadSpec" ); 8 | } 9 | 10 | function afterAll() { 11 | controller.getInterceptorService().unregister( "PostLoadSpec" ); 12 | super.afterAll(); 13 | } 14 | 15 | function run() { 16 | describe( "postLoad spec", function() { 17 | beforeEach( function() { 18 | variables.interceptData = {}; 19 | } ); 20 | 21 | it( "announces a postLoad interception point", function() { 22 | var song = getInstance( "Song" ).findOrFail( 1 ); 23 | expect( variables ).toHaveKey( "quickPostLoadCalled" ); 24 | expect( variables.quickPostLoadCalled ).toBeStruct(); 25 | expect( variables.quickPostLoadCalled ).toHaveKey( "entity" ); 26 | expect( variables.quickPostLoadCalled.entity.keyValues() ).toBe( [ 1 ] ); 27 | structDelete( variables, "quickPostLoadCalled" ); 28 | } ); 29 | 30 | it( "calls any preLoad method on the component", function() { 31 | var song = getInstance( "Song" ).findOrFail( 1 ); 32 | expect( request ).toHaveKey( "postLoadCalled" ); 33 | expect( request.postLoadCalled ).toBeStruct(); 34 | expect( request.postLoadCalled ).toHaveKey( "entity" ); 35 | expect( request.postLoadCalled.entity.keyValues() ).toBe( [ 1 ] ); 36 | structDelete( request, "postLoadCalled" ); 37 | } ); 38 | } ); 39 | } 40 | 41 | function quickPostLoad( 42 | event, 43 | interceptData, 44 | buffer, 45 | rc, 46 | prc 47 | ) { 48 | variables.quickPostLoadCalled = arguments.interceptData; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Events/PostUpdateSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function beforeAll() { 4 | super.beforeAll(); 5 | controller 6 | .getInterceptorService() 7 | .registerInterceptor( interceptorObject = this, interceptorName = "PostUpdateSpec" ); 8 | } 9 | 10 | function afterAll() { 11 | controller.getInterceptorService().unregister( "PostUpdateSpec" ); 12 | super.afterAll(); 13 | } 14 | 15 | function run() { 16 | describe( "postUpdate spec", function() { 17 | it( "announces a quickPostUpdate interception point", function() { 18 | structDelete( request, "quickPostUpdateCalled" ); 19 | var song = getInstance( "Song" ).findOrFail( 1 ); 20 | expect( song.getDownloadUrl() ).toBe( "https://open.spotify.com/track/4Nd5HJn4EExnLmHtClk4QV" ); 21 | 22 | song.update( { "downloadUrl" : "https://open.spotify.com/track/0GHGd3jYqChGNxzjqgRZSv" } ); 23 | 24 | expect( request ).toHaveKey( "quickPostUpdateCalled" ); 25 | expect( request.quickPostUpdateCalled ).toBeArray(); 26 | expect( request.quickPostUpdateCalled ).toHaveLength( 1 ); 27 | expect( request.quickPostUpdateCalled[ 1 ] ).toBeStruct(); 28 | expect( request.quickPostUpdateCalled[ 1 ] ).toHaveKey( "entity" ); 29 | expect( request.quickPostUpdateCalled[ 1 ].entity.downloadUrl ).toBe( 30 | "https://open.spotify.com/track/0GHGd3jYqChGNxzjqgRZSv" 31 | ); 32 | structDelete( request, "quickPostUpdateCalled" ); 33 | } ); 34 | 35 | it( "calls any postUpdate method on the component", function() { 36 | structDelete( request, "postUpdateCalled" ); 37 | var song = getInstance( "Song" ).findOrFail( 1 ); 38 | expect( song.getDownloadUrl() ).toBe( "https://open.spotify.com/track/4Nd5HJn4EExnLmHtClk4QV" ); 39 | 40 | song.update( { "downloadUrl" : "https://open.spotify.com/track/0GHGd3jYqChGNxzjqgRZSv" } ); 41 | 42 | expect( request ).toHaveKey( "postUpdateCalled" ); 43 | expect( request.postUpdateCalled ).toBeArray(); 44 | expect( request.postUpdateCalled ).toHaveLength( 1 ); 45 | expect( request.postUpdateCalled[ 1 ] ).toBeStruct(); 46 | expect( request.postUpdateCalled[ 1 ] ).toHaveKey( "entity" ); 47 | expect( request.postUpdateCalled[ 1 ].entity.downloadUrl ).toBe( 48 | "https://open.spotify.com/track/0GHGd3jYqChGNxzjqgRZSv" 49 | ); 50 | structDelete( request, "postUpdateCalled" ); 51 | } ); 52 | } ); 53 | } 54 | 55 | function quickPostUpdate( 56 | event, 57 | interceptData, 58 | buffer, 59 | rc, 60 | prc 61 | ) { 62 | param request.quickPostUpdateCalled = []; 63 | arrayAppend( request.quickPostUpdateCalled, { "entity" : arguments.interceptData.entity.getMemento() } ); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Events/PreDeleteSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function beforeAll() { 4 | super.beforeAll(); 5 | controller 6 | .getInterceptorService() 7 | .registerInterceptor( interceptorObject = this, interceptorName = "PreDeleteSpec" ); 8 | } 9 | 10 | function afterAll() { 11 | controller.getInterceptorService().unregister( "PreDeleteSpec" ); 12 | super.afterAll(); 13 | } 14 | 15 | function run() { 16 | describe( "preDelete spec", function() { 17 | it( "announces a quickPreDelete interception point", function() { 18 | structDelete( request, "quickPreDeleteCalled" ); 19 | var song = getInstance( "Song" ).findOrFail( 1 ); 20 | 21 | song.delete(); 22 | 23 | expect( request ).toHaveKey( "quickPreDeleteCalled" ); 24 | expect( request.quickPreDeleteCalled ).toBeArray(); 25 | expect( request.quickPreDeleteCalled ).toHaveLength( 1 ); 26 | expect( request.quickPreDeleteCalled[ 1 ] ).toBeStruct(); 27 | expect( request.quickPreDeleteCalled[ 1 ] ).toHaveKey( "entity" ); 28 | expect( request.quickPreDeleteCalled[ 1 ].entity.id ).toBe( 1 ); 29 | structDelete( request, "quickPreDeleteCalled" ); 30 | } ); 31 | 32 | it( "calls any preDelete method on the component", function() { 33 | structDelete( request, "preDeleteCalled" ); 34 | var song = getInstance( "Song" ).findOrFail( 1 ); 35 | 36 | song.delete(); 37 | 38 | expect( request ).toHaveKey( "preDeleteCalled" ); 39 | expect( request.preDeleteCalled ).toBeArray(); 40 | expect( request.preDeleteCalled ).toHaveLength( 1 ); 41 | expect( request.preDeleteCalled[ 1 ] ).toBeStruct(); 42 | expect( request.preDeleteCalled[ 1 ] ).toHaveKey( "entity" ); 43 | expect( request.preDeleteCalled[ 1 ].entity.id ).toBe( 1 ); 44 | structDelete( request, "preDeleteCalled" ); 45 | } ); 46 | } ); 47 | } 48 | 49 | function quickPreDelete( 50 | event, 51 | interceptData, 52 | buffer, 53 | rc, 54 | prc 55 | ) { 56 | param request.quickPreDeleteCalled = []; 57 | arrayAppend( request.quickPreDeleteCalled, { "entity" : arguments.interceptData.entity.getMemento() } ); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Events/PreInsertSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function beforeAll() { 4 | super.beforeAll(); 5 | controller 6 | .getInterceptorService() 7 | .registerInterceptor( interceptorObject = this, interceptorName = "PreInsertSpec" ); 8 | } 9 | 10 | function afterAll() { 11 | controller.getInterceptorService().unregister( "PreInsertSpec" ); 12 | super.afterAll(); 13 | } 14 | 15 | function run() { 16 | describe( "preInsert spec", function() { 17 | beforeEach( function() { 18 | variables.interceptData = {}; 19 | } ); 20 | 21 | it( "announces a quickPreInsert interception point", function() { 22 | var song = getInstance( "Song" ).create( { 23 | title : "Rainbow Connection", 24 | download_url : "https://open.spotify.com/track/1SJ4ycWow4yz6z4oFz8NAG" 25 | } ); 26 | expect( variables ).toHaveKey( "quickPreInsertCalled" ); 27 | expect( variables.quickPreInsertCalled ).toBeStruct(); 28 | expect( variables.quickPreInsertCalled ).toHaveKey( "entity" ); 29 | expect( variables.quickPreInsertCalled.entity.title ).toBe( "Rainbow Connection" ); 30 | expect( variables.quickPreInsertCalled.isLoaded ).toBeFalse(); 31 | structDelete( variables, "quickPreInsertCalled" ); 32 | } ); 33 | 34 | it( "calls any preInsert method on the component", function() { 35 | var song = getInstance( "Song" ).create( { 36 | title : "Rainbow Connection", 37 | download_url : "https://open.spotify.com/track/1SJ4ycWow4yz6z4oFz8NAG" 38 | } ); 39 | expect( request ).toHaveKey( "preInsertCalled" ); 40 | expect( request.preInsertCalled ).toBeStruct(); 41 | expect( request.preInsertCalled ).toHaveKey( "entity" ); 42 | expect( request.preInsertCalled.entity.title ).toBe( "Rainbow Connection" ); 43 | expect( request.preInsertCalled.isLoaded ).toBeFalse(); 44 | structDelete( request, "preInsertCalled" ); 45 | } ); 46 | 47 | it( "can influence the values being inserted", function() { 48 | var song = getInstance( "Song" ).create( { 49 | title : "Bohemian Rhapsody", 50 | download_url : "https://open.spotify.com/album/3BHe7LbW5yRjyqXNJ3A6mW" 51 | } ); 52 | expect( dateFormat( song.refresh().getCreatedDate(), "MM/dd/YYYY" ) ).toBe( "10/31/1975" ); 53 | } ); 54 | } ); 55 | } 56 | 57 | function quickPreInsert( 58 | event, 59 | interceptData, 60 | buffer, 61 | rc, 62 | prc 63 | ) { 64 | if ( arguments.interceptData.entity.getTitle() == "Bohemian Rhapsody" ) { 65 | arguments.interceptData.entity.assignAttribute( "createdDate", createDate( 1975, 10, 31 ) ); 66 | } 67 | 68 | variables.quickPreInsertCalled = { 69 | "entity" : arguments.interceptData.entity.getMemento(), 70 | "isLoaded" : arguments.interceptData.entity.isLoaded() 71 | }; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Events/PreLoadSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function beforeAll() { 4 | super.beforeAll(); 5 | controller 6 | .getInterceptorService() 7 | .registerInterceptor( interceptorObject = this, interceptorName = "PreLoadSpec" ); 8 | } 9 | 10 | function afterAll() { 11 | controller.getInterceptorService().unregister( "PreLoadSpec" ); 12 | super.afterAll(); 13 | } 14 | 15 | function run() { 16 | describe( "preLoad spec", function() { 17 | beforeEach( function() { 18 | variables.interceptData = {}; 19 | } ); 20 | 21 | it( "announces a preLoad interception point", function() { 22 | var song = getInstance( "Song" ).findOrFail( 1 ); 23 | expect( variables ).toHaveKey( "quickPreLoadCalled" ); 24 | expect( variables.quickPreLoadCalled ).toBeStruct(); 25 | expect( variables.quickPreLoadCalled ).toHaveKey( "id" ); 26 | expect( variables.quickPreLoadCalled.id ).toBe( [ 1 ] ); 27 | expect( variables.quickPreLoadCalled ).toHaveKey( "metadata" ); 28 | structDelete( variables, "quickPreLoadCalled" ); 29 | } ); 30 | 31 | it( "calls any preLoad method on the component", function() { 32 | var song = getInstance( "Song" ).findOrFail( 1 ); 33 | expect( request ).toHaveKey( "preLoadCalled" ); 34 | expect( request.preLoadCalled ).toBeStruct(); 35 | expect( request.preLoadCalled ).toHaveKey( "id" ); 36 | expect( request.preLoadCalled.id ).toBe( [ 1 ] ); 37 | expect( request.preLoadCalled ).toHaveKey( "metadata" ); 38 | structDelete( request, "preLoadCalled" ); 39 | } ); 40 | } ); 41 | } 42 | 43 | function quickPreLoad( 44 | event, 45 | interceptData, 46 | buffer, 47 | rc, 48 | prc 49 | ) { 50 | variables.quickPreLoadCalled = arguments.interceptData; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Events/PreUpdateSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function beforeAll() { 4 | super.beforeAll(); 5 | controller 6 | .getInterceptorService() 7 | .registerInterceptor( interceptorObject = this, interceptorName = "PreUpdateSpec" ); 8 | } 9 | 10 | function afterAll() { 11 | controller.getInterceptorService().unregister( "PreUpdateSpec" ); 12 | super.afterAll(); 13 | } 14 | 15 | function run() { 16 | describe( "preUpdate spec", function() { 17 | it( "announces a quickPreUpdate interception point", function() { 18 | structDelete( request, "quickPreUpdateCalled" ); 19 | var song = getInstance( "Song" ).findOrFail( 1 ); 20 | expect( song.getDownloadUrl() ).toBe( "https://open.spotify.com/track/4Nd5HJn4EExnLmHtClk4QV" ); 21 | 22 | song.update( { "downloadUrl" : "https://open.spotify.com/track/0GHGd3jYqChGNxzjqgRZSv" } ); 23 | 24 | expect( request ).toHaveKey( "quickPreUpdateCalled" ); 25 | expect( request.quickPreUpdateCalled ).toBeArray(); 26 | expect( request.quickPreUpdateCalled ).toHaveLength( 1 ); 27 | expect( request.quickPreUpdateCalled[ 1 ] ).toBeStruct(); 28 | expect( request.quickPreUpdateCalled[ 1 ] ).toHaveKey( "entity" ); 29 | expect( request.quickPreUpdateCalled[ 1 ].entity.downloadUrl ).toBe( 30 | "https://open.spotify.com/track/0GHGd3jYqChGNxzjqgRZSv" 31 | ); 32 | expect( request.quickPreUpdateCalled[ 1 ] ).toHaveKey( "originalAttributes" ); 33 | expect( request.quickPreUpdateCalled[ 1 ].originalAttributes.download_url ).toBe( "https://open.spotify.com/track/4Nd5HJn4EExnLmHtClk4QV" ); 34 | expect( request.quickPreUpdateCalled[ 1 ] ).toHaveKey( "newAttributes" ); 35 | expect( request.quickPreUpdateCalled[ 1 ].newAttributes.download_url ).toBe( "https://open.spotify.com/track/0GHGd3jYqChGNxzjqgRZSv" ); 36 | 37 | structDelete( request, "quickPreUpdateCalled" ); 38 | } ); 39 | 40 | it( "calls any preUpdate method on the component", function() { 41 | structDelete( request, "preUpdateCalled" ); 42 | var song = getInstance( "Song" ).findOrFail( 1 ); 43 | expect( song.getDownloadUrl() ).toBe( "https://open.spotify.com/track/4Nd5HJn4EExnLmHtClk4QV" ); 44 | 45 | song.update( { "downloadUrl" : "https://open.spotify.com/track/0GHGd3jYqChGNxzjqgRZSv" } ); 46 | 47 | expect( request ).toHaveKey( "preUpdateCalled" ); 48 | expect( request.preUpdateCalled ).toBeArray(); 49 | expect( request.preUpdateCalled ).toHaveLength( 1 ); 50 | expect( request.preUpdateCalled[ 1 ] ).toBeStruct(); 51 | expect( request.preUpdateCalled[ 1 ] ).toHaveKey( "entity" ); 52 | expect( request.preUpdateCalled[ 1 ].entity.downloadUrl ).toBe( 53 | "https://open.spotify.com/track/0GHGd3jYqChGNxzjqgRZSv" 54 | ); 55 | structDelete( request, "preUpdateCalled" ); 56 | } ); 57 | } ); 58 | } 59 | 60 | function quickPreUpdate( 61 | event, 62 | interceptData, 63 | buffer, 64 | rc, 65 | prc 66 | ) { 67 | param request.quickPreUpdateCalled = []; 68 | arrayAppend( 69 | request.quickPreUpdateCalled, 70 | { 71 | "entity" : arguments.interceptData.entity.getMemento(), 72 | "originalAttributes" : arguments.interceptData.originalAttributes, 73 | "newAttributes" : arguments.interceptData.newAttributes 74 | } 75 | ); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/FillSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Fill Spec", function() { 5 | it( "can fill many properties at once", function() { 6 | var user = getInstance( "User" ); 7 | expect( user.retrieveAttribute( "username" ) ).toBe( "" ); 8 | expect( user.retrieveAttribute( "first_name" ) ).toBe( "" ); 9 | expect( user.retrieveAttribute( "last_name" ) ).toBe( "" ); 10 | user.fill( { 11 | "username" : "JaneDoe", 12 | "first_name" : "Jane", 13 | "last_name" : "Doe" 14 | } ); 15 | expect( user.retrieveAttribute( "username" ) ).toBe( "JaneDoe" ); 16 | expect( user.retrieveAttribute( "first_name" ) ).toBe( "Jane" ); 17 | expect( user.retrieveAttribute( "last_name" ) ).toBe( "Doe" ); 18 | } ); 19 | 20 | it( "throws an error when trying to fill non-existant properties", function() { 21 | var user = getInstance( "User" ); 22 | expect( function() { 23 | user.fill( { "non-existant-property" : "any-value" } ); 24 | } ).toThrow( type = "AttributeNotFound" ); 25 | } ); 26 | 27 | it( "can ignore non-existant properties", function() { 28 | var user = getInstance( "UserFill" ); 29 | expect( user.retrieveAttribute( "firstName" ) ).toBe( "" ); 30 | expect( user.retrieveAttribute( "lastName" ) ).toBe( "" ); 31 | expect( user.retrieveAttribute( "email" ) ).toBe( "" ); 32 | 33 | var rc = { 34 | "DOMAIN" : "localtest.me", 35 | "SUBDOMAIN" : "", 36 | "aboutMe" : "I hate this old house...", 37 | "confirmPassword" : "123", 38 | "create" : "", 39 | "csrf" : "29DFD33D352C5CA0BF18CECF00B83D3E201CEBCB", 40 | "email" : "bob@vila.com", 41 | "event" : "registrations.create", 42 | "fieldnames" : "firstName,lastName,email,password,confirmPassword,aboutMe,terms,csrf", 43 | "firstName" : "Bob", 44 | "lastName" : "Vila", 45 | "last_url" : "registrations/new/", 46 | "password" : "123", 47 | "terms" : "on" 48 | }; 49 | user.fill( attributes = rc, ignoreNonExistentAttributes = "true" ); 50 | expect( user.retrieveAttribute( "firstName" ) ).toBe( "Bob" ); 51 | expect( user.retrieveAttribute( "lastName" ) ).toBe( "Vila" ); 52 | expect( user.retrieveAttribute( "email" ) ).toBe( "bob@vila.com" ); 53 | } ); 54 | } ); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/GUIDPrimaryKeySpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( 5 | title = "GUID Primary Key Spec", 6 | body = function() { 7 | it( "sets the primary key with a guid before saving", function() { 8 | var country = getInstance( "Actor" ).create( { "name" : "Tina Fey" } ); 9 | 10 | expect( country.getId() ).notToBeNumeric(); 11 | } ); 12 | }, 13 | skip = !server.keyExists( "lucee" ) && !server.keyExists( "boxlang" ) 14 | ); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/HydrateSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Hydrate Spec", function() { 5 | it( "can hydrate an entity from a struct of data", function() { 6 | var memento = { 7 | "id" : 4, 8 | "username" : "elpete2", 9 | "firstName" : "Another", 10 | "lastName" : "Peterson" 11 | }; 12 | var user = getInstance( "User" ); 13 | expect( user.retrieveAttribute( "username" ) ).toBe( "" ); 14 | expect( user.retrieveAttribute( "first_name" ) ).toBe( "" ); 15 | expect( user.retrieveAttribute( "last_name" ) ).toBe( "" ); 16 | user.hydrate( memento ); 17 | expect( user.isLoaded() ).toBeTrue( "An entity should be loaded after calling hydrate" ); 18 | expect( user.retrieveAttribute( "username" ) ).toBe( "elpete2" ); 19 | expect( user.retrieveAttribute( "first_name" ) ).toBe( "Another" ); 20 | expect( user.retrieveAttribute( "last_name" ) ).toBe( "Peterson" ); 21 | } ); 22 | 23 | it( "can hydrate multiple entities at once from an array of structs", function() { 24 | var mementos = [ 25 | { 26 | "id" : 2, 27 | "username" : "johndoe", 28 | "firstName" : "John", 29 | "lastName" : "Doe" 30 | }, 31 | { 32 | "id" : 4, 33 | "username" : "elpete2", 34 | "firstName" : "Another", 35 | "lastName" : "Peterson" 36 | } 37 | ]; 38 | 39 | var users = getInstance( "User" ).hydrateAll( mementos ); 40 | 41 | var userA = users[ 1 ]; 42 | expect( userA.isLoaded() ).toBeTrue( "An entity should be loaded after calling hydrate" ); 43 | expect( userA.retrieveAttribute( "username" ) ).toBe( "johndoe" ); 44 | expect( userA.retrieveAttribute( "first_name" ) ).toBe( "John" ); 45 | expect( userA.retrieveAttribute( "last_name" ) ).toBe( "Doe" ); 46 | 47 | var userB = users[ 2 ]; 48 | expect( userB.isLoaded() ).toBeTrue( "An entity should be loaded after calling hydrate" ); 49 | expect( userB.retrieveAttribute( "username" ) ).toBe( "elpete2" ); 50 | expect( userB.retrieveAttribute( "first_name" ) ).toBe( "Another" ); 51 | expect( userB.retrieveAttribute( "last_name" ) ).toBe( "Peterson" ); 52 | } ); 53 | } ); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/IsDirtySpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "isDirty Spec", function() { 5 | it( "can test to see if an updated entity differs from that created", function() { 6 | var user = getInstance( "User" ).find( 1 ); 7 | 8 | user.fill( { "last_name" : "Peterson" } ); 9 | expect( user.isDirty() ).toBeFalse(); 10 | 11 | user.fill( { "last_name" : "peterson" } ); 12 | expect( user.isDirty() ).toBeTrue(); 13 | 14 | user.fill( { "last_name" : "Smith" } ); 15 | expect( user.isDirty() ).toBeTrue(); 16 | 17 | user.fill( { "last_name" : "Peterson" } ); 18 | expect( user.isDirty() ).toBeFalse(); 19 | } ); 20 | } ); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/NullValuesSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Null Values Spec", function() { 5 | it( "returns null values as a string by default", function() { 6 | var user = getInstance( "User" ).findOrFail( 3 ); 7 | expect( user.getCountryId() ).toBe( "" ); 8 | expect( user.getMemento().countryId ).toBe( "" ); 9 | } ); 10 | 11 | it( "saves a column containing an empty string as null in the database by default", function() { 12 | var user = getInstance( "User" ).where( "username", "elpete" ).firstOrFail(); 13 | user.setCountryId( "" ); 14 | user.save(); 15 | expect( 16 | getInstance( "User" ) 17 | .whereId( 1 ) 18 | .whereNull( "country_id" ) 19 | .count() 20 | ).toBe( 1 ); 21 | expect( 22 | getInstance( "User" ) 23 | .whereId( 1 ) 24 | .whereNotNull( "country_id" ) 25 | .count() 26 | ).toBe( 0 ); 27 | } ); 28 | 29 | it( "can set a column to not convert empty strings to null", function() { 30 | expect( getInstance( "PhoneNumber" ).count() ).toBe( 3 ); 31 | getInstance( "PhoneNumber" ) 32 | .setNumber( "" ) 33 | .setActive( false ) 34 | .save(); 35 | expect( getInstance( "PhoneNumber" ).whereNull( "number" ).count() ).toBe( 0 ); 36 | expect( getInstance( "PhoneNumber" ).whereNotNull( "number" ).count() ).toBe( 4 ); 37 | } ); 38 | 39 | it( "can choose a custom value to convert to nulls in the database", function() { 40 | expect( getInstance( "Song" ).whereNull( "title" ).count() ).toBe( 0 ); 41 | 42 | getInstance( "Song" ) 43 | .fill( { 44 | title : "", 45 | downloadUrl : "https://example.com/songs/1" 46 | } ) 47 | .save(); 48 | expect( getInstance( "Song" ).whereNull( "title" ).count() ).toBe( 0 ); 49 | 50 | getInstance( "Song" ) 51 | .fill( { 52 | title : "Really_Null", 53 | downloadUrl : "https://example.com/songs/1" 54 | } ) 55 | .save(); 56 | expect( getInstance( "Song" ).whereNull( "title" ).count() ).toBe( 0 ); 57 | 58 | getInstance( "Song" ) 59 | .fill( { 60 | title : "REALLY_NULL", 61 | downloadUrl : "https://example.com/songs/1" 62 | } ) 63 | .save(); 64 | expect( getInstance( "Song" ).whereNull( "title" ).count() ).toBe( 1 ); 65 | } ); 66 | 67 | // https://github.com/coldbox-modules/quick/issues/157 68 | it( "can check for null values on newly created entities", () => { 69 | var newUser = getInstance( "User" ).create( { 70 | "username" : "newuser", 71 | "firstName" : "New", 72 | "lastName" : "User" 73 | } ); 74 | expect( newUser.isNullAttribute( "email" ) ).toBeTrue(); 75 | expect( newUser.isNullValue( "email", "" ) ).toBeTrue(); 76 | } ); 77 | } ); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/QuerySpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Query Spec", function() { 5 | it( "returns all records as array", function() { 6 | var users = getInstance( "User" ).all(); 7 | expect( users ).toHaveLength( 5, "Five users should exist in the database and be returned." ); 8 | expect( users[ 1 ].getId() ).toBe( 1 ); 9 | expect( users[ 1 ].getUsername() ).toBe( "elpete" ); 10 | expect( users[ 2 ].getId() ).toBe( 2 ); 11 | expect( users[ 2 ].getUsername() ).toBe( "johndoe" ); 12 | expect( users[ 3 ].getId() ).toBe( 3 ); 13 | expect( users[ 3 ].getUsername() ).toBe( "janedoe" ); 14 | expect( users[ 4 ].getId() ).toBe( 4 ); 15 | expect( users[ 4 ].getUsername() ).toBe( "elpete2" ); 16 | expect( users[ 5 ].getId() ).toBe( 5 ); 17 | expect( users[ 5 ].getUsername() ).toBe( "michaelscott" ); 18 | } ); 19 | 20 | it( "can execute an arbitrary get query", function() { 21 | var users = getInstance( "User" ).where( "username", "elpete" ).get(); 22 | expect( users ).toHaveLength( 1, "One user should be returned." ); 23 | expect( users[ 1 ].getId() ).toBe( 1 ); 24 | expect( users[ 1 ].getUsername() ).toBe( "elpete" ); 25 | } ); 26 | 27 | it( "can execute an arbitrary first query", function() { 28 | var user = getInstance( "User" ).where( "username", "elpete" ).first(); 29 | expect( user.getId() ).toBe( 1 ); 30 | expect( user.getUsername() ).toBe( "elpete" ); 31 | } ); 32 | 33 | it( "can use a QuickBuilder instance anywhere a QueryBuilder instance is accepted", function() { 34 | var users = getInstance( "User" ) 35 | .whereIn( "username", getInstance( "User" ).select( "username" ).where( "type", "admin" ) ) 36 | .get(); 37 | 38 | expect( users ).toHaveLength( 2, "Two users should be returned." ); 39 | expect( users[ 1 ].getId() ).toBe( 1 ); 40 | expect( users[ 1 ].getUsername() ).toBe( "elpete" ); 41 | expect( users[ 2 ].getId() ).toBe( 4 ); 42 | expect( users[ 2 ].getUsername() ).toBe( "elpete2" ); 43 | } ); 44 | } ); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/ReadOnlyEntitySpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Read Only models", function() { 5 | it( "prevents saves from being performed on new instances", function() { 6 | var referral = getInstance( "Referral" ); 7 | referral.setType( "internal" ); 8 | expect( function() { 9 | referral.save(); 10 | } ).toThrow( type = "QuickReadOnlyException" ); 11 | } ); 12 | 13 | it( "prevents create from being performed on new instances", function() { 14 | expect( function() { 15 | getInstance( "Referral" ).create( { type : "internal" } ); 16 | } ).toThrow( type = "QuickReadOnlyException" ); 17 | } ); 18 | 19 | it( "prevents save from being performed on existing instances", function() { 20 | var referral = getInstance( "Referral" ).findOrFail( 1 ); 21 | referral.setType( "external" ); 22 | expect( function() { 23 | referral.save(); 24 | } ).toThrow( type = "QuickReadOnlyException" ); 25 | } ); 26 | 27 | it( "prevents updates from being performed on existing instances", function() { 28 | var referral = getInstance( "Referral" ).findOrFail( 1 ); 29 | expect( function() { 30 | referral.update( { type : "external" } ); 31 | } ).toThrow( type = "QuickReadOnlyException" ); 32 | } ); 33 | 34 | it( "prevents mass updates from being performed on existing instances", function() { 35 | expect( function() { 36 | getInstance( "Referral" ).updateAll( { type : "external" } ); 37 | } ).toThrow( type = "QuickReadOnlyException" ); 38 | } ); 39 | 40 | it( "prevents deletes from being performed on existing instances", function() { 41 | var referral = getInstance( "Referral" ).findOrFail( 1 ); 42 | expect( function() { 43 | referral.delete(); 44 | } ).toThrow( type = "QuickReadOnlyException" ); 45 | } ); 46 | 47 | it( "prevents mass deletes from being performed on existing instances", function() { 48 | expect( function() { 49 | getInstance( "Referral" ).deleteAll(); 50 | } ).toThrow( type = "QuickReadOnlyException" ); 51 | } ); 52 | } ); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/ReadOnlyPropertySpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Read-only properties", function() { 5 | it( "prevents read-only properties from being saved", function() { 6 | var link = getInstance( "Link" ).findOrFail( 1 ); 7 | expect( link.getUrl() ).toBe( "http://example.com/some-link" ); 8 | expect( dateTimeFormat( link.getCreatedDate(), "YYYY-MM-dd HH:nn:ss" ) ).toBe( "2017-07-28 02:07:00" ); 9 | 10 | link.setUrl( "https://example.com/" ) 11 | .setCreatedDate( now() ) 12 | .save(); 13 | 14 | link.refresh(); 15 | 16 | expect( link.getUrl() ).toBe( "https://example.com/" ); 17 | expect( dateTimeFormat( link.getCreatedDate(), "YYYY-MM-dd HH:nn:ss" ) ).toBe( "2017-07-28 02:07:00" ); 18 | } ); 19 | 20 | it( "prevents create from setting read-only properties", function() { 21 | expect( function() { 22 | getInstance( "Link" ).create( { createdDate : now() } ); 23 | } ).toThrow( type = "QuickReadOnlyException" ); 24 | } ); 25 | 26 | it( "prevents assignAttribute from being called on a read-only property", function() { 27 | var link = getInstance( "Link" ).findOrFail( 1 ); 28 | expect( function() { 29 | link.assignAttribute( "createdDate", now() ); 30 | } ).toThrow( type = "QuickReadOnlyException" ); 31 | } ); 32 | 33 | it( "prevents fill from being called containing a read-only property", function() { 34 | var link = getInstance( "Link" ).findOrFail( 1 ); 35 | expect( function() { 36 | link.fill( { createdDate : now() } ); 37 | } ).toThrow( type = "QuickReadOnlyException" ); 38 | } ); 39 | 40 | it( "prevents updates from being performed on a read-only property", function() { 41 | var link = getInstance( "Link" ).findOrFail( 1 ); 42 | expect( function() { 43 | link.update( { createdDate : now() } ); 44 | } ).toThrow( type = "QuickReadOnlyException" ); 45 | } ); 46 | 47 | it( "prevents mass updates from being performed on read-only properties", function() { 48 | expect( function() { 49 | getInstance( "Link" ).updateAll( { createdDate : now() } ); 50 | } ).toThrow( type = "QuickReadOnlyException" ); 51 | } ); 52 | } ); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Relationships/BelongsToManySpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Belongs To Many Spec", function() { 5 | beforeEach( function() { 6 | variables.queries = []; 7 | } ); 8 | 9 | it( "can get the related entities", function() { 10 | var post = getInstance( "Post" ).find( 1245 ); 11 | var tags = post.getTags(); 12 | expect( tags ).toBeArray(); 13 | expect( tags ).toHaveLength( 2 ); 14 | } ); 15 | 16 | it( "can get the related entities from the inverse relationship", function() { 17 | var tag = getInstance( "Tag" ).find( 1 ); 18 | var posts = tag.getPosts(); 19 | expect( posts ).toBeArray(); 20 | expect( posts ).toHaveLength( 2 ); 21 | } ); 22 | } ); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Relationships/BelongsToThroughSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Belongs To Through Spec", function() { 5 | it( "can get the owning entity through other relationships", function() { 6 | var post = getInstance( "Post" ).findOrFail( 523526 ); 7 | expect( post.getCountry() ).notToBeNull(); 8 | expect( post.getCountry() ).notToBeArray(); 9 | expect( post.getCountry().getId() ).toBe( "02B84D66-0AA0-F7FB-1F71AFC954843861" ); 10 | } ); 11 | } ); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Relationships/HasOneSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Has One Spec", function() { 5 | it( "can get the owning entity", function() { 6 | var user = getInstance( "User" ).find( 1 ); 7 | var post = user.getLatestPost(); 8 | expect( post.getPost_Pk() ).toBe( 523526 ); 9 | } ); 10 | 11 | it( "returns null if there is no owning entity", function() { 12 | var user = getInstance( "User" ).find( 2 ); 13 | expect( user.getLatestPost() ).toBeNull(); 14 | } ); 15 | 16 | it( "returns null if the foreign key has no value", function() { 17 | var hasNoFavoritePost = getInstance( "User" ).find( 2 ); 18 | expect( hasNoFavoritePost.getFavoritePost() ).toBeNull(); 19 | } ); 20 | 21 | it( "can return an empty default entity if there is no owning entity", function() { 22 | var user = getInstance( "User" ).find( 2 ); 23 | expect( user.getLatestPostWithEmptyDefault() ).notToBeNull(); 24 | var post = user.getLatestPostWithEmptyDefault(); 25 | expect( post ).toBeInstanceOf( "Post" ); 26 | expect( post.isLoaded() ).toBeFalse( "A default model is not loaded" ); 27 | expect( post.retrieveAttributesData() ).toBeEmpty(); 28 | } ); 29 | 30 | it( "can return a configured default entity if there is no owning entity", function() { 31 | var user = getInstance( "User" ).find( 2 ); 32 | expect( user.getLatestPostWithDefaultAttributes() ).notToBeNull(); 33 | var post = user.getLatestPostWithDefaultAttributes(); 34 | expect( post ).toBeInstanceOf( "Post" ); 35 | expect( post.isLoaded() ).toBeFalse( "A default model is not loaded" ); 36 | expect( post.retrieveAttributesData( aliased = true ) ).toBe( { "body" : "Default Post" } ); 37 | } ); 38 | 39 | it( "can configure a default entity with a callback", function() { 40 | var user = getInstance( "User" ).find( 2 ); 41 | expect( user.getLatestPostWithCallbackConfiguredDefault() ).notToBeNull(); 42 | var post = user.getLatestPostWithCallbackConfiguredDefault(); 43 | expect( post ).toBeInstanceOf( "Post" ); 44 | expect( post.isLoaded() ).toBeFalse( "A default model is not loaded" ); 45 | expect( post.getBody() ).toBe( user.getUsername() ); 46 | } ); 47 | } ); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Relationships/HasOneThroughSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Has One Through Spec", function() { 5 | it( "can get the related entity through another entity", function() { 6 | var country = getInstance( "Country@something" ).find( "02B84D66-0AA0-F7FB-1F71AFC954843861" ); 7 | expect( country.getLatestPost() ).notToBeNull(); 8 | expect( country.getLatestPost() ).notToBeArray(); 9 | expect( country.getLatestPost().getPost_Pk() ).toBe( 523526 ); 10 | expect( country.getLatestPost().getBody() ).toBe( "My second awesome post body" ); 11 | } ); 12 | } ); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Relationships/OrderingByRelationshipsSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Ordering By Relationships Spec", function() { 5 | it( "can order by a belongs to relationship", function() { 6 | var posts = getInstance( "Post" ) 7 | .has( "author" ) 8 | .orderBy( "author.firstName" ) 9 | .orderBy( "post_pk" ) 10 | .get(); 11 | 12 | expect( posts ).toBeArray(); 13 | expect( posts ).toHaveLength( 3 ); 14 | expect( posts[ 1 ].keyValues() ).toBe( [ 321 ] ); 15 | expect( posts[ 2 ].keyValues() ).toBe( [ 1245 ] ); 16 | expect( posts[ 3 ].keyValues() ).toBe( [ 523526 ] ); 17 | } ); 18 | 19 | it( "can order by a belongs to relationship descending", function() { 20 | var posts = getInstance( "Post" ) 21 | .has( "author" ) 22 | .orderBy( "author.firstName", "desc" ) 23 | .orderBy( "post_pk" ) 24 | .get(); 25 | 26 | expect( posts ).toBeArray(); 27 | expect( posts ).toHaveLength( 3 ); 28 | expect( posts[ 1 ].keyValues() ).toBe( [ 1245 ] ); 29 | expect( posts[ 2 ].keyValues() ).toBe( [ 523526 ] ); 30 | expect( posts[ 3 ].keyValues() ).toBe( [ 321 ] ); 31 | } ); 32 | 33 | it( "can order by a nested belongs to relationship descending", function() { 34 | var postsQuery = getInstance( "Post" ) 35 | .has( "author.country" ) 36 | .orderBy( "author.country.name" ) 37 | .orderBy( "post_pk" ); 38 | 39 | var posts = postsQuery.get(); 40 | 41 | expect( posts ).toBeArray(); 42 | expect( posts ).toHaveLength( 3 ); 43 | expect( posts[ 1 ].keyValues() ).toBe( [ 321 ] ); 44 | expect( posts[ 2 ].keyValues() ).toBe( [ 1245 ] ); 45 | expect( posts[ 3 ].keyValues() ).toBe( [ 523526 ] ); 46 | } ); 47 | } ); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Relationships/PolymorphicBelongsToSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Polymorphic Belongs To Spec", function() { 5 | it( "can get the related polymorphic entity", function() { 6 | var commentA = getInstance( "Comment" ).find( 1 ); 7 | expect( commentA.getCommentable() ).toBeInstanceOf( "app.models.Post" ); 8 | expect( commentA.getCommentable().getBody() ).toBe( "My awesome post body" ); 9 | 10 | var commentB = getInstance( "Comment" ).find( 2 ); 11 | expect( commentB.getCommentable() ).toBeInstanceOf( "app.models.Post" ); 12 | expect( commentB.getCommentable().getBody() ).toBe( "My post with a different author" ); 13 | 14 | var commentC = getInstance( "Comment" ).find( 3 ); 15 | expect( commentC.getCommentable() ).toBeInstanceOf( "app.models.Video" ); 16 | expect( commentC.getCommentable().getTitle() ).toBe( "Cello Wars" ); 17 | } ); 18 | } ); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Relationships/PolymorphicHasManySpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Polymorphic Has Many Spec", function() { 5 | it( "can get the related polymorphic entities", function() { 6 | // delete our internal comments to allow the test to pass: 7 | getInstance( "InternalComment" ) 8 | .get() 9 | .each( function( comment ) { 10 | comment.delete(); 11 | } ); 12 | variables.queries = []; 13 | 14 | var postA = getInstance( "Post" ).find( 1245 ); 15 | var postAComments = postA.getComments(); 16 | expect( postAComments ).toBeArray(); 17 | expect( postAComments ).toHaveLength( 1 ); 18 | 19 | var postB = getInstance( "Post" ).find( 523526 ); 20 | var postBComments = postB.getComments(); 21 | expect( postBComments ).toBeArray(); 22 | expect( postBComments ).toBeEmpty(); 23 | 24 | var postC = getInstance( "Post" ).find( 321 ); 25 | var postCComments = postC.getComments(); 26 | expect( postCComments ).toBeArray(); 27 | expect( postCComments ).toHaveLength( 1 ); 28 | 29 | var videoA = getInstance( "Video" ).find( 1 ); 30 | var videoAComments = videoA.getComments(); 31 | expect( videoAComments ).toBeArray(); 32 | expect( videoAComments ).toBeEmpty(); 33 | 34 | var videoB = getInstance( "Video" ).find( 1245 ); 35 | var videoBComments = videoB.getComments(); 36 | expect( videoBComments ).toBeArray(); 37 | expect( videoBComments ).toHaveLength( 1 ); 38 | expect( videoBComments[ 1 ].getBody() ).toBe( "What a great video! So fun!" ); 39 | } ); 40 | } ); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/Relationships/WithDefaultSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "WithDefault Spec", function() { 5 | it( "will throw an exception when retrieving a relation on an unloaded entity", function() { 6 | var post = getInstance( "Post" ); 7 | 8 | expect( function() { 9 | post.getAuthor(); 10 | } ).toThrow( message = "Retrieving an unloaded entity should throw an exception" ); 11 | } ); 12 | 13 | it( "can load a entity and return a default entity if there is no owning entity", function() { 14 | var post = getInstance( "Post" ).find( 7777 ); 15 | var author = post.getAuthorWithEmptyDefault(); 16 | expect( post.getAuthorWithEmptyDefault() ).notToBeNull(); 17 | expect( author ).toBeInstanceOf( "User" ); 18 | expect( author.isLoaded() ).toBeFalse( "A default model is not loaded" ); 19 | expect( author.retrieveAttributesData() ).toBeEmpty(); 20 | } ); 21 | 22 | it( "can save a new entity and return a default entity if there is no owning entity", function() { 23 | var post = getInstance( "Post" ).create( { "body" : "This is a cool body post" }, true ); 24 | 25 | var author = post.getAuthorWithEmptyDefault(); 26 | expect( post.getAuthorWithEmptyDefault() ).notToBeNull(); 27 | expect( author ).toBeInstanceOf( "User" ); 28 | expect( author.isLoaded() ).toBeFalse( "A default model is not loaded" ); 29 | expect( author.retrieveAttributesData() ).toBeEmpty(); 30 | } ); 31 | } ); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/UUIDPrimaryKeySpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "UUID Primary Key Spec", function() { 5 | it( "sets the primary key with a uuid before saving", function() { 6 | var country = getInstance( "Country" ).create( { "name" : "Wakanda" } ); 7 | 8 | expect( country.getId() ).notToBeNumeric(); 9 | } ); 10 | } ); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/UpdateAllSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Mass Create Spec", function() { 5 | it( "can mass update all entities that fit the query criteria", function() { 6 | var postA = getInstance( "Post" ).find( 1245 ); 7 | var postB = getInstance( "Post" ).find( 523526 ); 8 | 9 | expect( postA.getBody() ).notToBe( "The new body" ); 10 | expect( postB.getBody() ).notToBe( "The new body" ); 11 | 12 | getInstance( "Post" ).updateAll( { "body" : "The new body" } ); 13 | 14 | postA.refresh(); 15 | postB.refresh(); 16 | 17 | expect( postA.getBody() ).toBe( "The new body" ); 18 | expect( postB.getBody() ).toBe( "The new body" ); 19 | } ); 20 | } ); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseEntity/UpdateSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Update Spec", function() { 5 | it( "update a model directly (without calling save)", function() { 6 | var user = getInstance( "User" ).find( 2 ); 7 | expect( user.getUsername() ).toBe( "johndoe" ); 8 | expect( user.getFirstName() ).toBe( "John" ); 9 | expect( user.getLastName() ).toBe( "Doe" ); 10 | user.update( { 11 | "username" : "janedoe", 12 | "first_name" : "Jane" 13 | } ); 14 | user.refresh(); 15 | expect( user.getUsername() ).toBe( "janedoe" ); 16 | expect( user.getFirstName() ).toBe( "Jane" ); 17 | expect( user.getLastName() ).toBe( "Doe" ); 18 | } ); 19 | 20 | it( "can ignore non-existant properties", function() { 21 | var user = getInstance( "User" ).find( 2 ); 22 | expect( user.getUsername() ).toBe( "johndoe" ); 23 | expect( user.getFirstName() ).toBe( "John" ); 24 | expect( user.getLastName() ).toBe( "Doe" ); 25 | user.update( 26 | { 27 | "username" : "janedoe", 28 | "first_name" : "Jane", 29 | "non-existant-property" : "any-value" 30 | }, 31 | true 32 | ); 33 | user.refresh(); 34 | expect( user.getUsername() ).toBe( "janedoe" ); 35 | expect( user.getFirstName() ).toBe( "Jane" ); 36 | expect( user.getLastName() ).toBe( "Doe" ); 37 | } ); 38 | 39 | describe( "updateOrCreate", function() { 40 | it( "updates an existing entity", function() { 41 | var user = getInstance( "User" ).updateOrCreate( 42 | { "username" : "elpete" }, 43 | { "firstName" : "changed" } 44 | ); 45 | expect( user.getId() ).toBe( 1 ); 46 | expect( user.getUsername() ).toBe( "elpete" ); 47 | expect( user.getFirstName() ).toBe( "changed" ); 48 | } ); 49 | 50 | it( "creates a new entity with both attribute structs filled", function() { 51 | var user = getInstance( "User" ).updateOrCreate( 52 | { "username" : "doesntexist" }, 53 | { 54 | "firstName" : "doesnt", 55 | "lastName" : "exist", 56 | "password" : "secret" 57 | } 58 | ); 59 | expect( user.isLoaded() ).toBeTrue(); 60 | expect( user.getUsername() ).toBe( "doesntexist" ); 61 | expect( user.getFirstName() ).toBe( "doesnt" ); 62 | expect( user.getLastName() ).toBe( "exist" ); 63 | expect( user.getPassword() ).toBe( "secret" ); 64 | } ); 65 | } ); 66 | } ); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /tests/specs/integration/BaseServiceSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "BaseService Spec", function() { 5 | describe( "instantiation", function() { 6 | it( "can be instantiated with an entity", function() { 7 | var user = getInstance( "User" ); 8 | var service = getInstance( name = "BaseService@quick", initArguments = { entity : user } ); 9 | expect( service.entityName() ).toBe( "User" ); 10 | } ); 11 | 12 | it( "can be instantiated with a wirebox mapping", function() { 13 | var service = getInstance( name = "BaseService@quick", initArguments = { entity : "User" } ); 14 | expect( service.entityName() ).toBe( "User" ); 15 | } ); 16 | 17 | it( "can inject a service using the wirebox dsl", function() { 18 | var service = getInstance( dsl = "quickService:User" ); 19 | expect( service.entityName() ).toBe( "User" ); 20 | } ); 21 | } ); 22 | 23 | describe( "retriving records", function() { 24 | beforeEach( function() { 25 | variables.service = getInstance( dsl = "quickService:User" ); 26 | } ); 27 | 28 | afterEach( function() { 29 | structDelete( variables, "service" ); 30 | } ); 31 | 32 | it( "can find a specific record", function() { 33 | var user = variables.service.find( 1 ); 34 | expect( user.keyValues() ).toBe( [ 1 ] ); 35 | } ); 36 | 37 | it( "can find or fail a specific record", function() { 38 | var user = variables.service.findOrFail( 1 ); 39 | expect( user.keyValues() ).toBe( [ 1 ] ); 40 | } ); 41 | 42 | it( "can handle any qb methods", function() { 43 | var users = variables.service.where( "last_name", "Doe" ).get(); 44 | expect( users ).toBeArray(); 45 | expect( users ).toHaveLength( 2 ); 46 | } ); 47 | } ); 48 | } ); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /tests/specs/integration/ModuleCanBeActivedSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "Module Activation", function() { 5 | it( "can activate the module", function() { 6 | expect( getController().getModuleService().isModuleRegistered( "quick" ) ).toBeTrue( 7 | "The quick module has not been registered" 8 | ); 9 | } ); 10 | } ); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/specs/integration/QuickCollectionSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function beforeAll() { 4 | super.beforeAll(); 5 | controller 6 | .getInterceptorService() 7 | .registerInterceptor( interceptorObject = this, interceptorName = "QuickCollectionSpec" ); 8 | } 9 | 10 | function afterAll() { 11 | controller.getInterceptorService().unregister( "QuickCollectionSpec" ); 12 | super.afterAll(); 13 | } 14 | 15 | function run() { 16 | describe( "Quick Collection Spec", function() { 17 | beforeEach( function() { 18 | variables.queries = []; 19 | } ); 20 | 21 | it( 22 | title = "can load a relationship lazily", 23 | body = function() { 24 | var posts = getInstance( "CollectionPost" ).all(); 25 | expect( variables.queries ).toHaveLength( 1 ); 26 | expectAll( posts.get() ).toSatisfy( function( post ) { 27 | return !post.isRelationshipLoaded( "author" ); 28 | }, "The relationship should not be loaded." ); 29 | posts.load( "author" ); 30 | expect( variables.queries ).toHaveLength( 2 ); 31 | expectAll( posts.get() ).toSatisfy( function( post ) { 32 | return post.isRelationshipLoaded( "author" ); 33 | }, "The relationship should now be loaded." ); 34 | }, 35 | skip = server.keyExists( "boxlang" ) 36 | ); 37 | } ); 38 | } 39 | 40 | function preQBExecute( 41 | event, 42 | interceptData, 43 | buffer, 44 | rc, 45 | prc 46 | ) { 47 | arrayAppend( variables.queries, interceptData ); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /tests/specs/integration/Unitlike.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" { 2 | 3 | function run() { 4 | describe( "codegen", function() { 5 | it( "Nested wheres have the expected associativity", function() { 6 | var sql = getInstance( "User" ) 7 | .reselect( "*" ) 8 | .withNestedWheresCombinedViaOr() 9 | .toSql(); 10 | 11 | expect( sql ).toBe( "SELECT * FROM `users` WHERE ((`users`.`first_name` = ? AND `users`.`last_name` = ?) OR (`users`.`first_name` = ? AND `users`.`last_name` = ?))" ) 12 | } ); 13 | } ); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /tests/specs/performance/EntityCreationSpec.cfc: -------------------------------------------------------------------------------- 1 | component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" { 2 | 3 | function run() { 4 | describe( "Entity Creation", function() { 5 | it( "entity creation should take less than 1ms on average", function() { 6 | var numEntities = 100; 7 | var times = []; 8 | var last = microsecondsTickCount(); 9 | for ( var i = 1; i <= numEntities; i++ ) { 10 | getInstance( "User" ); 11 | var now = microsecondsTickCount(); 12 | times.append( now - last ); 13 | last = now; 14 | } 15 | arrayDeleteAt( times, 1 ); // ignore the first one. WireBox and Quick are both booting up there. 16 | var averageDurationInMicroseconds = times.sum() / times.len(); 17 | var averageDuration = averageDurationInMicroseconds / 1000; 18 | debug( "Average duration: #averageDuration# ms" ); 19 | // debug( times ); 20 | } ); 21 | 22 | it( "can retrieve 1000 records", function() { 23 | queryExecute( "TRUNCATE TABLE `a`" ); 24 | for ( var i = 1; i <= 1000; i++ ) { 25 | // create A 26 | var a = queryExecute( "INSERT INTO `a` (`name`) VALUES (?)", [ "Instance #i#" ] ); 27 | } 28 | var start = microsecondsTickCount(); 29 | var records = getInstance( "A" ).get(); 30 | var end = microsecondsTickCount(); 31 | debug( "Duration: #( end - start ) / 1000# ms" ); 32 | expect( records ).toHaveLength( 1000 ); 33 | } ); 34 | } ); 35 | } 36 | 37 | function microsecondsTickCount() { 38 | param variables.javaSystem = createObject( "java", "java.lang.System" ); 39 | return variables.javaSystem.nanoTime() / 1000; 40 | } 41 | 42 | } 43 | --------------------------------------------------------------------------------