From de4382458ac23850ab23091aadd971806e5a32e8 Mon Sep 17 00:00:00 2001 From: harell Date: Mon, 9 Dec 2024 19:35:16 +1300 Subject: [PATCH 01/40] bump up version --- .Rprofile | 27 ++++++++++++++++++--------- DESCRIPTION | 4 ++-- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.Rprofile b/.Rprofile index 4e6aacc..ab14715 100644 --- a/.Rprofile +++ b/.Rprofile @@ -1,22 +1,31 @@ assign(".Rprofile", new.env(), envir = globalenv()) # .First ------------------------------------------------------------------ -.First <- function(){ - try(if(testthat::is_testing()) return()) - try(readRenviron(".Renviron"), silent = TRUE) +.First <- function() { + try(if (testthat::is_testing()) { + return() + }, silent = TRUE) + if (file.exists(".Renviron")) readRenviron(".Renviron") + # Package Management System - Date <- as.character(read.dcf("DESCRIPTION", "Date")); - URL <- if(is.na(Date)) "https://cran.rstudio.com/" else paste0("https://mran.microsoft.com/snapshot/", Date) - options(repos = URL) + r_version_date <- gsub(".*\\((\\d{4}-\\d{2}-\\d{2}).*", "\\1", R.Version()$version.string) + r_package_date <- as.character(read.dcf("DESCRIPTION", "Date")) + r_cran_date <- ifelse(is.na(r_package_date), r_version_date, r_package_date) + options(repos = c(CRAN = paste0("https://packagemanager.rstudio.com/cran/", r_cran_date))) + + # Options + Sys.setenv(`_R_S3_METHOD_REGISTRATION_NOTE_OVERWRITES_` = "false") } # .Last ------------------------------------------------------------------- -.Last <- function(){ - try(if(testthat::is_testing()) return()) - try(system('docker-compose down'), silent = TRUE) +.Last <- function() { + try(if (testthat::is_testing()) { + return() + }) } + # Docker ------------------------------------------------------------------ .Rprofile$docker$browse_url <- function(service){ path_script <- tempfile("system-", fileext = ".R") diff --git a/DESCRIPTION b/DESCRIPTION index 07c039c..e23502d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -3,8 +3,8 @@ Type: Package Title: Design Patterns in R URL: https://tidylab.github.io/R6P/, https://github.com/tidylab/R6P BugReports: https://github.com/tidylab/R6P/issues -Version: 0.2.2 -Date: 2021-08-01 +Version: 0.3.0 +Date: 2024-11-01 Authors@R: c( person("Harel", "Lustiger", email = "tidylab@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2953-9598")), From 5634ffca53fecaf81ce0a2faf944110c289217c9 Mon Sep 17 00:00:00 2001 From: harell Date: Mon, 9 Dec 2024 20:15:42 +1300 Subject: [PATCH 02/40] update github action to v2 --- .github/workflows/R-CMD-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index c60e9d5..6b5ffb2 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -36,7 +36,7 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: r-lib/actions/setup-r@v1 + - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} From d25f0544427c950080386fc6e087a2072a82e3c8 Mon Sep 17 00:00:00 2001 From: harell Date: Mon, 9 Dec 2024 20:37:53 +1300 Subject: [PATCH 03/40] update github aCTION --- .github/workflows/R-CMD-check.yml | 20 +++---- DESCRIPTION | 88 +++++++++++++++---------------- 2 files changed, 55 insertions(+), 53 deletions(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 6b5ffb2..01c4464 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -44,15 +44,17 @@ jobs: - name: Prepare run: | - echo "utils::chooseCRANmirror(graphics=FALSE, ind = 1)" > .Rprofile - Rscript -e "Date <- as.character(read.dcf('DESCRIPTION', 'Date')); - URL <- if(is.na(Date)) 'https://cran.rstudio.com/' else paste0('https://mran.microsoft.com/snapshot/', Date); - Rprofile <- file('.Rprofile', open = 'wt'); - writeLines('.libPaths(Sys.getenv(\'R_LIBS_USER\'))', Rprofile); - writeLines('require(remotes)', Rprofile); - writeLines(paste0('options(repos = \'', URL, '\')'), Rprofile); - close(Rprofile)" - Rscript -e "if(!'remotes' %in% rownames(utils::installed.packages())) utils::install.packages('remotes')" + Rscript -e " + Rprofile <- file('.Rprofile', open = 'wt') + writeLines('.libPaths(Sys.getenv(\'R_LIBS_USER\'))', Rprofile) + writeLines('require(remotes)', Rprofile) + writeLines('r_version_date <- gsub(\".*\\\\((\\\\d{4}-\\\\d{2}-\\\\d{2}).*\", \"\\\\1\", R.Version()$version.string)', Rprofile) + writeLines('r_package_date <- as.character(read.dcf(\"DESCRIPTION\", \"Date\"))', Rprofile) + writeLines('r_cran_date <- ifelse(is.na(r_package_date), r_version_date, r_package_date)', Rprofile) + writeLines('options(repos = c(CRAN = paste0(\"https://packagemanager.rstudio.com/cran/\", r_cran_date)))', Rprofile) + close(Rprofile)" + Rscript -e "if(!'remotes' %in% rownames(utils::installed.packages())) utils::install.packages('remotes')" + - name: Query dependencies run: | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) diff --git a/DESCRIPTION b/DESCRIPTION index e23502d..917f3c9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,44 +1,44 @@ -Package: R6P -Type: Package -Title: Design Patterns in R -URL: https://tidylab.github.io/R6P/, https://github.com/tidylab/R6P -BugReports: https://github.com/tidylab/R6P/issues -Version: 0.3.0 -Date: 2024-11-01 -Authors@R: c( - person("Harel", "Lustiger", email = "tidylab@gmail.com", role = c("aut", "cre"), - comment = c(ORCID = "0000-0003-2953-9598")), - person("Tidylab", role = c("cph", "fnd")) - ) -Maintainer: Harel Lustiger -Description: Build robust and maintainable software with object-oriented design - patterns in R. Design patterns abstract and present in neat, well-defined - components and interfaces the experience of many software designers and - architects over many years of solving similar problems. These are solutions - that have withstood the test of time with respect to re-usability, - flexibility, and maintainability. 'R6P' provides abstract base classes with - examples for a few known design patterns. The patterns were selected by - their applicability to analytic projects in R. Using these patterns in R - projects have proven effective in dealing with the complexity that - data-driven applications possess. -License: MIT + file LICENSE -Encoding: UTF-8 -RoxygenNote: 7.1.1 -Roxygen: list(markdown = TRUE, r6 = TRUE) -Language: en-GB -Depends: - R (>= 3.5) -Suggests: - testthat, - DBI, - RSQLite, - ggplot2 -Imports: - collections, - dplyr, - purrr, - stringr, - R6, - tibble, - tidyr -Config/testthat/edition: 3 +Package: R6P +Type: Package +Title: Design Patterns in R +URL: https://tidylab.github.io/R6P/, https://github.com/tidylab/R6P +BugReports: https://github.com/tidylab/R6P/issues +Version: 0.3.0 +Date: 2024-11-01 +Authors@R: c( + person("Harel", "Lustiger", email = "tidylab@gmail.com", role = c("aut", "cre"), + comment = c(ORCID = "0000-0003-2953-9598")), + person("Tidylab", role = c("cph", "fnd")) + ) +Maintainer: Harel Lustiger +Description: Build robust and maintainable software with object-oriented design + patterns in R. Design patterns abstract and present in neat, well-defined + components and interfaces the experience of many software designers and + architects over many years of solving similar problems. These are solutions + that have withstood the test of time with respect to re-usability, + flexibility, and maintainability. 'R6P' provides abstract base classes with + examples for a few known design patterns. The patterns were selected by + their applicability to analytic projects in R. Using these patterns in R + projects have proven effective in dealing with the complexity that + data-driven applications possess. +License: MIT + file LICENSE +Encoding: UTF-8 +RoxygenNote: 7.3.2 +Roxygen: list(markdown = TRUE, r6 = TRUE) +Language: en-GB +Depends: + R (>= 3.5) +Suggests: + testthat, + DBI, + RSQLite, + ggplot2 +Imports: + collections, + dplyr, + purrr, + stringr, + R6, + tibble, + tidyr +Config/testthat/edition: 3 From 97de698fa3d443a47280874cf35f17c998215bbb Mon Sep 17 00:00:00 2001 From: harell Date: Mon, 9 Dec 2024 20:41:16 +1300 Subject: [PATCH 04/40] fix missing qoutes --- .github/workflows/R-CMD-check.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 01c4464..46611ae 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -46,13 +46,14 @@ jobs: run: | Rscript -e " Rprofile <- file('.Rprofile', open = 'wt') - writeLines('.libPaths(Sys.getenv(\'R_LIBS_USER\'))', Rprofile) + writeLines('.libPaths(Sys.getenv(\\'R_LIBS_USER\\'))', Rprofile) writeLines('require(remotes)', Rprofile) - writeLines('r_version_date <- gsub(\".*\\\\((\\\\d{4}-\\\\d{2}-\\\\d{2}).*\", \"\\\\1\", R.Version()$version.string)', Rprofile) + writeLines('r_version_date <- gsub(\".*\\\\\\\\((\\\\\\\\d{4}-\\\\\\\\d{2}-\\\\\\\\d{2}).*\", \"\\\\\\\\1\", R.Version()$version.string)', Rprofile) writeLines('r_package_date <- as.character(read.dcf(\"DESCRIPTION\", \"Date\"))', Rprofile) writeLines('r_cran_date <- ifelse(is.na(r_package_date), r_version_date, r_package_date)', Rprofile) writeLines('options(repos = c(CRAN = paste0(\"https://packagemanager.rstudio.com/cran/\", r_cran_date)))', Rprofile) - close(Rprofile)" + close(Rprofile) + " Rscript -e "if(!'remotes' %in% rownames(utils::installed.packages())) utils::install.packages('remotes')" - name: Query dependencies From 4cc48b3552d3f7890dd7716a598343efda212234 Mon Sep 17 00:00:00 2001 From: harell Date: Mon, 9 Dec 2024 20:47:01 +1300 Subject: [PATCH 05/40] create an empty RProfile file --- .github/workflows/R-CMD-check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 46611ae..bcdf923 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -45,6 +45,7 @@ jobs: - name: Prepare run: | Rscript -e " + file.create('.Rprofile', showWarnings = FALSE) Rprofile <- file('.Rprofile', open = 'wt') writeLines('.libPaths(Sys.getenv(\\'R_LIBS_USER\\'))', Rprofile) writeLines('require(remotes)', Rprofile) From 58127ab4883d8b9633b5699cc3376b2521d54061 Mon Sep 17 00:00:00 2001 From: harell Date: Mon, 9 Dec 2024 20:50:37 +1300 Subject: [PATCH 06/40] fix quotes --- .github/workflows/R-CMD-check.yml | 2 +- DESCRIPTION | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index bcdf923..876d9f4 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -49,7 +49,7 @@ jobs: Rprofile <- file('.Rprofile', open = 'wt') writeLines('.libPaths(Sys.getenv(\\'R_LIBS_USER\\'))', Rprofile) writeLines('require(remotes)', Rprofile) - writeLines('r_version_date <- gsub(\".*\\\\\\\\((\\\\\\\\d{4}-\\\\\\\\d{2}-\\\\\\\\d{2}).*\", \"\\\\\\\\1\", R.Version()$version.string)', Rprofile) + writeLines('r_version_date <- gsub(\".*\\\\((\\\\d{4}-\\\\d{2}-\\\\d{2}).*\", \"\\\\1\", R.Version()$version.string)', Rprofile) writeLines('r_package_date <- as.character(read.dcf(\"DESCRIPTION\", \"Date\"))', Rprofile) writeLines('r_cran_date <- ifelse(is.na(r_package_date), r_version_date, r_package_date)', Rprofile) writeLines('options(repos = c(CRAN = paste0(\"https://packagemanager.rstudio.com/cran/\", r_cran_date)))', Rprofile) diff --git a/DESCRIPTION b/DESCRIPTION index 917f3c9..75842e2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -4,7 +4,6 @@ Title: Design Patterns in R URL: https://tidylab.github.io/R6P/, https://github.com/tidylab/R6P BugReports: https://github.com/tidylab/R6P/issues Version: 0.3.0 -Date: 2024-11-01 Authors@R: c( person("Harel", "Lustiger", email = "tidylab@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2953-9598")), From 440345c8ce212c43bf20c990fab2168b1a8ea781 Mon Sep 17 00:00:00 2001 From: harell Date: Mon, 9 Dec 2024 20:59:00 +1300 Subject: [PATCH 07/40] fix quotes --- .github/workflows/R-CMD-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 876d9f4..9f87bbb 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -46,10 +46,10 @@ jobs: run: | Rscript -e " file.create('.Rprofile', showWarnings = FALSE) - Rprofile <- file('.Rprofile', open = 'wt') + Rprofile <- file('.Rprofile', open='wt') writeLines('.libPaths(Sys.getenv(\\'R_LIBS_USER\\'))', Rprofile) writeLines('require(remotes)', Rprofile) - writeLines('r_version_date <- gsub(\".*\\\\((\\\\d{4}-\\\\d{2}-\\\\d{2}).*\", \"\\\\1\", R.Version()$version.string)', Rprofile) + writeLines('r_version_date <- gsub(\".*\\\\((\\\\d{4}-\\\\d{2}-\\\\d{2})\\\\).*\", \"\\\\1\", R.Version()$version.string)', Rprofile) writeLines('r_package_date <- as.character(read.dcf(\"DESCRIPTION\", \"Date\"))', Rprofile) writeLines('r_cran_date <- ifelse(is.na(r_package_date), r_version_date, r_package_date)', Rprofile) writeLines('options(repos = c(CRAN = paste0(\"https://packagemanager.rstudio.com/cran/\", r_cran_date)))', Rprofile) From f0b970fd8c1d88fc17efa507a70b310233930c2a Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 20:38:15 +1300 Subject: [PATCH 08/40] update github actions --- .github/workflows/R-CMD-check.yml | 103 ++++++------------------------ R6P.Rproj | 1 + 2 files changed, 22 insertions(+), 82 deletions(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 9f87bbb..76952ab 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -1,102 +1,41 @@ -# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. -# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions +name: R-CMD-check + on: + # Triggers when pushing or opening pull requests on specified branches push: - # branches: - # - master + branches: [ master, develop ] pull_request: - branches: - - master - - develop - -name: R-CMD-check + branches: [ master, develop ] jobs: - R-CMD-check: + check: runs-on: ${{ matrix.config.os }} - - name: ${{ matrix.config.os }} (${{ matrix.config.r }}) - strategy: fail-fast: false matrix: + # Define operating systems and R versions config: - - {os: windows-latest, r: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel'} - {os: macOS-latest, r: 'release'} - - {os: ubuntu-20.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} - - {os: ubuntu-20.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} - - env: - R_REMOTES_NO_ERRORS_FROM_WARNINGS: true - R_COMPILE_AND_INSTALL_PACKAGES: always - RSPM: ${{ matrix.config.rspm }} - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - NOT_CRAN: false + - {os: windows-latest, r: 'release'} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + # Checks out your repository code so subsequent steps can access it - uses: r-lib/actions/setup-r@v2 - with: - r-version: ${{ matrix.config.r }} - - - uses: r-lib/actions/setup-pandoc@v1 - - - name: Prepare - run: | - Rscript -e " - file.create('.Rprofile', showWarnings = FALSE) - Rprofile <- file('.Rprofile', open='wt') - writeLines('.libPaths(Sys.getenv(\\'R_LIBS_USER\\'))', Rprofile) - writeLines('require(remotes)', Rprofile) - writeLines('r_version_date <- gsub(\".*\\\\((\\\\d{4}-\\\\d{2}-\\\\d{2})\\\\).*\", \"\\\\1\", R.Version()$version.string)', Rprofile) - writeLines('r_package_date <- as.character(read.dcf(\"DESCRIPTION\", \"Date\"))', Rprofile) - writeLines('r_cran_date <- ifelse(is.na(r_package_date), r_version_date, r_package_date)', Rprofile) - writeLines('options(repos = c(CRAN = paste0(\"https://packagemanager.rstudio.com/cran/\", r_cran_date)))', Rprofile) - close(Rprofile) - " - Rscript -e "if(!'remotes' %in% rownames(utils::installed.packages())) utils::install.packages('remotes')" + # Installs the specified R version on the runner - - name: Query dependencies - run: | - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} + - uses: r-lib/actions/setup-pandoc@v2 + # Ensures pandoc is available for R markdown tasks - - name: Cache R packages - if: runner.os != 'Windows' - uses: actions/cache@v2 + - uses: r-lib/actions/setup-r-dependencies@v2 + # Installs default dependencies and caches them, speeding up builds with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install system dependencies - if: runner.os == 'Linux' - run: | - while read -r cmd - do - eval sudo $cmd - done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') - sudo apt-get install build-essential libcurl4-gnutls-dev libxml2-dev libssl-dev libgit2-dev - - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran(c("devtools", "rcmdcheck", "rmarkdown")) - shell: Rscript {0} - - - name: Check - env: - _R_CHECK_CRAN_INCOMING_REMOTE_: false - run: | - devtools::document() - rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") - shell: Rscript {0} + extra-packages: devtools rcmdcheck rmarkdown - - name: Upload check results - if: failure() - uses: actions/upload-artifact@main + - uses: r-lib/actions/check-r-package@v2 + # Runs R CMD check on your package with the provided arguments with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results - path: check + build-args: --as-cran diff --git a/R6P.Rproj b/R6P.Rproj index 6c495c2..bccf93f 100644 --- a/R6P.Rproj +++ b/R6P.Rproj @@ -1,4 +1,5 @@ Version: 1.0 +ProjectId: 1ea654cd-8c89-4fbc-b9cf-a5ad61baf097 RestoreWorkspace: No SaveWorkspace: No From bf62ef1c8ff3ff9d40ea7828410a1e04dd05bb76 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 20:41:03 +1300 Subject: [PATCH 09/40] run test on any branch --- .github/workflows/R-CMD-check.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 76952ab..e2ee117 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -1,11 +1,9 @@ name: R-CMD-check on: - # Triggers when pushing or opening pull requests on specified branches + # Triggers on any push or pull request, regardless of branch push: - branches: [ master, develop ] pull_request: - branches: [ master, develop ] jobs: check: From 1cefa76c1153ea779ed3e6a1d46ca12f24f83c9a Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 20:50:28 +1300 Subject: [PATCH 10/40] add testthat --- .github/workflows/R-CMD-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index e2ee117..a08d54e 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -31,7 +31,7 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 # Installs default dependencies and caches them, speeding up builds with: - extra-packages: devtools rcmdcheck rmarkdown + extra-packages: devtools rcmdcheck rmarkdown testthat - uses: r-lib/actions/check-r-package@v2 # Runs R CMD check on your package with the provided arguments From 509069cfa5b64f7db27eab661d189e1930c334da Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 20:54:03 +1300 Subject: [PATCH 11/40] install-suggests --- .github/workflows/R-CMD-check.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index a08d54e..f0b96b1 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -22,7 +22,9 @@ jobs: - uses: actions/checkout@v3 # Checks out your repository code so subsequent steps can access it - - uses: r-lib/actions/setup-r@v2 + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + install-suggests: true # Installs the specified R version on the runner - uses: r-lib/actions/setup-pandoc@v2 From 321701f484550f692a238bceda18b5578911c3bc Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 21:04:00 +1300 Subject: [PATCH 12/40] dependencies: all --- .Rprofile | 109 ------------------------------ .github/workflows/R-CMD-check.yml | 2 +- R/R6P-package.R | 13 ++++ 3 files changed, 14 insertions(+), 110 deletions(-) create mode 100644 R/R6P-package.R diff --git a/.Rprofile b/.Rprofile index ab14715..42b6ecb 100644 --- a/.Rprofile +++ b/.Rprofile @@ -24,112 +24,3 @@ assign(".Rprofile", new.env(), envir = globalenv()) return() }) } - - -# Docker ------------------------------------------------------------------ -.Rprofile$docker$browse_url <- function(service){ - path_script <- tempfile("system-", fileext = ".R") - job_name <- paste("Testing", as.character(read.dcf('DESCRIPTION', 'Package')), "in a Docker Container") - define_service <- paste0("service = c(", paste0(paste0("'",service,"'"), collapse = ", "),")") - define_service <- if(is.null(service)) "service = NULL" else define_service - writeLines(c( - "source('./R/utils-DockerCompose.R')", - define_service, - "DockerCompose$new()$browse_url(service)"), path_script) - .Rprofile$utils$run_script(path_script, job_name) -} - -.Rprofile$docker$start <- function(service = NULL){ - path_script <- tempfile("system-", fileext = ".R") - job_name <- paste("Testing", as.character(read.dcf('DESCRIPTION', 'Package')), "in a Docker Container") - define_service <- paste0("service <- c(", paste0(paste0("'",service,"'"), collapse = ", "),")") - define_service <- if(is.null(service)) "service = NULL" else define_service - writeLines(c( - "source('./R/utils-DockerCompose.R')", - define_service, - "DockerCompose$new()$start(service)"), path_script) - .Rprofile$utils$run_script(path_script, job_name) -} - -.Rprofile$docker$stop <- function(){ - path_script <- tempfile("system-", fileext = ".R") - job_name <- paste("Testing", as.character(read.dcf('DESCRIPTION', 'Package')), "in a Docker Container") - writeLines(c("source('./R/utils-DockerCompose.R'); DockerCompose$new()$stop()"), path_script) - .Rprofile$utils$run_script(path_script, job_name) -} - -.Rprofile$docker$restart <- function(service = NULL){ - path_script <- tempfile("system-", fileext = ".R") - job_name <- paste("Testing", as.character(read.dcf('DESCRIPTION', 'Package')), "in a Docker Container") - define_service <- paste0("service <- c(", paste0(paste0("'",service,"'"), collapse = ", "),")") - define_service <- if(is.null(service)) "service = NULL" else define_service - writeLines(c( - "source('./R/utils-DockerCompose.R')", - define_service, - "DockerCompose$new()$restart(service)"), path_script) - .Rprofile$utils$run_script(path_script, job_name) -} - -.Rprofile$docker$reset <- function(){ - path_script <- tempfile("system-", fileext = ".R") - job_name <- paste("Testing", as.character(read.dcf('DESCRIPTION', 'Package')), "in a Docker Container") - writeLines(c("source('./R/utils-DockerCompose.R'); DockerCompose$new()$reset()"), path_script) - .Rprofile$utils$run_script(path_script, job_name) -} - -# pkgdown ----------------------------------------------------------------- -.Rprofile$pkgdown$browse <- function(name){ - if(missing(name)){ - path <- "./docs" - name <- "index.html" - } else { - path <- "./docs/articles" - name <- match.arg(name, list.files(path, "*.html")) - } - try(browseURL(stringr::str_glue('{path}/{name}', path = path, name = name))) - invisible() -} - -.Rprofile$pkgdown$create <- function(){ - path_script <- tempfile("system-", fileext = ".R") - job_name <- "Rendering Package Website" - - writeLines(c( - "devtools::document()", - "rmarkdown::render('README.Rmd', 'md_document')", - "unlink(usethis::proj_path('docs'), TRUE, TRUE)", - paste0("try(detach('package:",read.dcf("DESCRIPTION", "Package")[[1]], "', unload = TRUE, force = TRUE))"), - "pkgdown::build_site(devel = FALSE, lazy = FALSE)" - ), path_script) - - .Rprofile$utils$run_script(path_script, job_name) -} - -.Rprofile$pkgdown$update <- function(){ - path_script <- tempfile("system-", fileext = ".R") - job_name <- "Rendering Package Website" - - writeLines(c( - "devtools::document()", - "rmarkdown::render('README.Rmd', 'md_document')", - paste0("try(detach('package:",read.dcf("DESCRIPTION", "Package")[[1]], "', unload = TRUE, force = TRUE))"), - "pkgdown::build_site(devel = TRUE, lazy = TRUE)" - ), path_script) - - .Rprofile$utils$run_script(path_script, job_name) -} - -# Utils ------------------------------------------------------------------- -.Rprofile$utils$run_script <- function(path, name){ - withr::with_envvar( - c(TESTTHAT = "true"), - rstudioapi::jobRunScript( - path = path, - name = name, - workingDir = ".", - importEnv = FALSE, - exportEnv = "" - )) - invisible() -} - diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index f0b96b1..913d273 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -24,7 +24,7 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - install-suggests: true + dependencies: all # Installs the specified R version on the runner - uses: r-lib/actions/setup-pandoc@v2 diff --git a/R/R6P-package.R b/R/R6P-package.R new file mode 100644 index 0000000..8547a91 --- /dev/null +++ b/R/R6P-package.R @@ -0,0 +1,13 @@ +#' @keywords internal +"_PACKAGE" + +## usethis namespace: start +## usethis namespace: end +NULL + +# Missing Packages Workaround +.workaround <- function() { + R6::R6Class + collections::queue + dplyr::.data +} From 7f8972e551cbfb509e34b87dc297ff455e04706d Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 21:08:50 +1300 Subject: [PATCH 13/40] install R --- .Rprofile | 1 + .github/workflows/R-CMD-check.yml | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.Rprofile b/.Rprofile index 42b6ecb..e76b332 100644 --- a/.Rprofile +++ b/.Rprofile @@ -16,6 +16,7 @@ assign(".Rprofile", new.env(), envir = globalenv()) # Options Sys.setenv(`_R_S3_METHOD_REGISTRATION_NOTE_OVERWRITES_` = "false") + Sys.setenv(`_R_CHECK_SYSTEM_CLOCK_` = 0) } # .Last ------------------------------------------------------------------- diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 913d273..74c9e52 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -1,7 +1,7 @@ name: R-CMD-check on: - # Triggers on any push or pull request, regardless of branch + # Trigger on pushes and pull requests. push: pull_request: @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - # Define operating systems and R versions + # Operating systems and R versions to test. config: - {os: ubuntu-latest, r: 'release'} - {os: ubuntu-latest, r: 'devel'} @@ -20,22 +20,27 @@ jobs: steps: - uses: actions/checkout@v3 - # Checks out your repository code so subsequent steps can access it + # Checks out repository code. + + - uses: r-lib/actions/setup-r@v2 + # Installs the R version specified in the matrix. + with: + r-version: ${{ matrix.config.r }} - uses: r-lib/actions/setup-r-dependencies@v2 + # Installs core dependencies from CRAN. with: dependencies: all - # Installs the specified R version on the runner - uses: r-lib/actions/setup-pandoc@v2 - # Ensures pandoc is available for R markdown tasks + # Makes pandoc available for R Markdown tasks. - uses: r-lib/actions/setup-r-dependencies@v2 - # Installs default dependencies and caches them, speeding up builds + # Installs additional R packages. with: extra-packages: devtools rcmdcheck rmarkdown testthat - uses: r-lib/actions/check-r-package@v2 - # Runs R CMD check on your package with the provided arguments + # Executes R CMD check on the package. with: build-args: --as-cran From 95aed2106e56bc3c531393c39a9eb93ccafd5bde Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 21:12:32 +1300 Subject: [PATCH 14/40] delete Rprofile --- .github/workflows/R-CMD-check.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index 74c9e52..fa32e64 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -19,6 +19,10 @@ jobs: - {os: windows-latest, r: 'release'} steps: + - name: Remove .Rprofile + # Deletes .Rprofile if it exists in the root directory. + run: rm -f ./.Rprofile + - uses: actions/checkout@v3 # Checks out repository code. From d192d9f5008e7916d008c1a200575224db1575e7 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 21:16:46 +1300 Subject: [PATCH 15/40] setup testthat --- .github/workflows/R-CMD-check.yml | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index fa32e64..c656332 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -1,7 +1,6 @@ name: R-CMD-check on: - # Trigger on pushes and pull requests. push: pull_request: @@ -11,7 +10,6 @@ jobs: strategy: fail-fast: false matrix: - # Operating systems and R versions to test. config: - {os: ubuntu-latest, r: 'release'} - {os: ubuntu-latest, r: 'devel'} @@ -20,31 +18,27 @@ jobs: steps: - name: Remove .Rprofile - # Deletes .Rprofile if it exists in the root directory. + # Delete .Rprofile if it exists in the repository root. run: rm -f ./.Rprofile - uses: actions/checkout@v3 - # Checks out repository code. + # Check out repository code. - uses: r-lib/actions/setup-r@v2 - # Installs the R version specified in the matrix. + # Install the specified R version. with: r-version: ${{ matrix.config.r }} - uses: r-lib/actions/setup-r-dependencies@v2 - # Installs core dependencies from CRAN. + # Install dependencies: specifying 'true' ensures all, including Suggests. with: - dependencies: all + dependencies: 'true' + extra-packages: devtools rcmdcheck rmarkdown testthat - uses: r-lib/actions/setup-pandoc@v2 - # Makes pandoc available for R Markdown tasks. - - - uses: r-lib/actions/setup-r-dependencies@v2 - # Installs additional R packages. - with: - extra-packages: devtools rcmdcheck rmarkdown testthat + # Provide pandoc for R Markdown tasks. - uses: r-lib/actions/check-r-package@v2 - # Executes R CMD check on the package. + # Run R CMD check with CRAN-like checks. with: build-args: --as-cran From e57f287bcdbf4b11385da6b7160ca55b72838170 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 21:20:15 +1300 Subject: [PATCH 16/40] all --- .Rprofile | 56 +++++++++++++++++++++++++++++++ .github/workflows/R-CMD-check.yml | 16 ++++----- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/.Rprofile b/.Rprofile index e76b332..e5223d6 100644 --- a/.Rprofile +++ b/.Rprofile @@ -19,6 +19,62 @@ assign(".Rprofile", new.env(), envir = globalenv()) Sys.setenv(`_R_CHECK_SYSTEM_CLOCK_` = 0) } +# pkgdown ----------------------------------------------------------------- +.Rprofile$pkgdown$browse <- function(name){ + if(missing(name)){ + path <- "./docs" + name <- "index.html" + } else { + path <- "./docs/articles" + name <- match.arg(name, list.files(path, "*.html")) + } + try(browseURL(stringr::str_glue('{path}/{name}', path = path, name = name))) + invisible() +} + +.Rprofile$pkgdown$create <- function(){ + path_script <- tempfile("system-", fileext = ".R") + job_name <- "Rendering Package Website" + + writeLines(c( + "devtools::document()", + "rmarkdown::render('README.Rmd', 'md_document')", + "unlink(usethis::proj_path('docs'), TRUE, TRUE)", + paste0("try(detach('package:",read.dcf("DESCRIPTION", "Package")[[1]], "', unload = TRUE, force = TRUE))"), + "pkgdown::build_site(devel = FALSE, lazy = FALSE)" + ), path_script) + + .Rprofile$utils$run_script(path_script, job_name) +} + +.Rprofile$pkgdown$update <- function(){ + path_script <- tempfile("system-", fileext = ".R") + job_name <- "Rendering Package Website" + + writeLines(c( + "devtools::document()", + "rmarkdown::render('README.Rmd', 'md_document')", + paste0("try(detach('package:",read.dcf("DESCRIPTION", "Package")[[1]], "', unload = TRUE, force = TRUE))"), + "pkgdown::build_site(devel = TRUE, lazy = TRUE)" + ), path_script) + + .Rprofile$utils$run_script(path_script, job_name) +} + +# Utils ------------------------------------------------------------------- +.Rprofile$utils$run_script <- function(path, name){ + withr::with_envvar( + c(TESTTHAT = "true"), + rstudioapi::jobRunScript( + path = path, + name = name, + workingDir = ".", + importEnv = FALSE, + exportEnv = "" + )) + invisible() +} + # .Last ------------------------------------------------------------------- .Last <- function() { try(if (testthat::is_testing()) { diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml index c656332..c4d1158 100644 --- a/.github/workflows/R-CMD-check.yml +++ b/.github/workflows/R-CMD-check.yml @@ -11,28 +11,28 @@ jobs: fail-fast: false matrix: config: - - {os: ubuntu-latest, r: 'release'} - - {os: ubuntu-latest, r: 'devel'} - - {os: macOS-latest, r: 'release'} - - {os: windows-latest, r: 'release'} + - { os: ubuntu-latest, r: 'release' } + - { os: ubuntu-latest, r: 'devel' } + - { os: macOS-latest, r: 'release' } + - { os: windows-latest, r: 'release' } steps: - name: Remove .Rprofile - # Delete .Rprofile if it exists in the repository root. + # Delete .Rprofile if present. run: rm -f ./.Rprofile - uses: actions/checkout@v3 # Check out repository code. - uses: r-lib/actions/setup-r@v2 - # Install the specified R version. + # Install the R version from matrix. with: r-version: ${{ matrix.config.r }} - uses: r-lib/actions/setup-r-dependencies@v2 - # Install dependencies: specifying 'true' ensures all, including Suggests. + # Install dependencies and extra packages. with: - dependencies: 'true' + dependencies: all extra-packages: devtools rcmdcheck rmarkdown testthat - uses: r-lib/actions/setup-pandoc@v2 From 28d8e1c2ee0593d3d76d1d9b3f32fb1e8166bb26 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 21:26:37 +1300 Subject: [PATCH 17/40] update github action --- .Rbuildignore | 1 + .github/workflows/R-CMD-check.yaml | 37 +++++++++++++++++++ .github/workflows/R-CMD-check.yml | 44 ----------------------- README.Rmd | 7 ++-- README.md | 57 +++++++++++++++++++++++------- 5 files changed, 85 insertions(+), 61 deletions(-) create mode 100644 .github/workflows/R-CMD-check.yaml delete mode 100644 .github/workflows/R-CMD-check.yml diff --git a/.Rbuildignore b/.Rbuildignore index 3c7b99d..356fb6f 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -29,3 +29,4 @@ +^\.github$ diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 0000000..63dc201 --- /dev/null +++ b/.github/workflows/R-CMD-check.yaml @@ -0,0 +1,37 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + +name: R-CMD-check.yaml + +permissions: read-all + +jobs: + R-CMD-check: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + steps: + - name: Remove .Rprofile + # Delete .Rprofile if present. + run: rm -f ./.Rprofile + + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml deleted file mode 100644 index c4d1158..0000000 --- a/.github/workflows/R-CMD-check.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: R-CMD-check - -on: - push: - pull_request: - -jobs: - check: - runs-on: ${{ matrix.config.os }} - strategy: - fail-fast: false - matrix: - config: - - { os: ubuntu-latest, r: 'release' } - - { os: ubuntu-latest, r: 'devel' } - - { os: macOS-latest, r: 'release' } - - { os: windows-latest, r: 'release' } - - steps: - - name: Remove .Rprofile - # Delete .Rprofile if present. - run: rm -f ./.Rprofile - - - uses: actions/checkout@v3 - # Check out repository code. - - - uses: r-lib/actions/setup-r@v2 - # Install the R version from matrix. - with: - r-version: ${{ matrix.config.r }} - - - uses: r-lib/actions/setup-r-dependencies@v2 - # Install dependencies and extra packages. - with: - dependencies: all - extra-packages: devtools rcmdcheck rmarkdown testthat - - - uses: r-lib/actions/setup-pandoc@v2 - # Provide pandoc for R Markdown tasks. - - - uses: r-lib/actions/check-r-package@v2 - # Run R CMD check with CRAN-like checks. - with: - build-args: --as-cran diff --git a/README.Rmd b/README.Rmd index 23c353f..b11b919 100644 --- a/README.Rmd +++ b/README.Rmd @@ -24,13 +24,10 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) # `R6P` - [![CRAN status](https://www.r-pkg.org/badges/version/R6P)](https://CRAN.R-project.org/package=R6P) -[![R build -status](https://github.com/tidylab/R6P/workflows/R-CMD-check/badge.svg)](https://github.com/tidylab/R6P/actions) -[![codecov](https://codecov.io/gh/tidylab/R6P/branch/master/graph/badge.svg?token=U6FL5N32FL)](https://codecov.io/gh/tidylab/R6P) - +[![Codecov test coverage](https://codecov.io/gh/tidylab/R6P/graph/badge.svg)](https://app.codecov.io/gh/tidylab/R6P) +[![R-CMD-check](https://github.com/tidylab/R6P/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tidylab/R6P/actions/workflows/R-CMD-check.yaml) ```{r, echo = FALSE, out.width = "100%"} diff --git a/README.md b/README.md index 43fa170..695c718 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + # `R6P` @@ -6,19 +7,20 @@ [![CRAN status](https://www.r-pkg.org/badges/version/R6P)](https://CRAN.R-project.org/package=R6P) -[![R build -status](https://github.com/tidylab/R6P/workflows/R-CMD-check/badge.svg)](https://github.com/tidylab/R6P/actions) -[![codecov](https://codecov.io/gh/tidylab/R6P/branch/master/graph/badge.svg?token=U6FL5N32FL)](https://codecov.io/gh/tidylab/R6P) - +[![Codecov test +coverage](https://codecov.io/gh/tidylab/R6P/graph/badge.svg)](https://app.codecov.io/gh/tidylab/R6P) +[![R-CMD-check](https://github.com/tidylab/R6P/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tidylab/R6P/actions/workflows/R-CMD-check.yaml) ## Intentions -- `R6P` is a collection of useful design patterns in R -- `R6P` explains *how a design pattern works* and *when to use a - design pattern* -- `R6P` provides examples that show how to implement each design - pattern in R +- `R6P` is a collection of useful design patterns in R +- `R6P` explains *how a design pattern works* and *when to use a design + pattern* +- `R6P` provides examples that show how to implement each design pattern + in R + +
**Caution:** Most functions and classes provided by the `R6P` package are not useful by themselves. This is because design patterns are @@ -27,6 +29,8 @@ demonstration purposes. Instead of directly using the design pattern as they appear in the package, you’d have to adjust the source code (provided in the examples) to the problem you are trying to solve. +
+ ## Introduction Build robust and maintainable software with object-oriented design @@ -44,6 +48,8 @@ This package is based on the work of Gamma1995, and Fowler2002. ### Should I use design patterns? +
+ Design patterns represent an alternative to design: rather than designing a new mechanism from scratch, just apply a well-known design pattern. For the most part, this is good: design patterns arose because @@ -52,6 +58,10 @@ provide clean solutions. If a design pattern works well in a particular situation, it will probably be hard for you to come up with a different approach that is better. +
+ +
+ The greatest risk with design patterns is over-application. Not every problem can be solved cleanly with an existing design pattern; don’t try to force a problem into a design pattern when a custom approach will be @@ -60,26 +70,49 @@ system; it only does so if the design patterns fit. As with many ideas in software design, the notion that design patterns are good doesn’t necessarily mean that more design patterns are better. +
+ ## Installation You can install the released version of R6P from CRAN with: - install.packages("R6P") +``` r +install.packages("R6P") +``` And the development version from GitHub with: - # install.packages("devtools") - devtools::install_github("tidylab/R6P") +``` r +# install.packages("devtools") +devtools::install_github("tidylab/R6P") +``` ## References +
+ +
+ Fowler, Martin. 2002. *Patterns of enterprise application architecture*. Addison-Wesley Longman Publishing Co., Inc. +
+ +
+ Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. 1995. *Design patterns: elements of reusable object-oriented software*. Pearson Education India. +
+ +
+ Ousterhout, John. 2018. *A Philosophy of Software Design*. Yaknyam Press. + +
+ +
From 64a9a1eed3cc2b9384c1c4858cd10c05a011cb1c Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 21:27:21 +1300 Subject: [PATCH 18/40] run on all branchs --- .github/workflows/R-CMD-check.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 63dc201..639697a 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -2,7 +2,6 @@ # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: - branches: [main, master] pull_request: name: R-CMD-check.yaml From 986bf981dcd5487966ab36d533bbc2cd243808a1 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 21:40:58 +1300 Subject: [PATCH 19/40] remove dependency purrr --- .github/workflows/R-CMD-check.yaml | 2 +- DESCRIPTION | 3 +- NAMESPACE | 4 --- R/base-ValueObject.R | 8 ++--- R/object_relational-Repository.R | 4 +-- R/utils-pipes.R | 19 ------------ README.md | 50 +++++------------------------- pkgdown/_pkgdown.yml | 1 + tests/testthat/helpers-xyz.R | 5 +-- vignettes/_common.R | 10 +++--- vignettes/details/ValueObject.Rmd | 8 ++--- vignettes/patterns/NullObject.Rmd | 4 +-- vignettes/patterns/Repository.Rmd | 10 +++--- vignettes/patterns/ValueObject.Rmd | 4 +-- 14 files changed, 38 insertions(+), 94 deletions(-) delete mode 100644 R/utils-pipes.R diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 639697a..5d5b1aa 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -4,7 +4,7 @@ on: push: pull_request: -name: R-CMD-check.yaml +name: R-CMD-check permissions: read-all diff --git a/DESCRIPTION b/DESCRIPTION index 75842e2..4140718 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,7 +26,7 @@ RoxygenNote: 7.3.2 Roxygen: list(markdown = TRUE, r6 = TRUE) Language: en-GB Depends: - R (>= 3.5) + R (>= 4.1) Suggests: testthat, DBI, @@ -35,7 +35,6 @@ Suggests: Imports: collections, dplyr, - purrr, stringr, R6, tibble, diff --git a/NAMESPACE b/NAMESPACE index eb76308..731233a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,10 +1,6 @@ # Generated by roxygen2: do not edit by hand -export("%>%") -export("%||%") export(AbstractRepository) export(NullObject) export(Singleton) export(ValueObject) -importFrom(purrr,"%>%") -importFrom(purrr,"%||%") diff --git a/R/base-ValueObject.R b/R/base-ValueObject.R index a41800b..3e2506c 100644 --- a/R/base-ValueObject.R +++ b/R/base-ValueObject.R @@ -29,7 +29,7 @@ #' stopifnot(is.Person(member), is.character(title)) #' stopifnot(nrow(member) == length(title) | all(is.na(title))) #' -#' member %>% dplyr::mutate(title = title) +#' member |> dplyr::mutate(title = title) #'} #' #' # Given one or more parliament members @@ -66,9 +66,9 @@ ValueObject <- function( stopifnot(length(given) == length(family) | all(is.na(family))) tibble::tibble( - given = given %>% stringr::str_to_title(), - family = family %>% stringr::str_to_title() - ) %>% tidyr::drop_na(given) + given = given |> stringr::str_to_title(), + family = family |> stringr::str_to_title() + ) |> tidyr::drop_na(given) } diff --git a/R/object_relational-Repository.R b/R/object_relational-Repository.R index fbf2b30..6e09189 100644 --- a/R/object_relational-Repository.R +++ b/R/object_relational-Repository.R @@ -34,13 +34,13 @@ #' # The following example, adds a query that returns all the objects in the database #' #' TransientRepository$set("public", "get_all_cars", overwrite = TRUE, function(){ -#' result <- private$cars$values() %>% dplyr::bind_rows() +#' result <- private$cars$values() |> dplyr::bind_rows() #' if(nrow(result) == 0) return(private$NULL_car) else return(result) #' }) #' #' # In this example, we use the mtcars dataset with a uid column that uniquely #' # identifies the different cars in the Repository: -#' mtcars <- datasets::mtcars %>% tibble::rownames_to_column("uid") +#' mtcars <- datasets::mtcars |> tibble::rownames_to_column("uid") #' head(mtcars, 2) #' #' # Here is how the caller uses the Repository: diff --git a/R/utils-pipes.R b/R/utils-pipes.R deleted file mode 100644 index 1e325fb..0000000 --- a/R/utils-pipes.R +++ /dev/null @@ -1,19 +0,0 @@ -#' Forward pipe operator -#' -#' @name %>% -#' @keywords internal -#' @importFrom purrr %>% -#' @usage lhs \%>\% rhs -#' @rdname pipes -#' @export -NULL - -#' NULL operator -#' -#' @name %||% -#' @keywords internal -#' @importFrom purrr %||% -#' @usage x \%||\% y -#' @rdname pipes -#' @export -NULL diff --git a/README.md b/README.md index 695c718..08e9eca 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # `R6P` @@ -14,13 +13,11 @@ coverage](https://codecov.io/gh/tidylab/R6P/graph/badge.svg)](https://app.codeco ## Intentions -- `R6P` is a collection of useful design patterns in R -- `R6P` explains *how a design pattern works* and *when to use a design - pattern* -- `R6P` provides examples that show how to implement each design pattern - in R - -
+- `R6P` is a collection of useful design patterns in R +- `R6P` explains *how a design pattern works* and *when to use a + design pattern* +- `R6P` provides examples that show how to implement each design + pattern in R **Caution:** Most functions and classes provided by the `R6P` package are not useful by themselves. This is because design patterns are @@ -29,8 +26,6 @@ demonstration purposes. Instead of directly using the design pattern as they appear in the package, you’d have to adjust the source code (provided in the examples) to the problem you are trying to solve. -
- ## Introduction Build robust and maintainable software with object-oriented design @@ -48,8 +43,6 @@ This package is based on the work of Gamma1995, and Fowler2002. ### Should I use design patterns? -
- Design patterns represent an alternative to design: rather than designing a new mechanism from scratch, just apply a well-known design pattern. For the most part, this is good: design patterns arose because @@ -58,10 +51,6 @@ provide clean solutions. If a design pattern works well in a particular situation, it will probably be hard for you to come up with a different approach that is better. -
- -
- The greatest risk with design patterns is over-application. Not every problem can be solved cleanly with an existing design pattern; don’t try to force a problem into a design pattern when a custom approach will be @@ -70,49 +59,26 @@ system; it only does so if the design patterns fit. As with many ideas in software design, the notion that design patterns are good doesn’t necessarily mean that more design patterns are better. -
- ## Installation You can install the released version of R6P from CRAN with: -``` r -install.packages("R6P") -``` + install.packages("R6P") And the development version from GitHub with: -``` r -# install.packages("devtools") -devtools::install_github("tidylab/R6P") -``` + # install.packages("devtools") + devtools::install_github("tidylab/R6P") ## References -
- -
- Fowler, Martin. 2002. *Patterns of enterprise application architecture*. Addison-Wesley Longman Publishing Co., Inc. -
- -
- Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. 1995. *Design patterns: elements of reusable object-oriented software*. Pearson Education India. -
- -
- Ousterhout, John. 2018. *A Philosophy of Software Design*. Yaknyam Press. - -
- -
diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index bab2bd5..bd6406a 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -7,6 +7,7 @@ authors: href: "https://github.com/tidylab" html: "" template: + bootstrap: 5 params: docsearch: api_key: d5f049d51d6123dd767e5f8bc7882c35 diff --git a/tests/testthat/helpers-xyz.R b/tests/testthat/helpers-xyz.R index f47b4d2..6398007 100644 --- a/tests/testthat/helpers-xyz.R +++ b/tests/testthat/helpers-xyz.R @@ -6,11 +6,12 @@ banner <- function(title) paste0(line_break(), paste0("\n## ", title), line_brea # testthat ---------------------------------------------------------------- expect_class <- function(object, class) testthat::expect(any(base::class(object) %in% class), "object is" %+% base::class(object) %+% "not" %+% class) -expect_not_failure <- purrr::partial(testthat::expect_type, type = "environment") expect_has_columns <- function(data, cols) testthat::expect(all(cols %in% colnames(data)), "not all column names are in the data.frame") expect_file_exists <- function(path) testthat::expect(file.exists(path), "File doesn't exist at " %+% path) expect_match <- function(object, regexp) testthat::expect_match(stringr::str_flatten(object, collapse = "\n"), regexp) - +expect_not_failure <- function(object, ...) { + testthat::expect_type(object, type = "environment", ...) +} # mocks ------------------------------------------------------------------- DummyRepository <- R6::R6Class(classname = "Repository", inherit = AbstractRepository, public = list( diff --git a/vignettes/_common.R b/vignettes/_common.R index d9b2c92..868fbbe 100644 --- a/vignettes/_common.R +++ b/vignettes/_common.R @@ -36,16 +36,16 @@ knitr::opts_chunk$set( knitr::knit_hooks$set( error = function(x, options) { paste('\n\n
', - x %>% - stringr::str_replace_all('^.*:', '**Caution:**') %>% + x |> + stringr::str_replace_all('^.*:', '**Caution:**') |> stringr::str_replace_all('#> ', '\n'), '
', sep = '\n') }, warning = function(x, options) { paste('\n\n
', - x %>% - stringr::str_replace_all('##', '\n') %>% - stringr::str_replace_all('^#>\ Warning:', '**Note:**') %>% + x |> + stringr::str_replace_all('##', '\n') |> + stringr::str_replace_all('^#>\ Warning:', '**Note:**') |> stringr::str_remove_all("#>"), '
', sep = '\n') }, diff --git a/vignettes/details/ValueObject.Rmd b/vignettes/details/ValueObject.Rmd index ef22964..a89944d 100644 --- a/vignettes/details/ValueObject.Rmd +++ b/vignettes/details/ValueObject.Rmd @@ -53,7 +53,7 @@ Person <- function(given = NA_character_, family = NA_character_){ return( tibble::tibble(given = given, family = family) - %>% tidyr::drop_na(given) + |> tidyr::drop_na(given) ) } ``` @@ -133,8 +133,8 @@ Passenger <- function(person = Person(), booking_reference = NA_character_){ return( person - %>% tibble::add_column(booking_reference = booking_reference) - %>% tidyr::drop_na(booking_reference) + |> tibble::add_column(booking_reference = booking_reference) + |> tidyr::drop_na(booking_reference) ) } @@ -151,7 +151,7 @@ Diner <- function(person = Person(), reservation_time = NA_POSIXct_){ return( person - %>% tibble::add_column(reservation_time = reservation_time) + |> tibble::add_column(reservation_time = reservation_time) ) } diff --git a/vignettes/patterns/NullObject.Rmd b/vignettes/patterns/NullObject.Rmd index 0ea8e0a..6e4cd8a 100644 --- a/vignettes/patterns/NullObject.Rmd +++ b/vignettes/patterns/NullObject.Rmd @@ -35,7 +35,7 @@ fig <- tryCatch({ stopifnot(runif(1) > 0.05) # simulate 5% chance for the subroutine to fail - mtcars %>% + mtcars |> ggplot2::ggplot(ggplot::aes(x = mpg, y = hp)) + ggplot2::geom_point() }, @@ -74,7 +74,7 @@ identical(rbind(mtcars, NullCar()), mtcars) ```{r, echo=TRUE} Person <- function(given = NA_character_, family = NA_character_){ - tibble::tibble(given = given, family = family) %>% tidyr::drop_na(given) + tibble::tibble(given = given, family = family) |> tidyr::drop_na(given) } # Instantiating a person with a `given` name, returns a non-null person object diff --git a/vignettes/patterns/Repository.Rmd b/vignettes/patterns/Repository.Rmd index 9c76167..9edc4e5 100644 --- a/vignettes/patterns/Repository.Rmd +++ b/vignettes/patterns/Repository.Rmd @@ -26,7 +26,7 @@ The code of the abstract base class of **Repository** is ```{r} path_snippet <- usethis::proj_path("R", "object_relational-Repository.R") snippet <- readLines(path_snippet) -snippet <- snippet %>% discard_comments() %>% discard_null() %>% discard_empty_lines() +snippet <- snippet |> discard_comments() |> discard_null() |> discard_empty_lines() ``` ```{r, code = snippet, eval = FALSE, echo = TRUE} @@ -108,7 +108,7 @@ following example, adds a query that returns all the objects in the database ```{r Repository-Transient-implementation-addon, echo=TRUE, results='markup'} TransientRepository$set("public", "get_all_cars", overwrite = TRUE, function(){ - result <- private$cars$values() %>% dplyr::bind_rows() + result <- private$cars$values() |> dplyr::bind_rows() if(nrow(result) == 0) return(private$NULL_car) else return(result) }) ``` @@ -117,7 +117,7 @@ In this example, we use the `mtcars` dataset with a `uid` column that uniquely identifies the different cars in the **Repository**: ```{r, echo=TRUE, results='markup'} -mtcars <- datasets::mtcars %>% tibble::rownames_to_column("uid") +mtcars <- datasets::mtcars |> tibble::rownames_to_column("uid") head(mtcars, 2) ``` @@ -162,7 +162,7 @@ PersistentRepository <- R6::R6Class( DBI::dbCreateTable(private$conn, "mtcars", private$NULL_car) }, add = function(key, value){ - car <- private$NULL_car %>% tibble::add_row(value) + car <- private$NULL_car |> tibble::add_row(value) self$del(key = key) DBI::dbAppendTable(private$conn, "mtcars", car) invisible(self) @@ -199,7 +199,7 @@ In this example, we use the `mtcars` dataset with a `uid` column that uniquely identifies the different cars in the **Repository**: ```{r, echo=TRUE, results='markup'} -mtcars <- datasets::mtcars %>% tibble::rownames_to_column("uid") +mtcars <- datasets::mtcars |> tibble::rownames_to_column("uid") head(mtcars, 2) ``` diff --git a/vignettes/patterns/ValueObject.Rmd b/vignettes/patterns/ValueObject.Rmd index afd9c0d..2436d1d 100644 --- a/vignettes/patterns/ValueObject.Rmd +++ b/vignettes/patterns/ValueObject.Rmd @@ -49,7 +49,7 @@ Person <- function(given = NA_character_, family = NA_character_){ return( tibble::tibble(given = given, family = family) - %>% tidyr::drop_na(given) + |> tidyr::drop_na(given) ) } @@ -79,7 +79,7 @@ Minister <- function(member = Person(), title = NA_character_){ stopifnot(is.Person(member), is.character(title)) stopifnot(nrow(member) == length(title) | all(is.na(title))) - member %>% dplyr::mutate(title = title) + member |> dplyr::mutate(title = title) } ``` From 001e19cd10c0b6ce11b5e51232b3e960b832e719 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 22:31:10 +1300 Subject: [PATCH 20/40] update pkgdown articles --- README.Rmd | 2 +- README.md | 52 ++++++++++++++++++++++++------ pkgdown/_pkgdown.yml | 17 +++++++++- vignettes/patterns/NullObject.Rmd | 2 +- vignettes/patterns/Repository.Rmd | 3 +- vignettes/patterns/Singleton.Rmd | 3 +- vignettes/patterns/ValueObject.Rmd | 3 +- 7 files changed, 67 insertions(+), 15 deletions(-) diff --git a/README.Rmd b/README.Rmd index b11b919..38beb66 100644 --- a/README.Rmd +++ b/README.Rmd @@ -21,7 +21,7 @@ editor_options: source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` -# `R6P` +# `R6P` package logo [![CRAN diff --git a/README.md b/README.md index 08e9eca..bec9476 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ + -# `R6P` +# `R6P` package logo @@ -13,11 +14,13 @@ coverage](https://codecov.io/gh/tidylab/R6P/graph/badge.svg)](https://app.codeco ## Intentions -- `R6P` is a collection of useful design patterns in R -- `R6P` explains *how a design pattern works* and *when to use a - design pattern* -- `R6P` provides examples that show how to implement each design - pattern in R +- `R6P` is a collection of useful design patterns in R +- `R6P` explains *how a design pattern works* and *when to use a design + pattern* +- `R6P` provides examples that show how to implement each design pattern + in R + +
**Caution:** Most functions and classes provided by the `R6P` package are not useful by themselves. This is because design patterns are @@ -26,6 +29,8 @@ demonstration purposes. Instead of directly using the design pattern as they appear in the package, you’d have to adjust the source code (provided in the examples) to the problem you are trying to solve. +
+ ## Introduction Build robust and maintainable software with object-oriented design @@ -43,6 +48,8 @@ This package is based on the work of Gamma1995, and Fowler2002. ### Should I use design patterns? +
+ Design patterns represent an alternative to design: rather than designing a new mechanism from scratch, just apply a well-known design pattern. For the most part, this is good: design patterns arose because @@ -51,6 +58,10 @@ provide clean solutions. If a design pattern works well in a particular situation, it will probably be hard for you to come up with a different approach that is better. +
+ +
+ The greatest risk with design patterns is over-application. Not every problem can be solved cleanly with an existing design pattern; don’t try to force a problem into a design pattern when a custom approach will be @@ -59,26 +70,49 @@ system; it only does so if the design patterns fit. As with many ideas in software design, the notion that design patterns are good doesn’t necessarily mean that more design patterns are better. +
+ ## Installation You can install the released version of R6P from CRAN with: - install.packages("R6P") +``` r +install.packages("R6P") +``` And the development version from GitHub with: - # install.packages("devtools") - devtools::install_github("tidylab/R6P") +``` r +# install.packages("devtools") +devtools::install_github("tidylab/R6P") +``` ## References +
+ +
+ Fowler, Martin. 2002. *Patterns of enterprise application architecture*. Addison-Wesley Longman Publishing Co., Inc. +
+ +
+ Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. 1995. *Design patterns: elements of reusable object-oriented software*. Pearson Education India. +
+ +
+ Ousterhout, John. 2018. *A Philosophy of Software Design*. Yaknyam Press. + +
+ +
diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index bd6406a..0d28e4c 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -5,16 +5,31 @@ authors: href: https://github.com/harell Tidylab: href: "https://github.com/tidylab" - html: "" + html: "TidyLab Homepage" + aria-label: Tidylab Logo + template: bootstrap: 5 params: docsearch: api_key: d5f049d51d6123dd767e5f8bc7882c35 index_name: tidylab-r6p +cran: + icon: fab fa-r-project + href: https://cloud.r-project.org/package=pkgdown + aria-label: View on CRAN # Articles ---------------------------------------------------------------- articles: +- title: internal + contents: + - article_template + - appendices/Dictionary + - details/NullObject + - details/Repository + - details/Singleton + - appendices/SpecialCase + - details/ValueObject - title: Base Patterns contents: - patterns/Singleton diff --git a/vignettes/patterns/NullObject.Rmd b/vignettes/patterns/NullObject.Rmd index 6e4cd8a..9d413d5 100644 --- a/vignettes/patterns/NullObject.Rmd +++ b/vignettes/patterns/NullObject.Rmd @@ -16,7 +16,7 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child="../details/NullObject.Rmd"} +```{r child=file.path(usethis::proj_get(), "vignettes", "details", "NullObject.Rmd")} ``` ## Example: Null ggplot2 diff --git a/vignettes/patterns/Repository.Rmd b/vignettes/patterns/Repository.Rmd index 9edc4e5..04c5cca 100644 --- a/vignettes/patterns/Repository.Rmd +++ b/vignettes/patterns/Repository.Rmd @@ -16,7 +16,8 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child="../details/Repository.Rmd"} +```{r child=file.path(usethis::proj_get(), "vignettes", "details", "Repository.Rmd")} + ``` ## Implementations diff --git a/vignettes/patterns/Singleton.Rmd b/vignettes/patterns/Singleton.Rmd index f2e11e0..b27a8a9 100644 --- a/vignettes/patterns/Singleton.Rmd +++ b/vignettes/patterns/Singleton.Rmd @@ -16,7 +16,8 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child="../details/Singleton.Rmd"} +```{r child=file.path(usethis::proj_get(), "vignettes", "details", "Singleton.Rmd")} + ``` ## Example: Counter {#example-1} diff --git a/vignettes/patterns/ValueObject.Rmd b/vignettes/patterns/ValueObject.Rmd index 2436d1d..8f6096c 100644 --- a/vignettes/patterns/ValueObject.Rmd +++ b/vignettes/patterns/ValueObject.Rmd @@ -16,7 +16,8 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child="../details/ValueObject.Rmd"} +```{r child=file.path(usethis::proj_get(), "vignettes", "details", "ValueObject.Rmd")} + ``` From 3bce3209e75f28891441a34d7db280a68ae70ebe Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 22:44:58 +1300 Subject: [PATCH 21/40] addressing missing documentation warning --- R/base-ValueObject.R | 1 + R/object_relational-Repository.R | 4 +++- pkgdown/_pkgdown.yml | 5 +---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/R/base-ValueObject.R b/R/base-ValueObject.R index 3e2506c..bff64f7 100644 --- a/R/base-ValueObject.R +++ b/R/base-ValueObject.R @@ -1,3 +1,4 @@ +#' @name ValueObject #' @title Value Object Pattern #' @includeRmd vignettes/details/ValueObject.Rmd #' @description Model a domain concept using natural lingo of the domain diff --git a/R/object_relational-Repository.R b/R/object_relational-Repository.R index 6e09189..f398f32 100644 --- a/R/object_relational-Repository.R +++ b/R/object_relational-Repository.R @@ -65,7 +65,9 @@ #' repository$get(key = "Mazda RX4") NULL -#' @rdname Repository +#' @name AbstractRepository +#' @title AbstractRepository +#' @aliases Repository #' @description Mediates between the domain and data mapping layers using a #' collection-like interface for accessing domain objects. #' @param key (`character`) Name of the element. diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 0d28e4c..9398684 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -14,10 +14,7 @@ template: docsearch: api_key: d5f049d51d6123dd767e5f8bc7882c35 index_name: tidylab-r6p -cran: - icon: fab fa-r-project - href: https://cloud.r-project.org/package=pkgdown - aria-label: View on CRAN + # Articles ---------------------------------------------------------------- articles: From dc6ac273ef585efb9822d3cc7656ff4c9cdfcec9 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 22:48:31 +1300 Subject: [PATCH 22/40] remove warning --- R/object_relational-Repository.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/object_relational-Repository.R b/R/object_relational-Repository.R index f398f32..8e76670 100644 --- a/R/object_relational-Repository.R +++ b/R/object_relational-Repository.R @@ -67,7 +67,6 @@ NULL #' @name AbstractRepository #' @title AbstractRepository -#' @aliases Repository #' @description Mediates between the domain and data mapping layers using a #' collection-like interface for accessing domain objects. #' @param key (`character`) Name of the element. From 4f6636fcb6d784ec3da10250a09726dd4194e7c5 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 22:55:10 +1300 Subject: [PATCH 23/40] build package website --- .github/workflows/pkgdown.yaml | 53 +++++++++++++++++++++++++++ .github/workflows/pkgdown.yml | 65 ---------------------------------- 2 files changed, 53 insertions(+), 65 deletions(-) create mode 100644 .github/workflows/pkgdown.yaml delete mode 100644 .github/workflows/pkgdown.yml diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 0000000..8ba25c5 --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,53 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: #[main, master] + pull_request: + release: + types: [published] + workflow_dispatch: + +name: pkgdown + +permissions: read-all + +jobs: + pkgdown: + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + steps: + - name: Remove .Rprofile + # Delete .Rprofile if present. + run: rm -f ./.Rprofile + + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.github/workflows/pkgdown.yml b/.github/workflows/pkgdown.yml deleted file mode 100644 index 85c1cdd..0000000 --- a/.github/workflows/pkgdown.yml +++ /dev/null @@ -1,65 +0,0 @@ -on: - push: - branches: - - master - - release/* - -name: pkgdown - -jobs: - pkgdown: - runs-on: macOS-latest - env: - GITHUB_PAT: ${{ secrets.PKGDOWN_PAT }} - _R_S3_METHOD_REGISTRATION_NOTE_OVERWRITES_: false - steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@master - - - uses: r-lib/actions/setup-pandoc@master - - - uses: r-lib/actions/setup-tinytex@v1 - - - name: Install system dependencies - run: | - brew install harfbuzz fribidi libgit2 - rm .Rprofile - - - name: Prepare - run: | - echo "utils::chooseCRANmirror(graphics=FALSE, ind = 1)" > .Rprofile - Rscript -e "if(!'remotes' %in% rownames(utils::installed.packages())) utils::install.packages('remotes')" - - - name: Query dependencies - run: | - pkgs <- remotes::dev_package_deps(dependencies = TRUE); print(pkgs[order(pkgs$package),]) - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} - - - name: Cache R packages - uses: actions/cache@v1 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install dependencies - run: | - sapply(c("tidyverse", "devtools", "pkgdown", "covr"), function(x) try(remotes::install_cran(x))) - remotes::update_packages("pkgdown") - remotes::install_deps(dependencies = TRUE) - shell: Rscript {0} - - - name: Install package - run: R CMD INSTALL . - - - name: Deploy website - run: | - git config --local user.email "actions@github.com" - git config --local user.name "GitHub Actions" - Rscript -e 'devtools::document()' - Rscript -e 'rmarkdown::render("README.Rmd", "md_document")' - Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' - Rscript -e 'covr::codecov(type = "all")' From 55bd739a38b291d063ef6419ab59cf82330d0387 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 23:08:40 +1300 Subject: [PATCH 24/40] update singleton doc --- R/base-Singleton.R | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/R/base-Singleton.R b/R/base-Singleton.R index 9372bc5..e9328ff 100644 --- a/R/base-Singleton.R +++ b/R/base-Singleton.R @@ -1,6 +1,10 @@ #' @title Singleton Pattern #' @name Singleton +#' @aliases Singleton #' @includeRmd vignettes/details/Singleton.Rmd +#' @description Ensure a class only has one instance, and provide a global point of access to it. +#' @family base design patterns +#' @export #' @examples #' # See more examples at #' address <- function(x) sub('', '\\1', capture.output(x)) @@ -10,7 +14,7 @@ #' Counter <- R6::R6Class("Counter", inherit = R6P::Singleton, public = list( #' count = 0, #' add_1 = function(){self$count = self$count + 1; invisible(self)} -#' )) +#' )) #' #' # Whenever we call the constructor on `Counter`, we always get the exact same #' # instance: @@ -36,11 +40,6 @@ #' counter_B$count NULL -#' @rdname Singleton -#' @description Ensure a class only has one instance, and provide a global point -#' of access to it. -#' @family base design patterns -#' @export Singleton <- R6::R6Class("Singleton", cloneable = FALSE, public = list( #' @description Create or retrieve an object initialize = function(){ From 7fc0f21273ad747559d7bb0f3e4d92e33fa18125 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 23:23:54 +1300 Subject: [PATCH 25/40] update file names --- R/base-NullObject.R | 2 +- R/base-Singleton.R | 2 +- R/base-ValueObject.R | 2 +- R/object_relational-Repository.R | 2 +- R/utils-DockerCompose.R | 112 ----- pkgdown/_pkgdown.yml | 5 - ...cle_template.Rmd => _article_template.Rmd} | 110 ++--- .../{NullObject.Rmd => _NullObject.Rmd} | 210 +++++----- .../{Repository.Rmd => _Repository.Rmd} | 104 ++--- .../details/{Singleton.Rmd => _Singleton.Rmd} | 86 ++-- .../{ValueObject.Rmd => _ValueObject.Rmd} | 394 +++++++++--------- vignettes/patterns/NullObject.Rmd | 2 +- vignettes/patterns/Singleton.Rmd | 2 +- vignettes/patterns/ValueObject.Rmd | 2 +- 14 files changed, 459 insertions(+), 576 deletions(-) delete mode 100644 R/utils-DockerCompose.R rename vignettes/{article_template.Rmd => _article_template.Rmd} (93%) rename vignettes/details/{NullObject.Rmd => _NullObject.Rmd} (97%) rename vignettes/details/{Repository.Rmd => _Repository.Rmd} (97%) rename vignettes/details/{Singleton.Rmd => _Singleton.Rmd} (96%) rename vignettes/details/{ValueObject.Rmd => _ValueObject.Rmd} (97%) diff --git a/R/base-NullObject.R b/R/base-NullObject.R index 3f2d9b0..5997438 100644 --- a/R/base-NullObject.R +++ b/R/base-NullObject.R @@ -1,5 +1,5 @@ #' @title Null Object Pattern -#' @includeRmd vignettes/details/NullObject.Rmd +#' @includeRmd vignettes/details/_NullObject.Rmd #' @description Model a domain concept using natural lingo of the domain #' experts, such as “Passenger”, “Address”, and “Money”. #' @family base design patterns diff --git a/R/base-Singleton.R b/R/base-Singleton.R index e9328ff..425c468 100644 --- a/R/base-Singleton.R +++ b/R/base-Singleton.R @@ -1,7 +1,7 @@ #' @title Singleton Pattern #' @name Singleton #' @aliases Singleton -#' @includeRmd vignettes/details/Singleton.Rmd +#' @includeRmd vignettes/details/_Singleton.Rmd #' @description Ensure a class only has one instance, and provide a global point of access to it. #' @family base design patterns #' @export diff --git a/R/base-ValueObject.R b/R/base-ValueObject.R index bff64f7..09d963a 100644 --- a/R/base-ValueObject.R +++ b/R/base-ValueObject.R @@ -1,6 +1,6 @@ #' @name ValueObject #' @title Value Object Pattern -#' @includeRmd vignettes/details/ValueObject.Rmd +#' @includeRmd vignettes/details/_ValueObject.Rmd #' @description Model a domain concept using natural lingo of the domain #' experts, such as “Passenger”, “Address”, and “Money”. #' @param given (`character`) A character vector with the given name. diff --git a/R/object_relational-Repository.R b/R/object_relational-Repository.R index 8e76670..0738db8 100644 --- a/R/object_relational-Repository.R +++ b/R/object_relational-Repository.R @@ -1,6 +1,6 @@ #' @title Repository Pattern #' @name Repository -#' @includeRmd vignettes/details/Repository.Rmd +#' @includeRmd vignettes/details/_Repository.Rmd #' @examples #' # See more examples at #' diff --git a/R/utils-DockerCompose.R b/R/utils-DockerCompose.R deleted file mode 100644 index 00616ce..0000000 --- a/R/utils-DockerCompose.R +++ /dev/null @@ -1,112 +0,0 @@ -# DockerCompose ----------------------------------------------------------- -#' @title Use a docker-compose.yml File -#' @description -#' GIVEN a \code{docker-compose.yml}, -#' WEHN \code{DockerCompose} is instantiated, -#' THEN the resulting object gives access to Docker commands. -#' @param service (`character`) Service name in \code{docker-compose.yml}. -#' @param field (`character`) Field name in \code{docker-compose.yml}. -#' @param slug (`character`) URL slug (e.g. \code{shiny-app-name}). -#' @family docker -#' @export -DockerCompose <- R6::R6Class(# nocov start - classname = "DockerCompose", - cloneable = FALSE, - lock_objects = FALSE, - public = list( - # Public Methods ------------------------------------------------------- - #' @description - #' Initialize a DockerCompose object - #' @param path_docker_compose (`character`) Path to docker-compose file. - initialize = function(path_docker_compose = "./docker-compose.yml"){ - stopifnot(file.exists(path_docker_compose)) - private$path_docker_compose <- path_docker_compose - private$composition <- yaml::read_yaml(path_docker_compose, eval.expr = FALSE) - invisible(self) - }, - #' @description - #' Get a value from a service - #' @examples \donttest{\dontrun{DockerCompose$new()$get("shinyserver", "ports")}} - get = function(service, field) DockerCompose$funs$get(self, private, service, field), - #' @description - #' Create and start containers. - start = function(service = NULL) DockerCompose$funs$start(self, private, service), - #' @description - #' Stop containers. - stop = function() DockerCompose$funs$stop(self, private), - #' @description - #' Restart containers. - restart = function(service = NULL) DockerCompose$funs$restart(self, private, service), - #' @description - #' Stop and remove containers, networks, images and volumes. - reset = function() DockerCompose$funs$reset(self, private), - #' @description - #' Load URL into an HTML Browser - browse_url = function(service, slug = "") DockerCompose$funs$browse_url(self, private, service, slug) - ),# end public - private = list( - path_docker_compose = c(), - composition = list() - ) -)# nocov end -DockerCompose$funs <- new.env() - -# Public Methods ---------------------------------------------------------- -DockerCompose$funs$reset <- function(self, private){ - system <- DockerCompose$funs$system - docker_commands <- c( - "docker-compose down", - "docker system prune -f", - "docker volume prune -f", - "docker network prune -f", - "docker rmi -f $(docker images -a -q)" - ) - sapply(docker_commands, function(x) try(system(x, wait = TRUE))) - invisible(self) -} - -DockerCompose$funs$restart <- function(self, private, service){ - system <- DockerCompose$funs$system - DockerCompose$funs$stop(self, private) - DockerCompose$funs$start(self, private, service) - invisible(self) -} - -DockerCompose$funs$start <- function(self, private, service){ - is.not.null <- Negate(is.null) - if(is.not.null(service)){ - service <- match.arg(service, names(private$composition$services), several.ok = TRUE) - } - - system <- DockerCompose$funs$system - docker_command <- stringr::str_glue("docker-compose up -d --build {services}", services = paste0(service, collapse = " ")) - system(docker_command, wait = TRUE) - invisible(self) -} - -DockerCompose$funs$stop <- function(self, private){ - system <- DockerCompose$funs$system - docker_command <- stringr::str_glue("docker-compose down") - system(docker_command, wait = TRUE) - invisible(self) -} - -DockerCompose$funs$browse_url <- function(self, private, service, slug){ - service <- match.arg(service, names(private$composition$services)) - url <- "localhost" - port <- stringr::str_remove(self$get(service, "ports"), ":.*") - if(length(port) == 0) port <- "8080" - address <- stringr::str_glue("http://{url}:{port}/{slug}", url = "localhost", port = port, slug = slug) - try(browseURL(utils::URLencode(address))) - return(self) -} - -DockerCompose$funs$get <- function(self, private, service, field){ - stopifnot(!missing(field)) - service <- match.arg(service, names(private$composition$services)) - private$composition$services[[service]][[field]] -} - -# Helpers ----------------------------------------------------------------- -DockerCompose$funs$system <- function(command, ...){ message("\033[43m\033[44m",command,"\033[43m\033[49m") ; base::system(command, ...) } -DockerCompose$funs$escape_character <- function(x){ if(is.character(x)) paste0('"', x, '"') else x } diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 9398684..4b07fc9 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -20,13 +20,8 @@ template: articles: - title: internal contents: - - article_template - appendices/Dictionary - - details/NullObject - - details/Repository - - details/Singleton - appendices/SpecialCase - - details/ValueObject - title: Base Patterns contents: - patterns/Singleton diff --git a/vignettes/article_template.Rmd b/vignettes/_article_template.Rmd similarity index 93% rename from vignettes/article_template.Rmd rename to vignettes/_article_template.Rmd index ad00b8f..d5c8ec2 100644 --- a/vignettes/article_template.Rmd +++ b/vignettes/_article_template.Rmd @@ -1,55 +1,55 @@ ---- -title: "Article Template" -bibliography: [../inst/REFERENCES.bib] -biblio-style: apalike -link-citations: yes -nocite: | - @Gamma1995 - @Fowler2002 -editor_options: - markdown: - wrap: 80 ---- - -```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -``` - - - -Mediates between the domain and data mapping layers using a collection-like -interface for accessing domain objects. - - - - - -## How It Works - -text - -## When to Use It - -text - -## Example: Name of Example 1 - -text - -## Example: Name of Example 2 - -text - -## Further Reading - -```{r, message=TRUE} -message("Signifies a tip or suggestion") -``` - -```{r, warning=TRUE} -warning("Signifies a general note") -``` - -```{r, error=TRUE} -stop("Signifies a warning or caution") -``` +--- +title: "Article Template" +bibliography: [../inst/REFERENCES.bib] +biblio-style: apalike +link-citations: yes +nocite: | + @Gamma1995 + @Fowler2002 +editor_options: + markdown: + wrap: 80 +--- + +```{r, include = FALSE} +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +``` + + + +Mediates between the domain and data mapping layers using a collection-like +interface for accessing domain objects. + + + + + +## How It Works + +text + +## When to Use It + +text + +## Example: Name of Example 1 + +text + +## Example: Name of Example 2 + +text + +## Further Reading + +```{r, message=TRUE} +message("Signifies a tip or suggestion") +``` + +```{r, warning=TRUE} +warning("Signifies a general note") +``` + +```{r, error=TRUE} +stop("Signifies a warning or caution") +``` diff --git a/vignettes/details/NullObject.Rmd b/vignettes/details/_NullObject.Rmd similarity index 97% rename from vignettes/details/NullObject.Rmd rename to vignettes/details/_NullObject.Rmd index 03d8052..3773bb0 100644 --- a/vignettes/details/NullObject.Rmd +++ b/vignettes/details/_NullObject.Rmd @@ -1,105 +1,105 @@ ---- -title: "The Null Object Pattern" -editor_options: - markdown: - wrap: 80 ---- - -```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -``` - -```{r, error=TRUE} -events$stop_not_useful("NullObject") -``` - - - -***Null Object*** provides special behaviour for particular cases. - -```{r warning=TRUE} -warning("The Null Object is not the same as the reserved word in R `NULL` (all caps).") -``` - -## How It Works - -When a function fails in R, some functions produce a run-time error while others -return `NULL` (and potentially prompt a warning). What the function evokes in -case of a failure is subjected to its programmer discretion. Usually, the -programmer follows either a punitive or forgiving policy regarding how -run-time errors should be handled. - -In other occasions, `NULL` is often the result of unavailable data. This could -happened when querying a data source matches no entries, or when the system is -waiting for user input (mainly in Shiny). - -If it is possible for a function to return `NULL` rather than an error, -then it is important to surround it with null test code, e.g. -`if(is.null(...)) do_the_right_thing()`. This way the software would do the -right thing if a null is present. - -Often the right thing is the same in many contexts, so you end up writing -similar code in lots of places—committing the sin of code duplication. - -Instead of returning `NULL`, or some odd value such as `NaN` or `logical(0)`, return a **Null Object** that has the same interface as what the caller expects. In R, this often means returning a `data.frame` structure, i.e. column names and variables types, with no rows. - - -## When to Use It - -- In situations when a subroutine is likely to fail, such as loss of Internet - or database connectivity. Instead of prompting a run-time error, you could - return the **Null Object** as part of a [gracefully - failing](https://en.wikipedia.org/wiki/Graceful_exit) strategy. A common - strategy employs `tryCatch` that returns the **Null Object** in the case of - an error: - -```{r, echo = TRUE, eval = TRUE} -# Simulate a database that is 5% likely to fail -read_mtcars <- function() if(runif(1) < 0.05) stop() else return(mtcars) - -# mtcars null object constructor -NullCar <- function() mtcars[0,] - -# How does the null car object look like? -NullCar() - -# Subroutine with gracefully failing strategy -set.seed(1814) -cars <- tryCatch( - # Try reading the mtcars dataset - read_mtcars(), - # If there is an error, return the Null Car object - error = function(e) return(NullCar()) -) - -# Notice: Whether the subroutine fails or succeeds, it returns a tibble with -# the same structure. -colnames(cars) -``` - -- In Shiny dashboards - -```{r, echo = TRUE, eval = FALSE} -geom_null <- function(...){ - ggplot2::ggplot() + ggplot2::geom_blank() + ggplot2::theme_void() -} - -if(exists("user_input")){ - ggplot2::ggplot(user_input, ggplot::aes(x = mpg, y = hp)) + ggplot2::geom_point() -} else { - geom_null() + geom_text(aes(0,0), label = "choose an entry from the list") -} -``` - -- In unit-tests - -```{r, echo = TRUE, eval = FALSE} -classes <- function(x) sapply(x, class) - -test_that("mtcars follows a certain table structure", { - # Compare column names - expect_identical(colnames(mtcars), colnames(NullCar())) - # Compare variable types - expect_identical(classes(mtcars), classes(NullCar())) -}) -``` +--- +title: "The Null Object Pattern" +editor_options: + markdown: + wrap: 80 +--- + +```{r, include = FALSE} +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +``` + +```{r, error=TRUE} +events$stop_not_useful("NullObject") +``` + + + +***Null Object*** provides special behaviour for particular cases. + +```{r warning=TRUE} +warning("The Null Object is not the same as the reserved word in R `NULL` (all caps).") +``` + +## How It Works + +When a function fails in R, some functions produce a run-time error while others +return `NULL` (and potentially prompt a warning). What the function evokes in +case of a failure is subjected to its programmer discretion. Usually, the +programmer follows either a punitive or forgiving policy regarding how +run-time errors should be handled. + +In other occasions, `NULL` is often the result of unavailable data. This could +happened when querying a data source matches no entries, or when the system is +waiting for user input (mainly in Shiny). + +If it is possible for a function to return `NULL` rather than an error, +then it is important to surround it with null test code, e.g. +`if(is.null(...)) do_the_right_thing()`. This way the software would do the +right thing if a null is present. + +Often the right thing is the same in many contexts, so you end up writing +similar code in lots of places—committing the sin of code duplication. + +Instead of returning `NULL`, or some odd value such as `NaN` or `logical(0)`, return a **Null Object** that has the same interface as what the caller expects. In R, this often means returning a `data.frame` structure, i.e. column names and variables types, with no rows. + + +## When to Use It + +- In situations when a subroutine is likely to fail, such as loss of Internet + or database connectivity. Instead of prompting a run-time error, you could + return the **Null Object** as part of a [gracefully + failing](https://en.wikipedia.org/wiki/Graceful_exit) strategy. A common + strategy employs `tryCatch` that returns the **Null Object** in the case of + an error: + +```{r, echo = TRUE, eval = TRUE} +# Simulate a database that is 5% likely to fail +read_mtcars <- function() if(runif(1) < 0.05) stop() else return(mtcars) + +# mtcars null object constructor +NullCar <- function() mtcars[0,] + +# How does the null car object look like? +NullCar() + +# Subroutine with gracefully failing strategy +set.seed(1814) +cars <- tryCatch( + # Try reading the mtcars dataset + read_mtcars(), + # If there is an error, return the Null Car object + error = function(e) return(NullCar()) +) + +# Notice: Whether the subroutine fails or succeeds, it returns a tibble with +# the same structure. +colnames(cars) +``` + +- In Shiny dashboards + +```{r, echo = TRUE, eval = FALSE} +geom_null <- function(...){ + ggplot2::ggplot() + ggplot2::geom_blank() + ggplot2::theme_void() +} + +if(exists("user_input")){ + ggplot2::ggplot(user_input, ggplot::aes(x = mpg, y = hp)) + ggplot2::geom_point() +} else { + geom_null() + geom_text(aes(0,0), label = "choose an entry from the list") +} +``` + +- In unit-tests + +```{r, echo = TRUE, eval = FALSE} +classes <- function(x) sapply(x, class) + +test_that("mtcars follows a certain table structure", { + # Compare column names + expect_identical(colnames(mtcars), colnames(NullCar())) + # Compare variable types + expect_identical(classes(mtcars), classes(NullCar())) +}) +``` diff --git a/vignettes/details/Repository.Rmd b/vignettes/details/_Repository.Rmd similarity index 97% rename from vignettes/details/Repository.Rmd rename to vignettes/details/_Repository.Rmd index 769dcaf..15d2de4 100644 --- a/vignettes/details/Repository.Rmd +++ b/vignettes/details/_Repository.Rmd @@ -1,52 +1,52 @@ ---- -title: "The Repository Pattern" -editor_options: - markdown: - wrap: 80 ---- - -```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -``` - - - -With ***Repository***, in-memory objects do not need to know whether there is a -database present or absent, they need no SQL interface code, and certainly no -knowledge of the database schema. - -## How It Works - -- **Repository** isolates domain objects from details of the database access - code; -- **Repository** concentrates code of query construction; and -- **Repository** helps to minimize duplicate query logic. - -In R, the simplest form of **Repository** encapsulates `data.frame` entries -persisted in a data store and the operations performed over them, providing a -more object-oriented view of the persistence layer. From the caller point of -view, the location (locally or remotely), the technology and the interface of -the data store are obscured. - -## When to Use It - -- In situations with multiple data sources. - -- In situations where the real data store, the one that is used in production, - is remote. This allows you to implement a **Repository** mock with identical - queries that runs locally. Then, the mock could be used during development - and testing. The mock itself may comprise a sample of the real data store or - just fake data. - -- In situations where the real data store doesn't exist. Implementing a mock - **Repository** allows you to defer immature decisions about the database - technology and/or defer its deployment. In this way, the temporary solution - allows you to focus the development effort on the core functionality of the - application. - -- In situations where using SQL queries can be represented by meaningful - names. For example - `Repository$get_efficient_cars() = SELECT * FROM mtcars WHERE mpg > 20` - -- When building [stateless - microservices](https://www.oreilly.com/library/view/software-architects-handbook/9781788624060/c47a09b6-91f9-4322-a6d4-9bc1604b1bdf.xhtml). +--- +title: "The Repository Pattern" +editor_options: + markdown: + wrap: 80 +--- + +```{r, include = FALSE} +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +``` + + + +With ***Repository***, in-memory objects do not need to know whether there is a +database present or absent, they need no SQL interface code, and certainly no +knowledge of the database schema. + +## How It Works + +- **Repository** isolates domain objects from details of the database access + code; +- **Repository** concentrates code of query construction; and +- **Repository** helps to minimize duplicate query logic. + +In R, the simplest form of **Repository** encapsulates `data.frame` entries +persisted in a data store and the operations performed over them, providing a +more object-oriented view of the persistence layer. From the caller point of +view, the location (locally or remotely), the technology and the interface of +the data store are obscured. + +## When to Use It + +- In situations with multiple data sources. + +- In situations where the real data store, the one that is used in production, + is remote. This allows you to implement a **Repository** mock with identical + queries that runs locally. Then, the mock could be used during development + and testing. The mock itself may comprise a sample of the real data store or + just fake data. + +- In situations where the real data store doesn't exist. Implementing a mock + **Repository** allows you to defer immature decisions about the database + technology and/or defer its deployment. In this way, the temporary solution + allows you to focus the development effort on the core functionality of the + application. + +- In situations where using SQL queries can be represented by meaningful + names. For example + `Repository$get_efficient_cars() = SELECT * FROM mtcars WHERE mpg > 20` + +- When building [stateless + microservices](https://www.oreilly.com/library/view/software-architects-handbook/9781788624060/c47a09b6-91f9-4322-a6d4-9bc1604b1bdf.xhtml). diff --git a/vignettes/details/Singleton.Rmd b/vignettes/details/_Singleton.Rmd similarity index 96% rename from vignettes/details/Singleton.Rmd rename to vignettes/details/_Singleton.Rmd index 82a5b60..4bb77b7 100644 --- a/vignettes/details/Singleton.Rmd +++ b/vignettes/details/_Singleton.Rmd @@ -1,43 +1,43 @@ ---- -title: "The Singleton Pattern" -editor_options: - markdown: - wrap: 80 ---- - -```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -``` - - - -***Singleton*** ensures a class only has one instance, and provide a global -point of access to it. - -## How It Works - -1. Create only one instance of the **Singleton** class; and -2. If an instance exists, then serve the same object again. - -The main features of **Singleton** are: - -- Ensuring that one and only one object of the class gets created; - -- Providing an access point for an object that is global to the program; and - -- Controlling concurrent access to resources that are shared. - -## When to Use It - -- In situations that require exactly one instance of a class, that must be - accessible to clients from a well-known access point. See the [`Counter` - example](#example-1). - -```{r, error=TRUE} -stop("Singletons can be a problem in multi-threaded applications, especially when they manipulate mutable data.") -``` - -```{r, message=TRUE} -message("Singletons work well for immutable data, such as reading from some data source, since anything that can’t change isn’t going to run into thread clash problems.") -``` - +--- +title: "The Singleton Pattern" +editor_options: + markdown: + wrap: 80 +--- + +```{r, include = FALSE} +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +``` + + + +***Singleton*** ensures a class only has one instance, and provide a global +point of access to it. + +## How It Works + +1. Create only one instance of the **Singleton** class; and +2. If an instance exists, then serve the same object again. + +The main features of **Singleton** are: + +- Ensuring that one and only one object of the class gets created; + +- Providing an access point for an object that is global to the program; and + +- Controlling concurrent access to resources that are shared. + +## When to Use It + +- In situations that require exactly one instance of a class, that must be + accessible to clients from a well-known access point. See the [`Counter` + example](#example-1). + +```{r, error=TRUE} +stop("Singletons can be a problem in multi-threaded applications, especially when they manipulate mutable data.") +``` + +```{r, message=TRUE} +message("Singletons work well for immutable data, such as reading from some data source, since anything that can’t change isn’t going to run into thread clash problems.") +``` + diff --git a/vignettes/details/ValueObject.Rmd b/vignettes/details/_ValueObject.Rmd similarity index 97% rename from vignettes/details/ValueObject.Rmd rename to vignettes/details/_ValueObject.Rmd index a89944d..69aa3df 100644 --- a/vignettes/details/ValueObject.Rmd +++ b/vignettes/details/_ValueObject.Rmd @@ -1,197 +1,197 @@ ---- -title: "The Value Object Pattern" -editor_options: - markdown: - wrap: 80 ---- - -```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -NA_POSIXct_ <- .POSIXct(NA_real_, tz = "UTC") -is.POSIXct <- function(x) inherits(x, "POSIXct") -``` - -```{r, error=TRUE} -events$stop_not_useful("ValueObject") -``` - - - -A **Value Object** models a domain concept using natural lingo of the domain -experts, such as "Passenger", "Address", and "Money". - -Any **Value Object** is created by a function that receives input, applies some -transformations, and outputs the results in some data structure such as a -vector, a list or a data.frame. - -## How It Works - -In R, a good option for creating a **Value Object** is to follow two -instructions: - -- A **Value Object** is created by a `function`, rather than a class method; - and -- A **Value Object** returns a `tibble`, rather than a list or a vector. - -In essence, a **Value Object** is a data type, like `integer`, `logical`, `Date` -or `data.frame` data types to name a few. While the built-in data types in R fit -any application, **Value Objects** are domain specific and as such, they fit -only to a specific application. This is because, `integer` is an abstract that -represent whole numbers. This abstract is useful in any application. However, a -**Value Object** represent a high-level abstraction that appears in a particular -domain. - -An example of a **Value Object** is the notion of a "Person". Any person in the -world has a name. Needless to say, a person name is spelt by letters, rather -than numbers. A **Value Object** captures these attribute as `tibble` columns -and type checks: - -```{r, echo=TRUE} -Person <- function(given = NA_character_, family = NA_character_){ - stopifnot(is.character(given), is.character(family)) - stopifnot(length(given) == length(family)) - - return( - tibble::tibble(given = given, family = family) - |> tidyr::drop_na(given) - ) -} -``` - -Instantiating a person **Value Object** is done by calling the `Person` -constructor function: - -```{r, echo=TRUE} -person <- Person(given = "Bilbo", family = "Baggins") -``` - -Getting to know the advantages of a **Value Object**, we should consider the -typical alternative -- constructing a Person by using the `tibble` function -directly: - -```{r, echo=TRUE} -person <- tibble::tibble(given = "Bilbo", family = "Baggins") -``` - -Both implementations return objects with identical content and structure, that -is, their column names, column types and cell values are identical. Then, why -would one prefer using a **Value Object** and its constructor over the direct -alternative? - -There are four predominant qualities offered by the **Value Object** pattern -which are not offered by the alternative: - -1. Readability. Each **Value Object** captures a concept belonging to the - problem domain. Rather than trying to infer what a `tibble` is by looking at - its low-level details, the **Value Object** constructor descries a context - on a high-level. - -2. Explicitness. Since the constructor of the **Value Object** is a function, - its expected input arguments and their type can be detailed in a helper - file. Moreover, assigning input arguments with default values of specific - type, such as `NA` (logical NA), `NA_integer_`, `NA_character_`, or - `NA_Date` (see `lubridate::NA_Date`), expresses clearly the variable types - of the **Value Object**. - -3. Coherence. The representation of a **Value Object** is concentrated in one - place -- its constructor. Any change, mainly modifications and extensions, - applied to the constructor promise the change would propagate to all - instances of the Value Objects. That means, no structure discrepancies - between instances that are supposed to represent the same concept. - -4. Safety. The constructor may start with [defensive - programming](https://en.wikipedia.org/wiki/Defensive_programming) to ensure - the qualities of its input. One important assertion is type checking. Type - checking eliminated the risk of implicit type coercing. Another important - assertion is checking if the lengths of the input arguments meet some - criteria, say all inputs are of the same length, or more restrictively, all - inputs are scalars. Having a set of checks makes the code base more robust. - This is because **Value Objects** are regularly created with the output of - other functions calls, having a set of checks serves as pseudo-tests of - these functions output throughout the code. - -In addition to these qualities, there are two desirable behaviours which are not -offered by directly calling `tibble`: - -1. Null Value Object. Calling the **Value Object** constructor with no input - arguments returns the structure of the `tibble` (column names and column - types). - -2. Default values for missing input arguments. In this manner, the **Value - Object** has a well-defined behaviour for a person without a family name, - such as Madonna and Bono. - -In addition to native R data types, a **Value Object** constructor can receive -other **Value Objects** as input arguments. Here are two examples that transmute -Person to other Person-based concepts: - -```{r, echo = TRUE, eval = TRUE} -# A Passenger is a Person with a flight booking reference -Passenger <- function(person = Person(), booking_reference = NA_character_){ - stopifnot(all(colnames(person) %in% colnames(Person()))) - stopifnot(is.character(booking_reference)) - - return( - person - |> tibble::add_column(booking_reference = booking_reference) - |> tidyr::drop_na(booking_reference) - ) -} - -person <- Person(given = "Bilbo", family = "Baggins") -passenger <- Passenger(person = person, booking_reference = "B662HR") -print(passenger) -``` - -```{r, echo = TRUE, eval = TRUE} -# A Diner is a Person that may have dinner reservation -Diner <- function(person = Person(), reservation_time = NA_POSIXct_){ - stopifnot(all(colnames(person) %in% colnames(Person()))) - stopifnot(is.POSIXct(reservation_time)) - - return( - person - |> tibble::add_column(reservation_time = reservation_time) - ) -} - -person <- Person(given = "Bilbo", family = "Baggins") -timestamp <- as.POSIXct("2021-01-23 18:00:00 NZDT") -diner <- Diner(person = person, reservation_time = timestamp) -print(diner) -``` - -## When to Use It - -- In situations where domain concepts are more important then the database - schema. For example, when you are modelling Passengers, your first instinct - might be to think about the different data sources you'd need for the - analysis. You may envision "FlightDetails" and "CustomerDetails". Next you - will define the relationship between them. Instead, let the domain drive the - design. Create a Passenger **Value Object** with the attributes you must - have, regardless of any particular database schema. - -- In a function that runs within a specific context. Rather than having an - input argument called `data` of type `data.frame`, use the appropriate - **Value Object** name and pass it its constructor. - -```{r, echo = TRUE, eval = FALSE} -Audience <- Person - -## Without a Value Object -clean_audience_data <- function(data) - dplyr::mutate(.data = data, given = stringr::str_to_title(given)) - -## With a Value Object -clean_audience_data <- function(attendees = Audience()) - dplyr::mutate(.data = attendees, given = stringr::str_to_title(given)) -``` - -- In [pipes and filters](https://en.wikipedia.org/wiki/Pipeline_(software)) - architecture. - -```{r, warning=TRUE} -warning("**Value Objects** do not need to have unit-tests. This is because of two reasons: -(1) **Value Objects** are often called by other functions that are being tested. That means, **Value Objects** are implicitly tested. -(2) **Value Objects** are data types similarly to 'data.frame' or 'list'. As such, they need no testing") -``` +--- +title: "The Value Object Pattern" +editor_options: + markdown: + wrap: 80 +--- + +```{r, include = FALSE} +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +NA_POSIXct_ <- .POSIXct(NA_real_, tz = "UTC") +is.POSIXct <- function(x) inherits(x, "POSIXct") +``` + +```{r, error=TRUE} +events$stop_not_useful("ValueObject") +``` + + + +A **Value Object** models a domain concept using natural lingo of the domain +experts, such as "Passenger", "Address", and "Money". + +Any **Value Object** is created by a function that receives input, applies some +transformations, and outputs the results in some data structure such as a +vector, a list or a data.frame. + +## How It Works + +In R, a good option for creating a **Value Object** is to follow two +instructions: + +- A **Value Object** is created by a `function`, rather than a class method; + and +- A **Value Object** returns a `tibble`, rather than a list or a vector. + +In essence, a **Value Object** is a data type, like `integer`, `logical`, `Date` +or `data.frame` data types to name a few. While the built-in data types in R fit +any application, **Value Objects** are domain specific and as such, they fit +only to a specific application. This is because, `integer` is an abstract that +represent whole numbers. This abstract is useful in any application. However, a +**Value Object** represent a high-level abstraction that appears in a particular +domain. + +An example of a **Value Object** is the notion of a "Person". Any person in the +world has a name. Needless to say, a person name is spelt by letters, rather +than numbers. A **Value Object** captures these attribute as `tibble` columns +and type checks: + +```{r, echo=TRUE} +Person <- function(given = NA_character_, family = NA_character_){ + stopifnot(is.character(given), is.character(family)) + stopifnot(length(given) == length(family)) + + return( + tibble::tibble(given = given, family = family) + |> tidyr::drop_na(given) + ) +} +``` + +Instantiating a person **Value Object** is done by calling the `Person` +constructor function: + +```{r, echo=TRUE} +person <- Person(given = "Bilbo", family = "Baggins") +``` + +Getting to know the advantages of a **Value Object**, we should consider the +typical alternative -- constructing a Person by using the `tibble` function +directly: + +```{r, echo=TRUE} +person <- tibble::tibble(given = "Bilbo", family = "Baggins") +``` + +Both implementations return objects with identical content and structure, that +is, their column names, column types and cell values are identical. Then, why +would one prefer using a **Value Object** and its constructor over the direct +alternative? + +There are four predominant qualities offered by the **Value Object** pattern +which are not offered by the alternative: + +1. Readability. Each **Value Object** captures a concept belonging to the + problem domain. Rather than trying to infer what a `tibble` is by looking at + its low-level details, the **Value Object** constructor descries a context + on a high-level. + +2. Explicitness. Since the constructor of the **Value Object** is a function, + its expected input arguments and their type can be detailed in a helper + file. Moreover, assigning input arguments with default values of specific + type, such as `NA` (logical NA), `NA_integer_`, `NA_character_`, or + `NA_Date` (see `lubridate::NA_Date`), expresses clearly the variable types + of the **Value Object**. + +3. Coherence. The representation of a **Value Object** is concentrated in one + place -- its constructor. Any change, mainly modifications and extensions, + applied to the constructor promise the change would propagate to all + instances of the Value Objects. That means, no structure discrepancies + between instances that are supposed to represent the same concept. + +4. Safety. The constructor may start with [defensive + programming](https://en.wikipedia.org/wiki/Defensive_programming) to ensure + the qualities of its input. One important assertion is type checking. Type + checking eliminated the risk of implicit type coercing. Another important + assertion is checking if the lengths of the input arguments meet some + criteria, say all inputs are of the same length, or more restrictively, all + inputs are scalars. Having a set of checks makes the code base more robust. + This is because **Value Objects** are regularly created with the output of + other functions calls, having a set of checks serves as pseudo-tests of + these functions output throughout the code. + +In addition to these qualities, there are two desirable behaviours which are not +offered by directly calling `tibble`: + +1. Null Value Object. Calling the **Value Object** constructor with no input + arguments returns the structure of the `tibble` (column names and column + types). + +2. Default values for missing input arguments. In this manner, the **Value + Object** has a well-defined behaviour for a person without a family name, + such as Madonna and Bono. + +In addition to native R data types, a **Value Object** constructor can receive +other **Value Objects** as input arguments. Here are two examples that transmute +Person to other Person-based concepts: + +```{r, echo = TRUE, eval = TRUE} +# A Passenger is a Person with a flight booking reference +Passenger <- function(person = Person(), booking_reference = NA_character_){ + stopifnot(all(colnames(person) %in% colnames(Person()))) + stopifnot(is.character(booking_reference)) + + return( + person + |> tibble::add_column(booking_reference = booking_reference) + |> tidyr::drop_na(booking_reference) + ) +} + +person <- Person(given = "Bilbo", family = "Baggins") +passenger <- Passenger(person = person, booking_reference = "B662HR") +print(passenger) +``` + +```{r, echo = TRUE, eval = TRUE} +# A Diner is a Person that may have dinner reservation +Diner <- function(person = Person(), reservation_time = NA_POSIXct_){ + stopifnot(all(colnames(person) %in% colnames(Person()))) + stopifnot(is.POSIXct(reservation_time)) + + return( + person + |> tibble::add_column(reservation_time = reservation_time) + ) +} + +person <- Person(given = "Bilbo", family = "Baggins") +timestamp <- as.POSIXct("2021-01-23 18:00:00 NZDT") +diner <- Diner(person = person, reservation_time = timestamp) +print(diner) +``` + +## When to Use It + +- In situations where domain concepts are more important then the database + schema. For example, when you are modelling Passengers, your first instinct + might be to think about the different data sources you'd need for the + analysis. You may envision "FlightDetails" and "CustomerDetails". Next you + will define the relationship between them. Instead, let the domain drive the + design. Create a Passenger **Value Object** with the attributes you must + have, regardless of any particular database schema. + +- In a function that runs within a specific context. Rather than having an + input argument called `data` of type `data.frame`, use the appropriate + **Value Object** name and pass it its constructor. + +```{r, echo = TRUE, eval = FALSE} +Audience <- Person + +## Without a Value Object +clean_audience_data <- function(data) + dplyr::mutate(.data = data, given = stringr::str_to_title(given)) + +## With a Value Object +clean_audience_data <- function(attendees = Audience()) + dplyr::mutate(.data = attendees, given = stringr::str_to_title(given)) +``` + +- In [pipes and filters](https://en.wikipedia.org/wiki/Pipeline_(software)) + architecture. + +```{r, warning=TRUE} +warning("**Value Objects** do not need to have unit-tests. This is because of two reasons: +(1) **Value Objects** are often called by other functions that are being tested. That means, **Value Objects** are implicitly tested. +(2) **Value Objects** are data types similarly to 'data.frame' or 'list'. As such, they need no testing") +``` diff --git a/vignettes/patterns/NullObject.Rmd b/vignettes/patterns/NullObject.Rmd index 9d413d5..5b21168 100644 --- a/vignettes/patterns/NullObject.Rmd +++ b/vignettes/patterns/NullObject.Rmd @@ -16,7 +16,7 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child=file.path(usethis::proj_get(), "vignettes", "details", "NullObject.Rmd")} +```{r child=file.path(usethis::proj_get(), "vignettes", "details", "_NullObject.Rmd")} ``` ## Example: Null ggplot2 diff --git a/vignettes/patterns/Singleton.Rmd b/vignettes/patterns/Singleton.Rmd index b27a8a9..67b0aa0 100644 --- a/vignettes/patterns/Singleton.Rmd +++ b/vignettes/patterns/Singleton.Rmd @@ -16,7 +16,7 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child=file.path(usethis::proj_get(), "vignettes", "details", "Singleton.Rmd")} +```{r child=file.path(usethis::proj_get(), "vignettes", "details", "_Singleton.Rmd")} ``` diff --git a/vignettes/patterns/ValueObject.Rmd b/vignettes/patterns/ValueObject.Rmd index 8f6096c..27123ed 100644 --- a/vignettes/patterns/ValueObject.Rmd +++ b/vignettes/patterns/ValueObject.Rmd @@ -16,7 +16,7 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child=file.path(usethis::proj_get(), "vignettes", "details", "ValueObject.Rmd")} +```{r child=file.path(usethis::proj_get(), "vignettes", "details", "_ValueObject.Rmd")} ``` From 09da45cde87a79a069db305d81d2287b2455ce25 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 23:42:38 +1300 Subject: [PATCH 26/40] updsate docus --- vignettes/details/_NullObject.Rmd | 16 +++++++++------- vignettes/details/_Singleton.Rmd | 1 - vignettes/patterns/NullObject.Rmd | 10 +++++----- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/vignettes/details/_NullObject.Rmd b/vignettes/details/_NullObject.Rmd index 3773bb0..add8ecd 100644 --- a/vignettes/details/_NullObject.Rmd +++ b/vignettes/details/_NullObject.Rmd @@ -26,23 +26,25 @@ warning("The Null Object is not the same as the reserved word in R `NULL` (all c When a function fails in R, some functions produce a run-time error while others return `NULL` (and potentially prompt a warning). What the function evokes in case of a failure is subjected to its programmer discretion. Usually, the -programmer follows either a punitive or forgiving policy regarding how -run-time errors should be handled. +programmer follows either a punitive or forgiving policy regarding how run-time +errors should be handled. In other occasions, `NULL` is often the result of unavailable data. This could happened when querying a data source matches no entries, or when the system is waiting for user input (mainly in Shiny). -If it is possible for a function to return `NULL` rather than an error, -then it is important to surround it with null test code, e.g. +If it is possible for a function to return `NULL` rather than an error, then it +is important to surround it with null test code, e.g. `if(is.null(...)) do_the_right_thing()`. This way the software would do the right thing if a null is present. Often the right thing is the same in many contexts, so you end up writing similar code in lots of places—committing the sin of code duplication. -Instead of returning `NULL`, or some odd value such as `NaN` or `logical(0)`, return a **Null Object** that has the same interface as what the caller expects. In R, this often means returning a `data.frame` structure, i.e. column names and variables types, with no rows. - +Instead of returning `NULL`, or some odd value such as `NaN` or `logical(0)`, +return a **Null Object** that has the same interface as what the caller expects. +In R, this often means returning a `data.frame` structure, i.e. column names and +variables types, with no rows. ## When to Use It @@ -50,7 +52,7 @@ Instead of returning `NULL`, or some odd value such as `NaN` or `logical(0)`, re or database connectivity. Instead of prompting a run-time error, you could return the **Null Object** as part of a [gracefully failing](https://en.wikipedia.org/wiki/Graceful_exit) strategy. A common - strategy employs `tryCatch` that returns the **Null Object** in the case of + strategy employs `tryCatch` that returns the **Null Object** in the case of an error: ```{r, echo = TRUE, eval = TRUE} diff --git a/vignettes/details/_Singleton.Rmd b/vignettes/details/_Singleton.Rmd index 4bb77b7..cfc444f 100644 --- a/vignettes/details/_Singleton.Rmd +++ b/vignettes/details/_Singleton.Rmd @@ -40,4 +40,3 @@ stop("Singletons can be a problem in multi-threaded applications, especially whe ```{r, message=TRUE} message("Singletons work well for immutable data, such as reading from some data source, since anything that can’t change isn’t going to run into thread clash problems.") ``` - diff --git a/vignettes/patterns/NullObject.Rmd b/vignettes/patterns/NullObject.Rmd index 5b21168..3346d52 100644 --- a/vignettes/patterns/NullObject.Rmd +++ b/vignettes/patterns/NullObject.Rmd @@ -21,14 +21,14 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ## Example: Null ggplot2 - ```{r, echo=TRUE, eval=FALSE} geom_null <- function(...){ ggplot2::ggplot() + ggplot2::geom_blank() + ggplot2::theme_void() } ``` -* Useful as part of a [gracefully failing](https://en.wikipedia.org/wiki/Graceful_exit) strategy. +- Useful as part of a [gracefully + failing](https://en.wikipedia.org/wiki/Graceful_exit) strategy. ```{r, echo=TRUE, eval=FALSE} fig <- @@ -45,9 +45,9 @@ fig <- plot(fig) ``` -* Useful in shiny dashboards when a visual is dependent on the user selection of -what to plot. In this case, you could also add a "call to action" text as a -ggplot object. +- Useful in shiny dashboards when a visual is dependent on the user selection + of what to plot. In this case, you could also add a "call to action" text as + a ggplot object. ```{r, echo=TRUE, eval=FALSE} if(exists("user_input")){ From a9e85312c5dbfc73074ff4910ef49d9f7a1c24f1 Mon Sep 17 00:00:00 2001 From: harell Date: Sat, 21 Dec 2024 23:44:05 +1300 Subject: [PATCH 27/40] update docs --- vignettes/patterns/Repository.Rmd | 22 +++++++++++++++------- vignettes/patterns/ValueObject.Rmd | 12 ++++++------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/vignettes/patterns/Repository.Rmd b/vignettes/patterns/Repository.Rmd index 04c5cca..26e7f7e 100644 --- a/vignettes/patterns/Repository.Rmd +++ b/vignettes/patterns/Repository.Rmd @@ -37,7 +37,6 @@ snippet <- snippet |> discard_comments() |> discard_null() |> discard_empty_line message("By passing the input argument `inherit = Singleton`, the `AbstractRepository` inherits the qualities of the [**Singleton** pattern](https://tidylab.github.io/R6P/articles/patterns/Singleton.html).") ``` - The given implementing of `AbstractRepository` requires you to define four functions: @@ -85,11 +84,16 @@ message("Transient implementations are a temporal solution that is good for test stop("Transient implementations are not recommended during the production stage. Transient storage is lost when a session is rebooted. You should think about what are the ramifications of losing all the data put into storage.") ``` -First, we define the class constructor, `initialize`, to establish a transient data storage. In this case we use a dictionary from the [`collections` package](https://randy3k.github.io/collections/reference/dict.html). +First, we define the class constructor, `initialize`, to establish a transient +data storage. In this case we use a dictionary from the [`collections` +package](https://randy3k.github.io/collections/reference/dict.html). -Second, we define the `add`, `del` and `get` functions that operate on the dictionary. +Second, we define the `add`, `del` and `get` functions that operate on the +dictionary. -As an optional step, we define the NULL object. In this case, rather then the reserved word `NULL`, the NULL object is a data.frame with 0 rows and predefined column. +As an optional step, we define the NULL object. In this case, rather then the +reserved word `NULL`, the NULL object is a data.frame with 0 rows and predefined +column. ```{r Repository-Transient-implementation, echo=TRUE, results='markup'} TransientRepository <- R6::R6Class( @@ -147,11 +151,15 @@ repository$get(key = "Mazda RX4") ### Example: Persistent Storage Implementation with [`DBI`](https://dbi.r-dbi.org/) -First, we define the class constructor, `initialize`, to establish an SQLite database. +First, we define the class constructor, `initialize`, to establish an SQLite +database. -Second, we define the `add`, `del` and `get` functions that operate on the dictionary. +Second, we define the `add`, `del` and `get` functions that operate on the +dictionary. -As an optional step, we define the NULL object. In this case, rather then the reserved word `NULL`, the NULL object is a data.frame with 0 rows and predefined column. +As an optional step, we define the NULL object. In this case, rather then the +reserved word `NULL`, the NULL object is a data.frame with 0 rows and predefined +column. ```{r Repository-Persistent-implementation, echo=TRUE} PersistentRepository <- R6::R6Class( diff --git a/vignettes/patterns/ValueObject.Rmd b/vignettes/patterns/ValueObject.Rmd index 27123ed..082fb63 100644 --- a/vignettes/patterns/ValueObject.Rmd +++ b/vignettes/patterns/ValueObject.Rmd @@ -25,15 +25,16 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ## Example: Person and Minister Value Objects In this example, we are appointing elected officials to random ministries, just -like in real life. +like in real life. The nomination process comprises three components: input, function, output. -* The input is the given and family names of a **Person** -* The function receives a **Person** value object and pairs it with ministry titles -* The output is a **Minister** value object +- The input is the given and family names of a **Person** +- The function receives a **Person** value object and pairs it with ministry + titles +- The output is a **Minister** value object - **Person** -> appoint_random_ministries +**Person** -\> appoint_random_ministries First, we implement the input type. `Person()` is the constructor of the **Person** value object. @@ -123,7 +124,6 @@ parliament_members appoint_random_ministries(member = parliament_members) ``` - ## Further Reading [Value Object on Wikipedia](https://en.wikipedia.org/wiki/Value_object) From 2fe07ddfc59fbdf36b254ccacfa40a0318d3ae39 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 20:11:29 +1300 Subject: [PATCH 28/40] drop markdown from function docs --- R/base-NullObject.R | 1 - R/base-Singleton.R | 1 - R/base-ValueObject.R | 1 - R/object_relational-Repository.R | 4 +--- 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/R/base-NullObject.R b/R/base-NullObject.R index 5997438..883d869 100644 --- a/R/base-NullObject.R +++ b/R/base-NullObject.R @@ -1,5 +1,4 @@ #' @title Null Object Pattern -#' @includeRmd vignettes/details/_NullObject.Rmd #' @description Model a domain concept using natural lingo of the domain #' experts, such as “Passenger”, “Address”, and “Money”. #' @family base design patterns diff --git a/R/base-Singleton.R b/R/base-Singleton.R index 425c468..cd0d7dc 100644 --- a/R/base-Singleton.R +++ b/R/base-Singleton.R @@ -1,7 +1,6 @@ #' @title Singleton Pattern #' @name Singleton #' @aliases Singleton -#' @includeRmd vignettes/details/_Singleton.Rmd #' @description Ensure a class only has one instance, and provide a global point of access to it. #' @family base design patterns #' @export diff --git a/R/base-ValueObject.R b/R/base-ValueObject.R index 09d963a..9eb59da 100644 --- a/R/base-ValueObject.R +++ b/R/base-ValueObject.R @@ -1,6 +1,5 @@ #' @name ValueObject #' @title Value Object Pattern -#' @includeRmd vignettes/details/_ValueObject.Rmd #' @description Model a domain concept using natural lingo of the domain #' experts, such as “Passenger”, “Address”, and “Money”. #' @param given (`character`) A character vector with the given name. diff --git a/R/object_relational-Repository.R b/R/object_relational-Repository.R index 0738db8..fe595f1 100644 --- a/R/object_relational-Repository.R +++ b/R/object_relational-Repository.R @@ -1,6 +1,5 @@ #' @title Repository Pattern #' @name Repository -#' @includeRmd vignettes/details/_Repository.Rmd #' @examples #' # See more examples at #' @@ -65,8 +64,7 @@ #' repository$get(key = "Mazda RX4") NULL -#' @name AbstractRepository -#' @title AbstractRepository +#' @rdname Repository #' @description Mediates between the domain and data mapping layers using a #' collection-like interface for accessing domain objects. #' @param key (`character`) Name of the element. From 81b4c89ddef4925d3ea6c51a2c3da81051d50963 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 20:14:02 +1300 Subject: [PATCH 29/40] update package extraneous files --- DESCRIPTION | 2 +- NEWS.md | 6 ++++ README.md | 50 +++++------------------------- inst/snippets/submitting-package.R | 9 ++++-- 4 files changed, 22 insertions(+), 45 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4140718..5b7d82e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -3,7 +3,7 @@ Type: Package Title: Design Patterns in R URL: https://tidylab.github.io/R6P/, https://github.com/tidylab/R6P BugReports: https://github.com/tidylab/R6P/issues -Version: 0.3.0 +Version: 0.4.0 Authors@R: c( person("Harel", "Lustiger", email = "tidylab@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2953-9598")), diff --git a/NEWS.md b/NEWS.md index e8f9ce4..9cc1b93 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# R6P 0.4.0 + +- Dropped dependency on `purrr` package +- Uses "|>" and therefore requires R 4.1.0 +- Revamp function documentation to pass CRAN checks + # R6P 0.2.1 - Update system files diff --git a/README.md b/README.md index bec9476..11f7a7e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # `R6P` package logo @@ -14,13 +13,11 @@ coverage](https://codecov.io/gh/tidylab/R6P/graph/badge.svg)](https://app.codeco ## Intentions -- `R6P` is a collection of useful design patterns in R -- `R6P` explains *how a design pattern works* and *when to use a design - pattern* -- `R6P` provides examples that show how to implement each design pattern - in R - -
+- `R6P` is a collection of useful design patterns in R +- `R6P` explains *how a design pattern works* and *when to use a + design pattern* +- `R6P` provides examples that show how to implement each design + pattern in R **Caution:** Most functions and classes provided by the `R6P` package are not useful by themselves. This is because design patterns are @@ -29,8 +26,6 @@ demonstration purposes. Instead of directly using the design pattern as they appear in the package, you’d have to adjust the source code (provided in the examples) to the problem you are trying to solve. -
- ## Introduction Build robust and maintainable software with object-oriented design @@ -48,8 +43,6 @@ This package is based on the work of Gamma1995, and Fowler2002. ### Should I use design patterns? -
- Design patterns represent an alternative to design: rather than designing a new mechanism from scratch, just apply a well-known design pattern. For the most part, this is good: design patterns arose because @@ -58,10 +51,6 @@ provide clean solutions. If a design pattern works well in a particular situation, it will probably be hard for you to come up with a different approach that is better. -
- -
- The greatest risk with design patterns is over-application. Not every problem can be solved cleanly with an existing design pattern; don’t try to force a problem into a design pattern when a custom approach will be @@ -70,49 +59,26 @@ system; it only does so if the design patterns fit. As with many ideas in software design, the notion that design patterns are good doesn’t necessarily mean that more design patterns are better. -
- ## Installation You can install the released version of R6P from CRAN with: -``` r -install.packages("R6P") -``` + install.packages("R6P") And the development version from GitHub with: -``` r -# install.packages("devtools") -devtools::install_github("tidylab/R6P") -``` + # install.packages("devtools") + devtools::install_github("tidylab/R6P") ## References -
- -
- Fowler, Martin. 2002. *Patterns of enterprise application architecture*. Addison-Wesley Longman Publishing Co., Inc. -
- -
- Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. 1995. *Design patterns: elements of reusable object-oriented software*. Pearson Education India. -
- -
- Ousterhout, John. 2018. *A Philosophy of Software Design*. Yaknyam Press. - -
- -
diff --git a/inst/snippets/submitting-package.R b/inst/snippets/submitting-package.R index 0e4317c..c55caf4 100644 --- a/inst/snippets/submitting-package.R +++ b/inst/snippets/submitting-package.R @@ -1,11 +1,16 @@ devtools::check(remote = TRUE, manual = TRUE) -rhub::check( platform="windows-x86_64-devel", env_vars=c(R_COMPILE_AND_INSTALL_PACKAGES = "always") ) +rhub::check(platform="windows-x86_64-devel", env_vars=c(R_COMPILE_AND_INSTALL_PACKAGES = "always")) +# rhub::rhub_setup() +# rhub::rhub_check(platforms = c("windows", "macos", "linux"), branch = "master") -devtools::check_win_devel() +# devtools::check_win_devel() +devtools::check_win_release() devtools::build_manual() devtools::spell_check() utils::maintainer(pkgload::pkg_name()) + +# devtools::submit_cran() From cf6f38ad8240980a57654175ae2540169e616251 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 20:33:31 +1300 Subject: [PATCH 30/40] retrieve files --- R/base-Singleton.R | 15 ++++++++------- R/base-ValueObject.R | 7 +++---- R/object_relational-Repository.R | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/R/base-Singleton.R b/R/base-Singleton.R index cd0d7dc..ee861ec 100644 --- a/R/base-Singleton.R +++ b/R/base-Singleton.R @@ -1,9 +1,6 @@ #' @title Singleton Pattern #' @name Singleton -#' @aliases Singleton -#' @description Ensure a class only has one instance, and provide a global point of access to it. -#' @family base design patterns -#' @export +#' @includeRmd vignettes/details/Singleton.Rmd #' @examples #' # See more examples at #' address <- function(x) sub('', '\\1', capture.output(x)) @@ -13,7 +10,7 @@ #' Counter <- R6::R6Class("Counter", inherit = R6P::Singleton, public = list( #' count = 0, #' add_1 = function(){self$count = self$count + 1; invisible(self)} -#' )) +#' )) #' #' # Whenever we call the constructor on `Counter`, we always get the exact same #' # instance: @@ -39,6 +36,11 @@ #' counter_B$count NULL +#' @rdname Singleton +#' @description Ensure a class only has one instance, and provide a global point +#' of access to it. +#' @family base design patterns +#' @export Singleton <- R6::R6Class("Singleton", cloneable = FALSE, public = list( #' @description Create or retrieve an object initialize = function(){ @@ -53,7 +55,7 @@ Singleton <- R6::R6Class("Singleton", cloneable = FALSE, public = list( Class$set('private', 'public_bind_env', private$public_bind_env, overwrite = TRUE) Class$set('private', 'private_bind_env', private$private_bind_env, overwrite = TRUE) - } else { + } else { self <- private$instance private$dynSet("public_bind_env", private$public_bind_env) private$dynSet("private_bind_env", private$private_bind_env) @@ -65,4 +67,3 @@ Singleton <- R6::R6Class("Singleton", cloneable = FALSE, public = list( dynGet = dynGet, dynSet = dynSet )) - diff --git a/R/base-ValueObject.R b/R/base-ValueObject.R index 9eb59da..10debe1 100644 --- a/R/base-ValueObject.R +++ b/R/base-ValueObject.R @@ -1,5 +1,5 @@ -#' @name ValueObject #' @title Value Object Pattern +#' @includeRmd vignettes/details/ValueObject.Rmd #' @description Model a domain concept using natural lingo of the domain #' experts, such as “Passenger”, “Address”, and “Money”. #' @param given (`character`) A character vector with the given name. @@ -59,8 +59,8 @@ #' #' appoint_random_ministries(member = parliament_members) ValueObject <- function( - given = NA_character_, - family = NA_character_ + given = NA_character_, + family = NA_character_ ){ stopifnot(is.character(given), is.character(family)) stopifnot(length(given) == length(family) | all(is.na(family))) @@ -71,4 +71,3 @@ ValueObject <- function( ) |> tidyr::drop_na(given) } - diff --git a/R/object_relational-Repository.R b/R/object_relational-Repository.R index fe595f1..189d118 100644 --- a/R/object_relational-Repository.R +++ b/R/object_relational-Repository.R @@ -1,5 +1,6 @@ #' @title Repository Pattern #' @name Repository +#' @includeRmd vignettes/details/Repository.Rmd #' @examples #' # See more examples at #' @@ -85,4 +86,3 @@ AbstractRepository <- R6::R6Class("Repository", inherit = Singleton, cloneable = get = function(key) exceptions$not_implemented_error() )) # nocov end - From b4756321126a84cc0d2e2abb63e8971ba3d2aa39 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 20:38:06 +1300 Subject: [PATCH 31/40] lint package --- R/R6P-helpers.R | 43 ++++++------ R/R6P-package.R | 6 +- R/base-NullObject.R | 4 +- R/base-Singleton.R | 48 ++++++------- R/base-ValueObject.R | 50 +++++++------- R/object_relational-Repository.R | 55 +++++++++------ R/zzz.R | 29 ++++---- README.Rmd | 2 +- inst/snippets/submitting-package.R | 9 +++ tests/testthat/helpers-xyz.R | 20 ++++-- tests/testthat/test-base-Singleton.R | 68 ++++++++++--------- .../test-object_relational-Repository.R | 14 ++-- vignettes/_article_template.Rmd | 2 +- vignettes/appendices/Dictionary.Rmd | 2 +- vignettes/appendices/SpecialCase.Rmd | 2 +- vignettes/details/_NullObject.Rmd | 41 ++++++----- vignettes/details/_Repository.Rmd | 2 +- vignettes/details/_Singleton.Rmd | 2 +- vignettes/details/_ValueObject.Rmd | 30 ++++---- vignettes/patterns/NullObject.Rmd | 42 +++++++----- vignettes/patterns/Repository.Rmd | 66 ++++++++++++------ vignettes/patterns/Singleton.Rmd | 38 ++++++----- vignettes/patterns/ValueObject.Rmd | 23 +++---- 23 files changed, 338 insertions(+), 260 deletions(-) diff --git a/R/R6P-helpers.R b/R/R6P-helpers.R index f0e8fc0..31142ac 100644 --- a/R/R6P-helpers.R +++ b/R/R6P-helpers.R @@ -1,27 +1,31 @@ # Helper Functions -------------------------------------------------------- -get_classname <- function(){ - calls <- as.character(sys.calls()) - calls <- calls[max(which(stringr::str_detect(calls, "\\$new\\(.*\\)")))] - stopifnot(length(calls) == 1) +get_classname <- function() { + calls <- as.character(sys.calls()) + calls <- calls[max(which(stringr::str_detect(calls, "\\$new\\(.*\\)")))] + stopifnot(length(calls) == 1) - classname <- stringr::str_remove(calls, "\\$new\\(.*\\)") - return(classname) + classname <- stringr::str_remove(calls, "\\$new\\(.*\\)") + return(classname) } -dynGet <- function(x, ...){ - tryCatch( - base::dynGet(x, ...), - error = function(e){ return(base::get(x, ...)) } - ) +dynGet <- function(x, ...) { + tryCatch( + base::dynGet(x, ...), + error = function(e) { + return(base::get(x, ...)) + } + ) } -dynSet <- function(key, value){ - n <- sys.nframe() - for(i in seq_len(n-1)) - if(any(key %in% ls(envir = parent.frame(i)))) - assign(key, value, envir = parent.frame(i)) +dynSet <- function(key, value) { + n <- sys.nframe() + for (i in seq_len(n - 1)) { + if (any(key %in% ls(envir = parent.frame(i)))) { + assign(key, value, envir = parent.frame(i)) + } + } - return() + return() } # Exception Handling ------------------------------------------------------ @@ -31,7 +35,6 @@ dynSet <- function(key, value){ exceptions <- new.env() exceptions$not_implemented_error <- function() { - caller_name <- deparse(sys.calls()[[sys.nframe()-2]]) - stop(paste(caller_name, "is not implmented")) + caller_name <- deparse(sys.calls()[[sys.nframe() - 2]]) + stop(paste(caller_name, "is not implmented")) } - diff --git a/R/R6P-package.R b/R/R6P-package.R index 8547a91..5bbc4d5 100644 --- a/R/R6P-package.R +++ b/R/R6P-package.R @@ -7,7 +7,7 @@ NULL # Missing Packages Workaround .workaround <- function() { - R6::R6Class - collections::queue - dplyr::.data + R6::R6Class + collections::queue + dplyr::.data } diff --git a/R/base-NullObject.R b/R/base-NullObject.R index 883d869..6a586cf 100644 --- a/R/base-NullObject.R +++ b/R/base-NullObject.R @@ -8,6 +8,4 @@ #' #' colnames(NullObject()) #' nrow(NullObject()) -NullObject <- function() tibble::tibble(given = NA_character_, family = NA_character_)[0,] - - +NullObject <- function() tibble::tibble(given = NA_character_, family = NA_character_)[0, ] diff --git a/R/base-Singleton.R b/R/base-Singleton.R index ee861ec..a0d3e0c 100644 --- a/R/base-Singleton.R +++ b/R/base-Singleton.R @@ -3,13 +3,16 @@ #' @includeRmd vignettes/details/Singleton.Rmd #' @examples #' # See more examples at -#' address <- function(x) sub('', '\\1', capture.output(x)) +#' address <- function(x) sub("", "\\1", capture.output(x)) #' #' # In this example we implement a `Counter` that inherits the qualities of #' # Singleton #' Counter <- R6::R6Class("Counter", inherit = R6P::Singleton, public = list( -#' count = 0, -#' add_1 = function(){self$count = self$count + 1; invisible(self)} +#' count = 0, +#' add_1 = function() { +#' self$count <- self$count + 1 +#' invisible(self) +#' } #' )) #' #' # Whenever we call the constructor on `Counter`, we always get the exact same @@ -42,28 +45,27 @@ NULL #' @family base design patterns #' @export Singleton <- R6::R6Class("Singleton", cloneable = FALSE, public = list( - #' @description Create or retrieve an object - initialize = function(){ - classname <- get_classname() - if(classname == "Singleton") stop(paste(classname, "is an abstract base class and therefore cannot be instantiated directly")) - if(is.null(private$public_bind_env)){ - Class <- private$dynGet(classname) + #' @description Create or retrieve an object + initialize = function() { + classname <- get_classname() + if (classname == "Singleton") stop(paste(classname, "is an abstract base class and therefore cannot be instantiated directly")) + if (is.null(private$public_bind_env)) { + Class <- private$dynGet(classname) - private$public_bind_env <- private$dynGet("public_bind_env") - private$private_bind_env <- private$dynGet("private_bind_env") + private$public_bind_env <- private$dynGet("public_bind_env") + private$private_bind_env <- private$dynGet("private_bind_env") - Class$set('private', 'public_bind_env', private$public_bind_env, overwrite = TRUE) - Class$set('private', 'private_bind_env', private$private_bind_env, overwrite = TRUE) - - } else { - self <- private$instance - private$dynSet("public_bind_env", private$public_bind_env) - private$dynSet("private_bind_env", private$private_bind_env) - } + Class$set("private", "public_bind_env", private$public_bind_env, overwrite = TRUE) + Class$set("private", "private_bind_env", private$private_bind_env, overwrite = TRUE) + } else { + self <- private$instance + private$dynSet("public_bind_env", private$public_bind_env) + private$dynSet("private_bind_env", private$private_bind_env) } + } ), private = list( - public_bind_env = NULL, - private_bind_env = NULL, - dynGet = dynGet, - dynSet = dynSet + public_bind_env = NULL, + private_bind_env = NULL, + dynGet = dynGet, + dynSet = dynSet )) diff --git a/R/base-ValueObject.R b/R/base-ValueObject.R index 10debe1..5d89210 100644 --- a/R/base-ValueObject.R +++ b/R/base-ValueObject.R @@ -21,29 +21,28 @@ #' #' # A 'Minister' is a 'Person' with a ministry title. We capture that information #' # in a new Value Object named 'Minister'. -# #' # The Minister constructor requires two inputs: #' # 1. (`Person`) Members of parliament #' # 2. (`character`) Ministry titles -#' Minister <- function(member = Person(), title = NA_character_){ -#' stopifnot(is.Person(member), is.character(title)) -#' stopifnot(nrow(member) == length(title) | all(is.na(title))) +#' Minister <- function(member = Person(), title = NA_character_) { +#' stopifnot(is.Person(member), is.character(title)) +#' stopifnot(nrow(member) == length(title) | all(is.na(title))) #' -#' member |> dplyr::mutate(title = title) -#'} +#' member |> dplyr::mutate(title = title) +#' } #' #' # Given one or more parliament members #' # When appoint_random_ministries is called #' # Then the parliament members are appointed to an office. -#' appoint_random_ministries <- function(member = Person()){ -#' positions <- c( -#' "Arts, Culture and Heritage", "Finance", "Corrections", -#' "Racing", "Sport and Recreation", "Housing", "Energy and Resources", -#' "Education", "Public Service", "Disability Issues", "Environment", -#' "Justice", "Immigration", "Defence", "Internal Affairs", "Transport" -#' ) +#' appoint_random_ministries <- function(member = Person()) { +#' positions <- c( +#' "Arts, Culture and Heritage", "Finance", "Corrections", +#' "Racing", "Sport and Recreation", "Housing", "Energy and Resources", +#' "Education", "Public Service", "Disability Issues", "Environment", +#' "Justice", "Immigration", "Defence", "Internal Affairs", "Transport" +#' ) #' -#' Minister(member = member, title = sample(positions, size = nrow(member))) +#' Minister(member = member, title = sample(positions, size = nrow(member))) #' } #' #' # Listing New Zealand elected officials in 2020, we instantiate a Person Object, @@ -51,23 +50,20 @@ #' set.seed(2020) #' #' parliament_members <- Person( -#' given = c("Jacinda", "Grant", "Kelvin", "Megan", "Chris", "Carmel"), -#' family = c("Ardern", "Robertson", "Davis", "Woods", "Hipkins", "Sepuloni") +#' given = c("Jacinda", "Grant", "Kelvin", "Megan", "Chris", "Carmel"), +#' family = c("Ardern", "Robertson", "Davis", "Woods", "Hipkins", "Sepuloni") #' ) #' #' parliament_members #' #' appoint_random_ministries(member = parliament_members) -ValueObject <- function( - given = NA_character_, - family = NA_character_ -){ - stopifnot(is.character(given), is.character(family)) - stopifnot(length(given) == length(family) | all(is.na(family))) +ValueObject <- function(given = NA_character_, + family = NA_character_) { + stopifnot(is.character(given), is.character(family)) + stopifnot(length(given) == length(family) | all(is.na(family))) - tibble::tibble( - given = given |> stringr::str_to_title(), - family = family |> stringr::str_to_title() - ) |> tidyr::drop_na(given) + tibble::tibble( + given = given |> stringr::str_to_title(), + family = family |> stringr::str_to_title() + ) |> tidyr::drop_na(given) } - diff --git a/R/object_relational-Repository.R b/R/object_relational-Repository.R index 189d118..ae6f1fd 100644 --- a/R/object_relational-Repository.R +++ b/R/object_relational-Repository.R @@ -20,22 +20,37 @@ #' # predefined column. #' #' TransientRepository <- R6::R6Class( -#' classname = "Repository", inherit = R6P::AbstractRepository, public = list( -#' initialize = function() {private$cars <- collections::dict()}, -#' add = function(key, value){private$cars$set(key, value); invisible(self)}, -#' del = function(key){private$cars$remove(key); invisible(self)}, -#' get = function(key){return(private$cars$get(key, default = private$NULL_car))} -#' ), private = list( -#' NULL_car = cbind(uid = NA_character_, datasets::mtcars)[0,], -#' cars = NULL -#' )) +#' classname = "Repository", inherit = R6P::AbstractRepository, public = list( +#' initialize = function() { +#' private$cars <- collections::dict() +#' }, +#' add = function(key, value) { +#' private$cars$set(key, value) +#' invisible(self) +#' }, +#' del = function(key) { +#' private$cars$remove(key) +#' invisible(self) +#' }, +#' get = function(key) { +#' return(private$cars$get(key, default = private$NULL_car)) +#' } +#' ), private = list( +#' NULL_car = cbind(uid = NA_character_, datasets::mtcars)[0, ], +#' cars = NULL +#' ) +#' ) #' #' # Adding customised operations is also possible via the R6 set function. #' # The following example, adds a query that returns all the objects in the database #' -#' TransientRepository$set("public", "get_all_cars", overwrite = TRUE, function(){ -#' result <- private$cars$values() |> dplyr::bind_rows() -#' if(nrow(result) == 0) return(private$NULL_car) else return(result) +#' TransientRepository$set("public", "get_all_cars", overwrite = TRUE, function() { +#' result <- private$cars$values() |> dplyr::bind_rows() +#' if (nrow(result) == 0) { +#' return(private$NULL_car) +#' } else { +#' return(result) +#' } #' }) #' #' # In this example, we use the mtcars dataset with a uid column that uniquely @@ -76,13 +91,13 @@ NULL #' @export # nocov start AbstractRepository <- R6::R6Class("Repository", inherit = Singleton, cloneable = FALSE, public = list( - #' @description Instantiate an object - initialize = function() exceptions$not_implemented_error(), - #' @description Add an element to the Repository. - add = function(key, value) exceptions$not_implemented_error(), - #' @description Delete an element from the Repository. - del = function(key) exceptions$not_implemented_error(), - #' @description Retrieve an element from the Repository. - get = function(key) exceptions$not_implemented_error() + #' @description Instantiate an object + initialize = function() exceptions$not_implemented_error(), + #' @description Add an element to the Repository. + add = function(key, value) exceptions$not_implemented_error(), + #' @description Delete an element from the Repository. + del = function(key) exceptions$not_implemented_error(), + #' @description Retrieve an element from the Repository. + get = function(key) exceptions$not_implemented_error() )) # nocov end diff --git a/R/zzz.R b/R/zzz.R index b5732dd..1869988 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,15 +1,18 @@ -.onAttach <- function(lib, pkg,...){#nocov start - options( - usethis.quiet = TRUE - ) +.onAttach <- function(lib, pkg, ...) { # nocov start + options( + usethis.quiet = TRUE + ) - if(interactive()) packageStartupMessage( - paste( - "\n\033[44m\033[37m", - "\nWelcome to R6P", - "\nMore information, vignettes, and guides are available on the R6P project website:", - "\nhttps://tidylab.github.io/R6P/", - "\n\033[39m\033[49m", - sep="") + if (interactive()) { + packageStartupMessage( + paste( + "\n\033[44m\033[37m", + "\nWelcome to R6P", + "\nMore information, vignettes, and guides are available on the R6P project website:", + "\nhttps://tidylab.github.io/R6P/", + "\n\033[39m\033[49m", + sep = "" + ) ) -}#nocov end + } +} # nocov end diff --git a/README.Rmd b/README.Rmd index 38beb66..30c041d 100644 --- a/README.Rmd +++ b/README.Rmd @@ -18,7 +18,7 @@ editor_options: ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` # `R6P` package logo diff --git a/inst/snippets/submitting-package.R b/inst/snippets/submitting-package.R index c55caf4..986abdc 100644 --- a/inst/snippets/submitting-package.R +++ b/inst/snippets/submitting-package.R @@ -1,3 +1,12 @@ +# Lint the package +lintr::lint_package(show_progress = TRUE) +styler::style_pkg( + filetype = c("R", "Rprofile", "Rmd", "Rmarkdown", "Rnw", "qmd")[c(1,3)], + exclude_dirs = c("packrat", "renv", ".dev", ".git", ".github", ".Rproj.user", "docs", "inst"), + include_roxygen_examples = TRUE, +) + + devtools::check(remote = TRUE, manual = TRUE) rhub::check(platform="windows-x86_64-devel", env_vars=c(R_COMPILE_AND_INSTALL_PACKAGES = "always")) diff --git a/tests/testthat/helpers-xyz.R b/tests/testthat/helpers-xyz.R index 6398007..10f2038 100644 --- a/tests/testthat/helpers-xyz.R +++ b/tests/testthat/helpers-xyz.R @@ -10,15 +10,21 @@ expect_has_columns <- function(data, cols) testthat::expect(all(cols %in% colnam expect_file_exists <- function(path) testthat::expect(file.exists(path), "File doesn't exist at " %+% path) expect_match <- function(object, regexp) testthat::expect_match(stringr::str_flatten(object, collapse = "\n"), regexp) expect_not_failure <- function(object, ...) { - testthat::expect_type(object, type = "environment", ...) + testthat::expect_type(object, type = "environment", ...) } # mocks ------------------------------------------------------------------- DummyRepository <- R6::R6Class(classname = "Repository", inherit = AbstractRepository, public = list( - initialize = function() private$cars <- collections::dict(), - add = function(key, value){private$cars$set(key, value); return(self)}, - del = function(key){private$cars$remove(key); return(self)}, - get = function(key){return(private$cars$get(key, default = NULL))} + initialize = function() private$cars <- collections::dict(), + add = function(key, value) { + private$cars$set(key, value) + return(self) + }, + del = function(key) { + private$cars$remove(key) + return(self) + }, + get = function(key) { + return(private$cars$get(key, default = NULL)) + } ), private = list(cars = NULL)) - - diff --git a/tests/testthat/test-base-Singleton.R b/tests/testthat/test-base-Singleton.R index 6570204..431e5e6 100644 --- a/tests/testthat/test-base-Singleton.R +++ b/tests/testthat/test-base-Singleton.R @@ -1,45 +1,53 @@ # General ----------------------------------------------------------------- test_that("calling Singleton$new fails because it cannot be instantiated directly", { - expect_error(Singleton$new()) + expect_error(Singleton$new()) }) # Implementation ---------------------------------------------------------- test_that("instantiating of multiple objects of the same Singleton are identical", { - Counter <- R6::R6Class(classname = "Counter", inherit = Singleton, public = list( - count = 0, - add_1 = function(){self$count = self$count + 1; invisible(self)} - )) - - expect_s3_class(counter_1 <- Counter$new(), "Singleton") - expect_s3_class(counter_2 <- Counter$new(), "Counter") - expect_identical(counter_1, counter_2) - - counter_1$add_1() - expect_equal(counter_1$count, counter_2$count) + Counter <- R6::R6Class(classname = "Counter", inherit = Singleton, public = list( + count = 0, + add_1 = function() { + self$count <- self$count + 1 + invisible(self) + } + )) + + expect_s3_class(counter_1 <- Counter$new(), "Singleton") + expect_s3_class(counter_2 <- Counter$new(), "Counter") + expect_identical(counter_1, counter_2) + + counter_1$add_1() + expect_equal(counter_1$count, counter_2$count) }) test_that("instantiating of multiple objects of the same Singleton with superclass", { - SuperCounter <- R6::R6Class(classname = "SuperCounter", inherit = Singleton, public = list( - count = 0, - add_1 = function(){self$count = self$count + 1; invisible(self)}, - initialize = function(){super$initialize()} - )) - - expect_s3_class(counter_1 <- SuperCounter$new(), "Singleton") - expect_s3_class(counter_2 <- SuperCounter$new(), "SuperCounter") - expect_identical(counter_1, counter_2) - - counter_1$add_1() - expect_equal(counter_1$count, counter_2$count) + SuperCounter <- R6::R6Class(classname = "SuperCounter", inherit = Singleton, public = list( + count = 0, + add_1 = function() { + self$count <- self$count + 1 + invisible(self) + }, + initialize = function() { + super$initialize() + } + )) + + expect_s3_class(counter_1 <- SuperCounter$new(), "Singleton") + expect_s3_class(counter_2 <- SuperCounter$new(), "SuperCounter") + expect_identical(counter_1, counter_2) + + counter_1$add_1() + expect_equal(counter_1$count, counter_2$count) }) test_that("instantiating of multiple objects of the different Singleton are not identical", { - SingletonA <- R6::R6Class(classname = "SingletonA", inherit = Singleton, public = list(uid = "A")) - SingletonB <- R6::R6Class(classname = "SingletonB", inherit = Singleton, public = list(uid = "B")) + SingletonA <- R6::R6Class(classname = "SingletonA", inherit = Singleton, public = list(uid = "A")) + SingletonB <- R6::R6Class(classname = "SingletonB", inherit = Singleton, public = list(uid = "B")) - expect_s3_class(singleton_A <- SingletonA$new(), "Singleton") - expect_s3_class(singleton_B <- SingletonB$new(), "Singleton") - expect_false(identical(singleton_A, singleton_B)) + expect_s3_class(singleton_A <- SingletonA$new(), "Singleton") + expect_s3_class(singleton_B <- SingletonB$new(), "Singleton") + expect_false(identical(singleton_A, singleton_B)) }) # test_that("inheriting Singleton takes the last class name", { @@ -51,5 +59,3 @@ test_that("instantiating of multiple objects of the different Singleton are not # # expect_false(identical(level1, level2)) # }) - - diff --git a/tests/testthat/test-object_relational-Repository.R b/tests/testthat/test-object_relational-Repository.R index bad1444..82ed2a7 100644 --- a/tests/testthat/test-object_relational-Repository.R +++ b/tests/testthat/test-object_relational-Repository.R @@ -1,14 +1,14 @@ # General ----------------------------------------------------------------- test_that("calling AbstractRepository$new fails because it cannot be instantiated directly", { - expect_error(AbstractRepository$new()) - expect_named(AbstractRepository$public_methods, c('initialize', 'add', 'del', 'get')) + expect_error(AbstractRepository$new()) + expect_named(AbstractRepository$public_methods, c("initialize", "add", "del", "get")) }) # Implementation ---------------------------------------------------------- test_that("implementing AbstractRepository returns Repository", { - expect_s3_class(repository <- DummyRepository$new(), "Repository") - expect_s3_class(repository$add(key = rownames(mtcars[1,]), value = mtcars[1,]), "Repository") - expect_identical(repository$get(key = rownames(mtcars[1,])), mtcars[1,]) - expect_s3_class(repository$del(key = rownames(mtcars[1,])), "Repository") - expect_null(repository$get(key = rownames(mtcars[1,])), "Repository") + expect_s3_class(repository <- DummyRepository$new(), "Repository") + expect_s3_class(repository$add(key = rownames(mtcars[1, ]), value = mtcars[1, ]), "Repository") + expect_identical(repository$get(key = rownames(mtcars[1, ])), mtcars[1, ]) + expect_s3_class(repository$del(key = rownames(mtcars[1, ])), "Repository") + expect_null(repository$get(key = rownames(mtcars[1, ])), "Repository") }) diff --git a/vignettes/_article_template.Rmd b/vignettes/_article_template.Rmd index d5c8ec2..4a3a950 100644 --- a/vignettes/_article_template.Rmd +++ b/vignettes/_article_template.Rmd @@ -12,7 +12,7 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` diff --git a/vignettes/appendices/Dictionary.Rmd b/vignettes/appendices/Dictionary.Rmd index 38dfce1..e3fa39a 100644 --- a/vignettes/appendices/Dictionary.Rmd +++ b/vignettes/appendices/Dictionary.Rmd @@ -9,7 +9,7 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` `collections::dict` diff --git a/vignettes/appendices/SpecialCase.Rmd b/vignettes/appendices/SpecialCase.Rmd index 179102a..0b85b95 100644 --- a/vignettes/appendices/SpecialCase.Rmd +++ b/vignettes/appendices/SpecialCase.Rmd @@ -9,7 +9,7 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` The NULL object [@Fowler2002, p. 496] diff --git a/vignettes/details/_NullObject.Rmd b/vignettes/details/_NullObject.Rmd index add8ecd..c63378c 100644 --- a/vignettes/details/_NullObject.Rmd +++ b/vignettes/details/_NullObject.Rmd @@ -6,7 +6,7 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` ```{r, error=TRUE} @@ -56,25 +56,27 @@ variables types, with no rows. an error: ```{r, echo = TRUE, eval = TRUE} -# Simulate a database that is 5% likely to fail -read_mtcars <- function() if(runif(1) < 0.05) stop() else return(mtcars) +# Simulate a database that is 5% likely to fail +read_mtcars <- function() if (runif(1) < 0.05) stop() else return(mtcars) # mtcars null object constructor -NullCar <- function() mtcars[0,] +NullCar <- function() mtcars[0, ] -# How does the null car object look like? +# How does the null car object look like? NullCar() # Subroutine with gracefully failing strategy set.seed(1814) cars <- tryCatch( # Try reading the mtcars dataset - read_mtcars(), + read_mtcars(), # If there is an error, return the Null Car object - error = function(e) return(NullCar()) + error = function(e) { + return(NullCar()) + } ) -# Notice: Whether the subroutine fails or succeeds, it returns a tibble with +# Notice: Whether the subroutine fails or succeeds, it returns a tibble with # the same structure. colnames(cars) ``` @@ -82,14 +84,17 @@ colnames(cars) - In Shiny dashboards ```{r, echo = TRUE, eval = FALSE} -geom_null <- function(...){ - ggplot2::ggplot() + ggplot2::geom_blank() + ggplot2::theme_void() +geom_null <- function(...) { + ggplot2::ggplot() + + ggplot2::geom_blank() + + ggplot2::theme_void() } -if(exists("user_input")){ - ggplot2::ggplot(user_input, ggplot::aes(x = mpg, y = hp)) + ggplot2::geom_point() +if (exists("user_input")) { + ggplot2::ggplot(user_input, ggplot::aes(x = mpg, y = hp)) + + ggplot2::geom_point() } else { - geom_null() + geom_text(aes(0,0), label = "choose an entry from the list") + geom_null() + geom_text(aes(0, 0), label = "choose an entry from the list") } ``` @@ -97,11 +102,11 @@ if(exists("user_input")){ ```{r, echo = TRUE, eval = FALSE} classes <- function(x) sapply(x, class) - + test_that("mtcars follows a certain table structure", { - # Compare column names - expect_identical(colnames(mtcars), colnames(NullCar())) - # Compare variable types - expect_identical(classes(mtcars), classes(NullCar())) + # Compare column names + expect_identical(colnames(mtcars), colnames(NullCar())) + # Compare variable types + expect_identical(classes(mtcars), classes(NullCar())) }) ``` diff --git a/vignettes/details/_Repository.Rmd b/vignettes/details/_Repository.Rmd index 15d2de4..0af6c95 100644 --- a/vignettes/details/_Repository.Rmd +++ b/vignettes/details/_Repository.Rmd @@ -6,7 +6,7 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` diff --git a/vignettes/details/_Singleton.Rmd b/vignettes/details/_Singleton.Rmd index cfc444f..6d90ada 100644 --- a/vignettes/details/_Singleton.Rmd +++ b/vignettes/details/_Singleton.Rmd @@ -6,7 +6,7 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` diff --git a/vignettes/details/_ValueObject.Rmd b/vignettes/details/_ValueObject.Rmd index 69aa3df..f708385 100644 --- a/vignettes/details/_ValueObject.Rmd +++ b/vignettes/details/_ValueObject.Rmd @@ -6,7 +6,7 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) NA_POSIXct_ <- .POSIXct(NA_real_, tz = "UTC") is.POSIXct <- function(x) inherits(x, "POSIXct") ``` @@ -47,10 +47,10 @@ than numbers. A **Value Object** captures these attribute as `tibble` columns and type checks: ```{r, echo=TRUE} -Person <- function(given = NA_character_, family = NA_character_){ +Person <- function(given = NA_character_, family = NA_character_) { stopifnot(is.character(given), is.character(family)) stopifnot(length(given) == length(family)) - + return( tibble::tibble(given = given, family = family) |> tidyr::drop_na(given) @@ -127,14 +127,14 @@ Person to other Person-based concepts: ```{r, echo = TRUE, eval = TRUE} # A Passenger is a Person with a flight booking reference -Passenger <- function(person = Person(), booking_reference = NA_character_){ +Passenger <- function(person = Person(), booking_reference = NA_character_) { stopifnot(all(colnames(person) %in% colnames(Person()))) stopifnot(is.character(booking_reference)) - + return( - person + person |> tibble::add_column(booking_reference = booking_reference) - |> tidyr::drop_na(booking_reference) + |> tidyr::drop_na(booking_reference) ) } @@ -145,12 +145,12 @@ print(passenger) ```{r, echo = TRUE, eval = TRUE} # A Diner is a Person that may have dinner reservation -Diner <- function(person = Person(), reservation_time = NA_POSIXct_){ +Diner <- function(person = Person(), reservation_time = NA_POSIXct_) { stopifnot(all(colnames(person) %in% colnames(Person()))) stopifnot(is.POSIXct(reservation_time)) - + return( - person + person |> tibble::add_column(reservation_time = reservation_time) ) } @@ -179,19 +179,21 @@ print(diner) Audience <- Person ## Without a Value Object -clean_audience_data <- function(data) +clean_audience_data <- function(data) { dplyr::mutate(.data = data, given = stringr::str_to_title(given)) +} ## With a Value Object -clean_audience_data <- function(attendees = Audience()) +clean_audience_data <- function(attendees = Audience()) { dplyr::mutate(.data = attendees, given = stringr::str_to_title(given)) +} ``` - In [pipes and filters](https://en.wikipedia.org/wiki/Pipeline_(software)) architecture. ```{r, warning=TRUE} -warning("**Value Objects** do not need to have unit-tests. This is because of two reasons: -(1) **Value Objects** are often called by other functions that are being tested. That means, **Value Objects** are implicitly tested. +warning("**Value Objects** do not need to have unit-tests. This is because of two reasons: +(1) **Value Objects** are often called by other functions that are being tested. That means, **Value Objects** are implicitly tested. (2) **Value Objects** are data types similarly to 'data.frame' or 'list'. As such, they need no testing") ``` diff --git a/vignettes/patterns/NullObject.Rmd b/vignettes/patterns/NullObject.Rmd index 3346d52..e0bb5f8 100644 --- a/vignettes/patterns/NullObject.Rmd +++ b/vignettes/patterns/NullObject.Rmd @@ -11,7 +11,7 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` @@ -22,8 +22,10 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ## Example: Null ggplot2 ```{r, echo=TRUE, eval=FALSE} -geom_null <- function(...){ - ggplot2::ggplot() + ggplot2::geom_blank() + ggplot2::theme_void() +geom_null <- function(...) { + ggplot2::ggplot() + + ggplot2::geom_blank() + + ggplot2::theme_void() } ``` @@ -31,15 +33,18 @@ geom_null <- function(...){ failing](https://en.wikipedia.org/wiki/Graceful_exit) strategy. ```{r, echo=TRUE, eval=FALSE} -fig <- - tryCatch({ - stopifnot(runif(1) > 0.05) # simulate 5% chance for the subroutine to fail - - mtcars |> - ggplot2::ggplot(ggplot::aes(x = mpg, y = hp)) + - ggplot2::geom_point() - }, - error = function(e) return(geom_null()) # if subroutine has failed, return null +fig <- + tryCatch( + { + stopifnot(runif(1) > 0.05) # simulate 5% chance for the subroutine to fail + + mtcars |> + ggplot2::ggplot(ggplot::aes(x = mpg, y = hp)) + + ggplot2::geom_point() + }, + error = function(e) { + return(geom_null()) + } # if subroutine has failed, return null ) plot(fig) @@ -50,30 +55,31 @@ plot(fig) a ggplot object. ```{r, echo=TRUE, eval=FALSE} -if(exists("user_input")){ - ggplot2::ggplot(user_input, ggplot::aes(x = mpg, y = hp)) + ggplot2::geom_point() +if (exists("user_input")) { + ggplot2::ggplot(user_input, ggplot::aes(x = mpg, y = hp)) + + ggplot2::geom_point() } else { - geom_null() + geom_text(aes(0,0), label = "choose an entry from the list") + geom_null() + geom_text(aes(0, 0), label = "choose an entry from the list") } ``` ## Example: Null mtcars Car ```{r, echo=TRUE} -NullCar <- function() mtcars[0,] +NullCar <- function() mtcars[0, ] print(NullCar()) # The Null Car and the NULL value are not the same identical(NullCar(), NULL) -# Binding mtcars with the Null Car returns mtcars +# Binding mtcars with the Null Car returns mtcars identical(rbind(mtcars, NullCar()), mtcars) ``` ## Example: Null Value Object ```{r, echo=TRUE} -Person <- function(given = NA_character_, family = NA_character_){ +Person <- function(given = NA_character_, family = NA_character_) { tibble::tibble(given = given, family = family) |> tidyr::drop_na(given) } diff --git a/vignettes/patterns/Repository.Rmd b/vignettes/patterns/Repository.Rmd index 26e7f7e..5e741b3 100644 --- a/vignettes/patterns/Repository.Rmd +++ b/vignettes/patterns/Repository.Rmd @@ -11,13 +11,12 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` ```{r child=file.path(usethis::proj_get(), "vignettes", "details", "Repository.Rmd")} - ``` ## Implementations @@ -27,7 +26,10 @@ The code of the abstract base class of **Repository** is ```{r} path_snippet <- usethis::proj_path("R", "object_relational-Repository.R") snippet <- readLines(path_snippet) -snippet <- snippet |> discard_comments() |> discard_null() |> discard_empty_lines() +snippet <- snippet |> + discard_comments() |> + discard_null() |> + discard_empty_lines() ``` ```{r, code = snippet, eval = FALSE, echo = TRUE} @@ -98,23 +100,38 @@ column. ```{r Repository-Transient-implementation, echo=TRUE, results='markup'} TransientRepository <- R6::R6Class( classname = "Repository", inherit = R6P::AbstractRepository, public = list( - initialize = function() {private$cars <- collections::dict()}, - add = function(key, value){private$cars$set(key, value); invisible(self)}, - del = function(key){private$cars$remove(key); invisible(self)}, - get = function(key){return(private$cars$get(key, default = private$NULL_car))} + initialize = function() { + private$cars <- collections::dict() + }, + add = function(key, value) { + private$cars$set(key, value) + invisible(self) + }, + del = function(key) { + private$cars$remove(key) + invisible(self) + }, + get = function(key) { + return(private$cars$get(key, default = private$NULL_car)) + } ), private = list( - NULL_car = cbind(uid = NA_character_, datasets::mtcars)[0,], + NULL_car = cbind(uid = NA_character_, datasets::mtcars)[0, ], cars = NULL - )) + ) +) ``` Adding customised operations is also possible via the R6 `set` function. The following example, adds a query that returns all the objects in the database ```{r Repository-Transient-implementation-addon, echo=TRUE, results='markup'} -TransientRepository$set("public", "get_all_cars", overwrite = TRUE, function(){ +TransientRepository$set("public", "get_all_cars", overwrite = TRUE, function() { result <- private$cars$values() |> dplyr::bind_rows() - if(nrow(result) == 0) return(private$NULL_car) else return(result) + if (nrow(result) == 0) { + return(private$NULL_car) + } else { + return(result) + } }) ``` @@ -165,31 +182,36 @@ column. PersistentRepository <- R6::R6Class( classname = "Repository", inherit = AbstractRepository, public = list( #' @param immediate (`logical`) Should queries be committed immediately? - initialize = function(immediate = TRUE){ + initialize = function(immediate = TRUE) { private$immediate <- immediate private$conn <- DBI::dbConnect(RSQLite::SQLite(), dbname = ":memory:") DBI::dbCreateTable(private$conn, "mtcars", private$NULL_car) }, - add = function(key, value){ + add = function(key, value) { car <- private$NULL_car |> tibble::add_row(value) self$del(key = key) DBI::dbAppendTable(private$conn, "mtcars", car) invisible(self) }, - del = function(key){ + del = function(key) { statement <- paste0("DELETE FROM mtcars WHERE uid = '", key, "'") DBI::dbExecute(private$conn, statement, immediate = private$immediate) invisible(self) }, - get = function(key){ + get = function(key) { statement <- paste0("SELECT * FROM mtcars WHERE uid = '", key, "'") result <- DBI::dbGetQuery(private$conn, statement) - if(nrow(result) == 0) return(private$NULL_car) else return(result) + if (nrow(result) == 0) { + return(private$NULL_car) + } else { + return(result) + } } ), private = list( - NULL_car = cbind(uid = NA_character_, datasets::mtcars)[0,], + NULL_car = cbind(uid = NA_character_, datasets::mtcars)[0, ], immediate = NULL, - conn = NULL) + conn = NULL + ) ) ``` @@ -197,10 +219,14 @@ Adding customised operations is also possible via the R6 `set` function. The following example, adds a query that returns all the objects in the database ```{r Repository-Persistent-implementation-addon, echo=TRUE} -PersistentRepository$set("public", "get_all_cars", overwrite = TRUE, function(){ +PersistentRepository$set("public", "get_all_cars", overwrite = TRUE, function() { statement <- "SELECT * FROM mtcars" result <- DBI::dbGetQuery(private$conn, statement) - if(nrow(result) == 0) return(private$NULL_car) else return(result) + if (nrow(result) == 0) { + return(private$NULL_car) + } else { + return(result) + } }) ``` diff --git a/vignettes/patterns/Singleton.Rmd b/vignettes/patterns/Singleton.Rmd index 67b0aa0..6c9687c 100644 --- a/vignettes/patterns/Singleton.Rmd +++ b/vignettes/patterns/Singleton.Rmd @@ -11,13 +11,12 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` ```{r child=file.path(usethis::proj_get(), "vignettes", "details", "_Singleton.Rmd")} - ``` ## Example: Counter {#example-1} @@ -27,8 +26,11 @@ In this example we implement a `Counter` that inherits the qualities of ```{r Singleton-Counter-implementation, echo=TRUE, results='markup'} Counter <- R6::R6Class(inherit = Singleton, public = list( - count = 0, - add_1 = function(){self$count = self$count + 1; invisible(self)} + count = 0, + add_1 = function() { + self$count <- self$count + 1 + invisible(self) + } )) ``` @@ -57,7 +59,7 @@ counter_A$add_1() # How many times have the counters been increased? counter_A$count -counter_B$count +counter_B$count ``` ## Example: Data Transfer Object (DTO) @@ -70,19 +72,19 @@ Notice how the `initialize` public method first calls the `initialize` of the ```{r Singleton-DTO-implementation, echo=TRUE, results='hold', eval=TRUE} DTO <- R6::R6Class(classname = "DTO", inherit = Singleton, public = list( - con = NULL, - initialize = function(){ - super$initialize() - self$establish_connection() - }, - establish_connection = function(){ - self$con <- DBI::dbConnect(RSQLite::SQLite(), dbname = ":memory:") - return(self) - }, - dbSendQuery = function(statement){ - res <- DBI::dbSendQuery(self$con, statement) - return(res) - } + con = NULL, + initialize = function() { + super$initialize() + self$establish_connection() + }, + establish_connection = function() { + self$con <- DBI::dbConnect(RSQLite::SQLite(), dbname = ":memory:") + return(self) + }, + dbSendQuery = function(statement) { + res <- DBI::dbSendQuery(self$con, statement) + return(res) + } )) ``` diff --git a/vignettes/patterns/ValueObject.Rmd b/vignettes/patterns/ValueObject.Rmd index 082fb63..88e236f 100644 --- a/vignettes/patterns/ValueObject.Rmd +++ b/vignettes/patterns/ValueObject.Rmd @@ -11,13 +11,12 @@ editor_options: --- ```{r, include = FALSE} -source(file.path(usethis::proj_get(), "vignettes", "_common.R")) +source(file.path(usethis::proj_get(), "vignettes", "_common.R")) ``` ```{r child=file.path(usethis::proj_get(), "vignettes", "details", "_ValueObject.Rmd")} - ``` @@ -41,14 +40,14 @@ First, we implement the input type. `Person()` is the constructor of the ```{r, echo=TRUE} #' @title Person Value Object Constructor -#' @description A Person encapsulates the information that constitute an individual +#' @description A Person encapsulates the information that constitute an individual #' @param given (`character`) Individual first name. #' @param family (`character`) Individual last name. #' @return (`Person`) Person value object. -Person <- function(given = NA_character_, family = NA_character_){ +Person <- function(given = NA_character_, family = NA_character_) { stopifnot(is.character(given), is.character(family)) stopifnot(length(given) == length(family)) - + return( tibble::tibble(given = given, family = family) |> tidyr::drop_na(given) @@ -73,14 +72,14 @@ Second, we implement the output type. `Minister()` is the constructor of the ```{r, echo=TRUE} #' @title Minister Value Object Constructor -#' @decription A 'Minister' is a 'Person' with a ministry title. +#' @decription A 'Minister' is a 'Person' with a ministry title. #' @param Person (`Person`) A member of parliament. #' @param title (`character`) A string with one or more ministry titles. #' @return (`Minister`) Minister value object. -Minister <- function(member = Person(), title = NA_character_){ +Minister <- function(member = Person(), title = NA_character_) { stopifnot(is.Person(member), is.character(title)) stopifnot(nrow(member) == length(title) | all(is.na(title))) - + member |> dplyr::mutate(title = title) } ``` @@ -95,14 +94,14 @@ Third, we write a function that transforms **Person** into **Minister**. #' Then the parliament members are appointed to an office. #' @param memeber (`Person`) A Person value object. #' @return (`Minister`) Minister value object. -appoint_random_ministries <- function(member = Person()){ +appoint_random_ministries <- function(member = Person()) { positions <- c( "Arts, Culture and Heritage", "Finance", "Corrections", "Racing", "Sport and Recreation", "Housing", "Energy and Resources", "Education", "Public Service", "Disability Issues", "Environment", "Justice", "Immigration", "Defence", "Internal Affairs", "Transport" ) - + Minister(member = member, title = sample(positions, size = nrow(member))) } ``` @@ -115,8 +114,8 @@ Finally, we pair parliament members with ministries set.seed(2020) parliament_members <- Person( - given = c("Jacinda", "Grant", "Kelvin", "Megan", "Chris", "Carmel"), - family = c("Ardern", "Robertson", "Davis", "Woods", "Hipkins", "Sepuloni") + given = c("Jacinda", "Grant", "Kelvin", "Megan", "Chris", "Carmel"), + family = c("Ardern", "Robertson", "Davis", "Woods", "Hipkins", "Sepuloni") ) parliament_members From a544b85c025fa54690ce27a2a6a95d7ee84b1320 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 21:01:56 +1300 Subject: [PATCH 32/40] fix file paths --- R/base-Singleton.R | 2 +- R/base-ValueObject.R | 2 +- R/object_relational-Repository.R | 2 +- inst/snippets/submitting-package.R | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/base-Singleton.R b/R/base-Singleton.R index a0d3e0c..afa8d43 100644 --- a/R/base-Singleton.R +++ b/R/base-Singleton.R @@ -1,6 +1,6 @@ #' @title Singleton Pattern #' @name Singleton -#' @includeRmd vignettes/details/Singleton.Rmd +#' @includeRmd vignettes/details/_Singleton.Rmd #' @examples #' # See more examples at #' address <- function(x) sub("", "\\1", capture.output(x)) diff --git a/R/base-ValueObject.R b/R/base-ValueObject.R index 5d89210..2cd9f41 100644 --- a/R/base-ValueObject.R +++ b/R/base-ValueObject.R @@ -1,5 +1,5 @@ #' @title Value Object Pattern -#' @includeRmd vignettes/details/ValueObject.Rmd +#' @includeRmd vignettes/details/_ValueObject.Rmd #' @description Model a domain concept using natural lingo of the domain #' experts, such as “Passenger”, “Address”, and “Money”. #' @param given (`character`) A character vector with the given name. diff --git a/R/object_relational-Repository.R b/R/object_relational-Repository.R index ae6f1fd..cdf8741 100644 --- a/R/object_relational-Repository.R +++ b/R/object_relational-Repository.R @@ -1,6 +1,6 @@ #' @title Repository Pattern #' @name Repository -#' @includeRmd vignettes/details/Repository.Rmd +#' @includeRmd vignettes/details/_Repository.Rmd #' @examples #' # See more examples at #' diff --git a/inst/snippets/submitting-package.R b/inst/snippets/submitting-package.R index 986abdc..5e4ca00 100644 --- a/inst/snippets/submitting-package.R +++ b/inst/snippets/submitting-package.R @@ -1,5 +1,5 @@ # Lint the package -lintr::lint_package(show_progress = TRUE) +(report = lintr::lint_package(show_progress = TRUE)) styler::style_pkg( filetype = c("R", "Rprofile", "Rmd", "Rmarkdown", "Rnw", "qmd")[c(1,3)], exclude_dirs = c("packrat", "renv", ".dev", ".git", ".github", ".Rproj.user", "docs", "inst"), From 2336ff7d023c5d1d130730a86775e58feb3dfcd3 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 21:07:50 +1300 Subject: [PATCH 33/40] fix path --- R/base-Singleton.R | 1 - R/base-ValueObject.R | 1 - R/object_relational-Repository.R | 1 - vignettes/patterns/Repository.Rmd | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/R/base-Singleton.R b/R/base-Singleton.R index afa8d43..a51e27e 100644 --- a/R/base-Singleton.R +++ b/R/base-Singleton.R @@ -1,6 +1,5 @@ #' @title Singleton Pattern #' @name Singleton -#' @includeRmd vignettes/details/_Singleton.Rmd #' @examples #' # See more examples at #' address <- function(x) sub("", "\\1", capture.output(x)) diff --git a/R/base-ValueObject.R b/R/base-ValueObject.R index 2cd9f41..e03694e 100644 --- a/R/base-ValueObject.R +++ b/R/base-ValueObject.R @@ -1,5 +1,4 @@ #' @title Value Object Pattern -#' @includeRmd vignettes/details/_ValueObject.Rmd #' @description Model a domain concept using natural lingo of the domain #' experts, such as “Passenger”, “Address”, and “Money”. #' @param given (`character`) A character vector with the given name. diff --git a/R/object_relational-Repository.R b/R/object_relational-Repository.R index cdf8741..611b868 100644 --- a/R/object_relational-Repository.R +++ b/R/object_relational-Repository.R @@ -1,6 +1,5 @@ #' @title Repository Pattern #' @name Repository -#' @includeRmd vignettes/details/_Repository.Rmd #' @examples #' # See more examples at #' diff --git a/vignettes/patterns/Repository.Rmd b/vignettes/patterns/Repository.Rmd index 5e741b3..58c24d1 100644 --- a/vignettes/patterns/Repository.Rmd +++ b/vignettes/patterns/Repository.Rmd @@ -16,7 +16,7 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child=file.path(usethis::proj_get(), "vignettes", "details", "Repository.Rmd")} +```{r child=file.path(usethis::proj_get(), "vignettes", "details", "_Repository.Rmd")} ``` ## Implementations From 072a1a92c3475cd9b32809296f4f825d1ad6a27d Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 21:26:45 +1300 Subject: [PATCH 34/40] updated function documentation --- R/base-Singleton.R | 82 +++++++++++++++++++++++--------------------- R/base-ValueObject.R | 27 ++++++++------- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/R/base-Singleton.R b/R/base-Singleton.R index a51e27e..196cc36 100644 --- a/R/base-Singleton.R +++ b/R/base-Singleton.R @@ -1,11 +1,17 @@ -#' @title Singleton Pattern -#' @name Singleton +#' @title Singleton +#' +#' @description +#' Enforces a single instance of a class and provides a global access point. +#' +#' @details +#' This is an abstract base class. Instantiating `Singleton` directly triggers an error. +#' Classes inheriting from `Singleton` share a single instance. +#' #' @examples #' # See more examples at #' address <- function(x) sub("", "\\1", capture.output(x)) #' -#' # In this example we implement a `Counter` that inherits the qualities of -#' # Singleton +#' # In this example we implement a `Counter` that inherits the qualities of Singleton #' Counter <- R6::R6Class("Counter", inherit = R6P::Singleton, public = list( #' count = 0, #' add_1 = function() { @@ -14,18 +20,15 @@ #' } #' )) #' -#' # Whenever we call the constructor on `Counter`, we always get the exact same -#' # instance: +#' # Whenever we call the constructor on `Counter`, we always get the exact same instance: #' counter_A <- Counter$new() #' counter_B <- Counter$new() #' #' identical(counter_A, counter_B, ignore.environment = FALSE) #' -#' # The two objects are equal and located at the same address; thus, they are -#' # the same object. +#' # The two objects are equal and located at the same address; thus, they are the same object. #' -#' # When we make a change in any of the class instances, the rest of the -#' # instances are changed as well. +#' # When we make a change in any of the class instances, the rest of the instances are changed as well. #' #' # How many times has the counter been increased? #' counter_A$count @@ -36,35 +39,36 @@ #' # How many times have the counters been increased? #' counter_A$count #' counter_B$count -NULL - -#' @rdname Singleton -#' @description Ensure a class only has one instance, and provide a global point -#' of access to it. +#' #' @family base design patterns #' @export -Singleton <- R6::R6Class("Singleton", cloneable = FALSE, public = list( - #' @description Create or retrieve an object - initialize = function() { - classname <- get_classname() - if (classname == "Singleton") stop(paste(classname, "is an abstract base class and therefore cannot be instantiated directly")) - if (is.null(private$public_bind_env)) { - Class <- private$dynGet(classname) - - private$public_bind_env <- private$dynGet("public_bind_env") - private$private_bind_env <- private$dynGet("private_bind_env") - - Class$set("private", "public_bind_env", private$public_bind_env, overwrite = TRUE) - Class$set("private", "private_bind_env", private$private_bind_env, overwrite = TRUE) - } else { - self <- private$instance - private$dynSet("public_bind_env", private$public_bind_env) - private$dynSet("private_bind_env", private$private_bind_env) +Singleton <- R6::R6Class( + "Singleton", + cloneable = FALSE, + public = list( + #' @description Create or retrieve an object + initialize = function() { + classname <- get_classname() + if (classname == "Singleton") { + stop(paste(classname, "is an abstract base class and cannot be instantiated directly")) + } + if (is.null(private$public_bind_env)) { + Class <- private$dynGet(classname) + private$public_bind_env <- private$dynGet("public_bind_env") + private$private_bind_env <- private$dynGet("private_bind_env") + Class$set("private", "public_bind_env", private$public_bind_env, overwrite = TRUE) + Class$set("private", "private_bind_env", private$private_bind_env, overwrite = TRUE) + } else { + self <- private$instance + private$dynSet("public_bind_env", private$public_bind_env) + private$dynSet("private_bind_env", private$private_bind_env) + } } - } -), private = list( - public_bind_env = NULL, - private_bind_env = NULL, - dynGet = dynGet, - dynSet = dynSet -)) + ), + private = list( + public_bind_env = NULL, + private_bind_env = NULL, + dynGet = dynGet, + dynSet = dynSet + ) +) diff --git a/R/base-ValueObject.R b/R/base-ValueObject.R index e03694e..565485b 100644 --- a/R/base-ValueObject.R +++ b/R/base-ValueObject.R @@ -1,10 +1,14 @@ #' @title Value Object Pattern -#' @description Model a domain concept using natural lingo of the domain -#' experts, such as “Passenger”, “Address”, and “Money”. +#' +#' @description +#' Model a domain concept using natural lingo of domain experts, such as “Passenger,” “Address,” or “Money.” +#' #' @param given (`character`) A character vector with the given name. #' @param family (`character`) A character vector with the family name. +#' #' @family base design patterns #' @export +#' #' @examples #' # See more examples at #' @@ -18,9 +22,8 @@ #' # * Check that the input argument has all the columns that a Person has #' is.Person <- function(x) all(colnames(x) %in% colnames(Person())) #' -#' # A 'Minister' is a 'Person' with a ministry title. We capture that information -#' # in a new Value Object named 'Minister'. -#' # The Minister constructor requires two inputs: +#' # A 'Minister' is a 'Person' with a ministry title. The Minister constructor +#' # requires two inputs: #' # 1. (`Person`) Members of parliament #' # 2. (`character`) Ministry titles #' Minister <- function(member = Person(), title = NA_character_) { @@ -35,26 +38,24 @@ #' # Then the parliament members are appointed to an office. #' appoint_random_ministries <- function(member = Person()) { #' positions <- c( -#' "Arts, Culture and Heritage", "Finance", "Corrections", -#' "Racing", "Sport and Recreation", "Housing", "Energy and Resources", -#' "Education", "Public Service", "Disability Issues", "Environment", -#' "Justice", "Immigration", "Defence", "Internal Affairs", "Transport" +#' "Arts, Culture and Heritage", "Finance", "Corrections", "Racing", +#' "Sport and Recreation", "Housing", "Energy and Resources", "Education", +#' "Public Service", "Disability Issues", "Environment", "Justice", +#' "Immigration", "Defence", "Internal Affairs", "Transport" #' ) #' #' Minister(member = member, title = sample(positions, size = nrow(member))) #' } #' -#' # Listing New Zealand elected officials in 2020, we instantiate a Person Object, -#' # appoint them to random offices and return a Member value object. +#' # Listing New Zealand elected officials in 2020, we instantiate a Person object, +#' # appoint them to random offices, and return a Minister value object. #' set.seed(2020) -#' #' parliament_members <- Person( #' given = c("Jacinda", "Grant", "Kelvin", "Megan", "Chris", "Carmel"), #' family = c("Ardern", "Robertson", "Davis", "Woods", "Hipkins", "Sepuloni") #' ) #' #' parliament_members -#' #' appoint_random_ministries(member = parliament_members) ValueObject <- function(given = NA_character_, family = NA_character_) { From 9441e3f70830e1343fc432b27b748599b6345fc1 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 21:34:28 +1300 Subject: [PATCH 35/40] run devtools::document --- .github/workflows/R-CMD-check.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 5d5b1aa..b6f8ba2 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -27,9 +27,12 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::rcmdcheck + extra-packages: any::rcmdcheck, any::devtools needs: check + - name: Document the package + run: Rscript -e 'devtools::document()' + - uses: r-lib/actions/check-r-package@v2 with: upload-snapshots: true From 9bb2baaa35fd0c825aa2d83cb5c12007f5265436 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 21:41:47 +1300 Subject: [PATCH 36/40] render docs before creating website --- .github/workflows/pkgdown.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 8ba25c5..d0c1ea3 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -41,7 +41,10 @@ jobs: needs: website - name: Build site - run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + run: | + devtools::document() + rmarkdown::render("README.Rmd", output_format = "md_document") + pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) shell: Rscript {0} - name: Deploy to GitHub pages 🚀 From 372410da0a0429835027a08cb8b8c66462f3fef8 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 21:48:47 +1300 Subject: [PATCH 37/40] install roxygen2 --- .github/workflows/R-CMD-check.yaml | 6 ++++-- .github/workflows/pkgdown.yaml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index b6f8ba2..d05eda5 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -27,11 +27,13 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::rcmdcheck, any::devtools + extra-packages: any::rcmdcheck, any::roxygen2 needs: check - name: Document the package - run: Rscript -e 'devtools::document()' + run: | + roxygen2::roxygenise() + shell: Rscript {0} - uses: r-lib/actions/check-r-package@v2 with: diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index d0c1ea3..64a92a9 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -37,12 +37,12 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::pkgdown, local::. + extra-packages: any::pkgdown, any::roxygen2, local::. needs: website - name: Build site run: | - devtools::document() + roxygen2::roxygenise() rmarkdown::render("README.Rmd", output_format = "md_document") pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) shell: Rscript {0} From f132871e0eafb4d0a751584d19cf261b608bc087 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 21:54:15 +1300 Subject: [PATCH 38/40] run code coverage --- .github/workflows/pkgdown.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 64a92a9..7838880 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -37,7 +37,7 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::pkgdown, any::roxygen2, local::. + extra-packages: any::pkgdown, any::roxygen2, any::covr, local::. needs: website - name: Build site @@ -45,6 +45,7 @@ jobs: roxygen2::roxygenise() rmarkdown::render("README.Rmd", output_format = "md_document") pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + covr::codecov(type = "all") shell: Rscript {0} - name: Deploy to GitHub pages 🚀 From c2d1fa17cf445540e3b607f12d4b9fa36a91c80a Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 21:59:50 +1300 Subject: [PATCH 39/40] install usethis --- .github/workflows/pkgdown.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 7838880..7848a08 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -37,7 +37,12 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::pkgdown, any::roxygen2, any::covr, local::. + extra-packages: | + any::pkgdown, + any::roxygen2, + any::covr, + any::usethis, + local::., needs: website - name: Build site From 4be2d889cf6548423861ededa99ff0a4d6c82862 Mon Sep 17 00:00:00 2001 From: harell Date: Sun, 22 Dec 2024 23:14:15 +1300 Subject: [PATCH 40/40] update file paths --- .Rbuildignore | 12 +++--------- .dev/CRAN/prepare-for-release.R | 8 ++++++++ .dev/docker/r-test/Dockerfile | 31 ------------------------------ .dockerignore | 15 --------------- .github/workflows/pkgdown.yaml | 2 ++ CRAN-SUBMISSION | 3 +++ R/base-Singleton.R | 2 +- cran-comments.md | 11 +++++++---- docker-compose.yml | 14 -------------- inst/snippets/submitting-package.R | 25 ------------------------ vignettes/patterns/NullObject.Rmd | 3 ++- vignettes/patterns/Repository.Rmd | 3 ++- vignettes/patterns/Singleton.Rmd | 4 +++- vignettes/patterns/ValueObject.Rmd | 3 ++- 14 files changed, 33 insertions(+), 103 deletions(-) delete mode 100644 .dev/docker/r-test/Dockerfile delete mode 100644 .dockerignore create mode 100644 CRAN-SUBMISSION delete mode 100644 docker-compose.yml delete mode 100644 inst/snippets/submitting-package.R diff --git a/.Rbuildignore b/.Rbuildignore index 356fb6f..2452b83 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -2,6 +2,7 @@ ^\..*/ ^\.Rproj\.user$ .*_cache/ +^\.github$ ^data-raw/ ^inst/snippets/ ^revdep/ @@ -16,17 +17,10 @@ ^CRAN-RELEASE$ ^README\.Rmd$ ^LICENSE\.md$ -^vignettes/_common.R$ -^vignettes/references.bib$ - -# Docker -^docker-compose\.yml$ -^R/utils-DockerCompose\.R$ # pkgdown$ ^docs$ ^pkgdown$ - - -^\.github$ +# cran +^CRAN-SUBMISSION$ diff --git a/.dev/CRAN/prepare-for-release.R b/.dev/CRAN/prepare-for-release.R index a77b2e4..c5654b1 100644 --- a/.dev/CRAN/prepare-for-release.R +++ b/.dev/CRAN/prepare-for-release.R @@ -6,6 +6,14 @@ Sys.setenv(`_R_DEPENDS_ONLY` = "true") remotes::install_cran(c("devtools", "urlchecker", "rhub", "revdepcheck")) # remotes::install_github("r-lib/revdepcheck@master") +# Printing Code ----------------------------------------------------------- +# Lint the package +(report = lintr::lint_package(show_progress = TRUE)) +styler::style_pkg( + filetype = c("R", "Rprofile", "Rmd", "Rmarkdown", "Rnw", "qmd")[c(1,3)], + exclude_dirs = c("packrat", "renv", ".dev", ".git", ".github", ".Rproj.user", "docs", "inst"), + include_roxygen_examples = TRUE, +) # Steps ------------------------------------------------------------------- # devtools::build_readme() diff --git a/.dev/docker/r-test/Dockerfile b/.dev/docker/r-test/Dockerfile deleted file mode 100644 index 958a484..0000000 --- a/.dev/docker/r-test/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -# R Package Development: Core -------------------------------------------------- -FROM tidylab/package:4.1.0 - -# Install Project Dependencies ------------------------------------------------- -COPY ./DESCRIPTION ./DESCRIPTION -RUN R -q -e "install_deps(dependencies = 'Depends')" -RUN R -q -e "install_deps(dependencies = 'Imports')" -RUN R -q -e "install_deps(dependencies = 'Suggests')" - -# R Package Development: Testing------------------------------------------------ -RUN touch .Renviron .Rprofile -RUN echo "" > .Rprofile - -# Prepare Package Files -------------------------------------------------------- -ARG R_USER=./home/rstudio/ -ARG R_PACKAGE_NAME=rproject -COPY . ${R_USER}/${R_PACKAGE_NAME} -RUN cp .Rprofile ${R_USER}/${R_PACKAGE_NAME} -RUN cp .env ${R_USER}/.Renviron -WORKDIR ${R_USER}/${R_PACKAGE_NAME} - -# Test-Suite ------------------------------------------------------------------- -RUN R -q -e "system.time(devtools::document())" -RUN R -q -e "system.time(devtools::check(error_on = 'note'))" -RUN R -q -e "system.time({\ - devtools::load_all(export_all = FALSE, helpers = FALSE);\ - testthat::test_dir('./tests/testthat', stop_on_failure = TRUE)\ - })" - -# Teardown --------------------------------------------------------------------- -ENTRYPOINT /bin/bash diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 4bc4847..0000000 --- a/.dockerignore +++ /dev/null @@ -1,15 +0,0 @@ -# Folders -.*/ -cache/ -inst/docs/ -man/ -vignettes/_cache/ - -# Files -.* -!.Rbuildignore -^docker-compose.yml$ - -# Demo -./R/demo-* -./tests/testthat/test-demo-* diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 7848a08..6290568 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -42,6 +42,7 @@ jobs: any::roxygen2, any::covr, any::usethis, + any::pkgload, local::., needs: website @@ -49,6 +50,7 @@ jobs: run: | roxygen2::roxygenise() rmarkdown::render("README.Rmd", output_format = "md_document") + pkgload::load_all() pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) covr::codecov(type = "all") shell: Rscript {0} diff --git a/CRAN-SUBMISSION b/CRAN-SUBMISSION new file mode 100644 index 0000000..575fa14 --- /dev/null +++ b/CRAN-SUBMISSION @@ -0,0 +1,3 @@ +Version: 0.4.0 +Date: 2024-12-22 09:21:16 UTC +SHA: c2d1fa17cf445540e3b607f12d4b9fa36a91c80a diff --git a/R/base-Singleton.R b/R/base-Singleton.R index 196cc36..3f4da6e 100644 --- a/R/base-Singleton.R +++ b/R/base-Singleton.R @@ -28,7 +28,7 @@ #' #' # The two objects are equal and located at the same address; thus, they are the same object. #' -#' # When we make a change in any of the class instances, the rest of the instances are changed as well. +#' # When we make a change in any of the class instances, the rest are changed as well. #' #' # How many times has the counter been increased? #' counter_A$count diff --git a/cran-comments.md b/cran-comments.md index b51aeb0..546740e 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,6 +1,9 @@ +## v0.4.0 Fix Notes +* Fixed the check problems shown on + ## v0.2.2 Bug fix -* Include the tibble pacakge in "Imports" +* Include the tibble package in "Imports" ## v0.2.1 Update @@ -18,9 +21,9 @@ * Discard examples from changing the global environment ## Test environments -* windows (on github-actions), R 4.0.3 -* ubuntu 18.04.5 (on github-actions), R 4.0.3 -* macOS 10.15.7 (on github-actions), R 4.0.3 +* windows (on github-actions), R 4.4.2 +* ubuntu 18.04.5 (on github-actions), R 4.4.2 +* macOS 10.15.7 (on github-actions), R 4.4.2 ## R CMD check results diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index aa2d3d2..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -services: - ###################################################### - # R Testing - ###################################################### - r-test: - image: r-package/r6p - build: - context: ./ - dockerfile: ./.dev/docker/r-test/Dockerfile - entrypoint: '/bin/bash' - container_name: r_test - restart: "no" - ###################################################### -version: "3.8" diff --git a/inst/snippets/submitting-package.R b/inst/snippets/submitting-package.R deleted file mode 100644 index 5e4ca00..0000000 --- a/inst/snippets/submitting-package.R +++ /dev/null @@ -1,25 +0,0 @@ -# Lint the package -(report = lintr::lint_package(show_progress = TRUE)) -styler::style_pkg( - filetype = c("R", "Rprofile", "Rmd", "Rmarkdown", "Rnw", "qmd")[c(1,3)], - exclude_dirs = c("packrat", "renv", ".dev", ".git", ".github", ".Rproj.user", "docs", "inst"), - include_roxygen_examples = TRUE, -) - - -devtools::check(remote = TRUE, manual = TRUE) - -rhub::check(platform="windows-x86_64-devel", env_vars=c(R_COMPILE_AND_INSTALL_PACKAGES = "always")) -# rhub::rhub_setup() -# rhub::rhub_check(platforms = c("windows", "macos", "linux"), branch = "master") - -# devtools::check_win_devel() -devtools::check_win_release() - -devtools::build_manual() - -devtools::spell_check() - -utils::maintainer(pkgload::pkg_name()) - -# devtools::submit_cran() diff --git a/vignettes/patterns/NullObject.Rmd b/vignettes/patterns/NullObject.Rmd index e0bb5f8..57d9eea 100644 --- a/vignettes/patterns/NullObject.Rmd +++ b/vignettes/patterns/NullObject.Rmd @@ -16,7 +16,8 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child=file.path(usethis::proj_get(), "vignettes", "details", "_NullObject.Rmd")} +```{r, echo=FALSE, results='asis'} +cat(knitr::knit_child('./vignettes/details/_NullObject.Rmd', quiet = TRUE)) ``` ## Example: Null ggplot2 diff --git a/vignettes/patterns/Repository.Rmd b/vignettes/patterns/Repository.Rmd index 58c24d1..9de6078 100644 --- a/vignettes/patterns/Repository.Rmd +++ b/vignettes/patterns/Repository.Rmd @@ -16,7 +16,8 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child=file.path(usethis::proj_get(), "vignettes", "details", "_Repository.Rmd")} +```{r, echo=FALSE, results='asis'} +cat(knitr::knit_child('./vignettes/details/_Repository.Rmd', quiet = TRUE)) ``` ## Implementations diff --git a/vignettes/patterns/Singleton.Rmd b/vignettes/patterns/Singleton.Rmd index 6c9687c..efbee99 100644 --- a/vignettes/patterns/Singleton.Rmd +++ b/vignettes/patterns/Singleton.Rmd @@ -16,9 +16,11 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child=file.path(usethis::proj_get(), "vignettes", "details", "_Singleton.Rmd")} +```{r, echo=FALSE, results='asis'} +cat(knitr::knit_child('./vignettes/details/_Singleton.Rmd', quiet = TRUE)) ``` + ## Example: Counter {#example-1} In this example we implement a `Counter` that inherits the qualities of diff --git a/vignettes/patterns/ValueObject.Rmd b/vignettes/patterns/ValueObject.Rmd index 88e236f..8a9537b 100644 --- a/vignettes/patterns/ValueObject.Rmd +++ b/vignettes/patterns/ValueObject.Rmd @@ -16,7 +16,8 @@ source(file.path(usethis::proj_get(), "vignettes", "_common.R")) -```{r child=file.path(usethis::proj_get(), "vignettes", "details", "_ValueObject.Rmd")} +```{r, echo=FALSE, results='asis'} +cat(knitr::knit_child('./vignettes/details/_ValueObject.Rmd', quiet = TRUE)) ```