Skip to content

Commit

Permalink
Call private$post_clone() after cloning
Browse files Browse the repository at this point in the history
If a class defines a post_clone() method, it will automatically be called on the newly created object.

This is convenient because post_clone() allows to modify private fields of the new object at its creation.

See
r-lib#179 (comment)
  • Loading branch information
zeehio committed Apr 16, 2023
1 parent 22f81f3 commit ceaeb20
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 1 deletion.
6 changes: 5 additions & 1 deletion R/clone.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This function will be added as a method to R6 objects, with the name '.clone',
# and with the environment changed.
generator_funs$.clone_method <- function(deep = FALSE) {
generator_funs$.clone_method <- function(deep = FALSE, post_clone_args = list()) {
# Need to embed these utility functions inside this closure because the
# environment of this function will change.

Expand Down Expand Up @@ -367,6 +367,10 @@ generator_funs$.clone_method <- function(deep = FALSE) {

class(new_1_binding) <- class(old_1_binding)

if (has_private && is.function(new[[1]]$private$post_clone)) {
do.call(new[[1]]$private$post_clone, post_clone_args)
}

new_1_binding
}

Expand Down
68 changes: 68 additions & 0 deletions tests/testthat/test-clone.R
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,74 @@ test_that("Custom clone() can call .clone() and modify the original", {
expect_equal(b$x, 1)
})

test_that("post_clone() can change fields on new object", {
AC <- R6Class("AC",
public = list(
x = 1,
increment_counter = function() {
private$counter <- private$counter + 1
},
get_counter = function() {
private$counter
}
),
private = list(
counter = 0,
post_clone = function() {
# reset the counter on clone()
private$counter <- 0
}
)
)

a <- AC$new()
a$increment_counter()
b <- a$clone()
# x is cloned:
expect_equal(a$x, 1)
expect_equal(b$x, 1)
# counter is reset:
expect_equal(a$get_counter(), 1)
expect_equal(b$get_counter(), 0)
})

test_that("post_clone() accepts custom arguments", {
AC <- R6Class("AC",
public = list(
x = 1,
increment_counter = function() {
private$counter <- private$counter + 1
},
get_counter = function() {
private$counter
},
clone = function(deep = FALSE, new_counter = 0) {
self$.clone(deep = deep, post_clone_args = list(counter = new_counter))
}
),
private = list(
counter = 0,
post_clone = function(counter) {
# reset the counter on clone()
private$counter <- counter
}
)
)

a <- AC$new()
expect_equal(a$get_counter(), 0)
a$increment_counter()
expect_equal(a$get_counter(), 1)
b <- a$clone()
# x field was cloned:
expect_equal(a$x, 1)
expect_equal(b$x, 1)
# counter was reset on the copy, but not on the original:
expect_equal(a$get_counter(), 1)
expect_equal(b$get_counter(), 0)
})


test_that("Cloning portable objects with public only", {
parenv <- new.env()
AC <- R6Class("AC",
Expand Down

0 comments on commit ceaeb20

Please sign in to comment.