R Under development (unstable) (2025-10-08 r88906 ucrt) -- "Unsuffered Consequences" Copyright (C) 2025 The R Foundation for Statistical Computing Platform: x86_64-w64-mingw32/x64 R is free software and comes with ABSOLUTELY NO WARRANTY. You are welcome to redistribute it under certain conditions. Type 'license()' or 'licence()' for distribution details. R is a collaborative project with many contributors. Type 'contributors()' for more information and 'citation()' on how to cite R or R packages in publications. Type 'demo()' for some demos, 'help()' for on-line help, or 'help.start()' for an HTML browser interface to help. Type 'q()' to quit R. > if (!requireNamespace("data.table", quietly = TRUE)) { + message("SKIP: data.table not available; skipping test-locked-bindings.R") + quit(save = "no", status = 0, runLast = FALSE) + } > > library(data.table) > > # it is possible for a end user to modify the internal objects, this is a R > # problem. For example, if you use data.table::setDT on datasets::mtcars you can > # modify the object. The following example worked as of 29 Aug 2025 in R 4.5.1 > # and datasets_4.5.1. > # > #> setDT(datasets::mtcars) > #> set(datasets::mtcars, j = "cyl", value = 1L) > #> stopifnot(all(mtcars[["cyl"]] == 1L)) > #> stopifnot(all(datasets::mtcars[["cyl"]] == 1L)) > > # medicalcoder tries to prevent this for its lookup tables. Importally, :w > # > # several user friendly data sets in .onLoad. If a end user tries to > # modify the non-exported internal objects, only accessable via ::: then the > # namespace is loaded _before_ anything else can be done and the data sets used > # within the package should be preserved. > setDT(medicalcoder:::..mdcr_internal_icd_codes..) > x <- medicalcoder:::..mdcr_internal_icd_codes.. > > # bad user, don't modify things, even if you can! > x[, icdv := 8L] > stopifnot(medicalcoder:::..mdcr_internal_icd_codes..[, all(icdv == 8L)]) > > # but the good news is that when the namespace was loaded the data set that > # needed to be provided to the user and is called within the package via > # get_icd_codes is not modified! > x <- medicalcoder::get_icd_codes() > stopifnot(x[["icdv"]] %in% c(9L, 10L)) > > # now, what about getting at the built objects? > # first, the ..mdcr_data_env.. is not accessable other than by ::: > library(medicalcoder) > stopifnot(medicalcoder:::..mdcr_internal_icd_codes..[, all(icdv == 8L)]) # still bad, but... > > # the environment is hard to get to > x <- tryCatch(..mdcr_data_env.., error = function(e) e) > stopifnot(inherits(x, "error")) > x <- tryCatch(medicalcoder::..mdcr_data_env.., error = function(e) e) > stopifnot(inherits(x, "error")) > > x <- medicalcoder:::..mdcr_data_env.. > stopifnot(is.environment(x)) > # and modifing the data will error > t <- tryCatch(x$icd_codes[["icdv"]] <- 11L, error = function(e) e) > stopifnot(inherits(t, "error")) > > # data.table will also fail, at first. Note that accessing the icd_codes right > # now it is just a data.frame > stopifnot( + is.data.frame(medicalcoder:::..mdcr_data_env..$icd_codes), + !is.data.table(medicalcoder:::..mdcr_data_env..$icd_codes) + ) > > # an error is thrown, but the class has been modified and it is now a data.table > t <- tryCatch(setDT(x$icd_codes), error = function(e) e) > stopifnot(inherits(t, "error")) > stopifnot( + is.data.frame(medicalcoder:::..mdcr_data_env..$icd_codes), + is.data.table(medicalcoder:::..mdcr_data_env..$icd_codes) + ) > > # so now, end user could modify the object and the return from get_icd_codes() > # will reflect this change > stopifnot(all(get_icd_codes()[["icdv"]] %in% c(9L, 10L))) > medicalcoder:::..mdcr_data_env..$icd_codes[, icdv := 98L] > stopifnot(all(get_icd_codes()[["icdv"]] == 98L)) > > # so, yeah, end users could mess things up but if someone does this that is on > # them. Importantly, the get_x functions use > # > # unserialize(serialize(x, connection = NULL)) > # > # to ensure end users only get deep copies of the chached data sets. One quick > # check. > detach("package:medicalcoder", unload = TRUE) > library(medicalcoder) > x <- get_icd_codes() > setDT(x) > y <- data.table::copy(x) > x[, icdv := 42L] > z <- get_icd_codes() > > stopifnot( + all(y[["icdv"]] %in% c(9L, 10L)), + all(z[["icdv"]] %in% c(9L, 10L)), + all(x[["icdv"]] == 42L) + ) > > ################################################################################ > # End of File # > ################################################################################ > > proc.time() user system elapsed 6.04 0.53 6.56