Skip to content
/ oopR Public

Implementation and best practices for object oriented programming in R.

Notifications You must be signed in to change notification settings

bill-ash/oopR

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

oopR

A how to guide for implementing a S3 object oriented programming style in R.

Building data structures in R.

limited number of complex objects.

library(tibble)

print

UseMethod('print')

print.data.frame

Simple exercise

# Creates a constructor with a class of 'qb_journal'
qb_journal <- function(RefNum = NULL, 
                       TxnDate = NULL, 
                       DebitAccount = NULL, 
                       DebitClass = NULL, 
                       DebitEntity = NULL,
                       CreditAccount = NULL, 
                       CreditClass = NULL, 
                       CreditEntity = NULL, 
                       Amount = NULL, 
                       Memo = NULL, 
                       ...) {
  journal <- structure(
    list(
      RefNum= RefNum,
      TxnDate = TxnDate,
      DebitAccount= DebitAccount,
      DebitClass= DebitClass,
      DebitEntity = DebitEntity,
      CreditAccount = CreditAccount,
      CreditClass = CreditClass,
      CreditEntity= CreditEntity,
      Amount= Amount,
      Memo= Memo
    ), class = 'qb_journal'
  )
  
  return(journal)
}

# Build a journal entry object 
journal_entry <- qb_journal(
  RefNum = '100',
  TxnDate = as.Date('2020-01-01'), 
  DebitAccount = 'Advertising Expense',
  CreditAccount = 'Accounts Payable',
  Amount = 100.23,
  Memo = 'Advertising expense for new ad campaign'
)

# Confirm the class == qb_journal and has a names attribute with all of the 
# variables need for creating a journal entry 
attributes(journal_entry)

# Create a generic qb_add function for adding new information  to QuickBooks
qb_add <- function(x, ...) {
  UseMethod('qb_add')
}

# Helper insert statement - will format the SQL for creating a new journal entry. 
.insert_statement <- function(table = NULL, 
                              qb_object = NULL) {
  if (is.null(table)) {
    stop("Must choose a table to insert.", call. = FALSE)
  }
  
  qb_object <- purrr::compact(qb_object)
  
  values <- purrr::map_if(qb_object, is.character, ~paste0("'", gsub("'", "''", .x), "'"))
  
  values <- paste0(values, collapse = ", ")
  
  ins_names <- paste0(names(qb_object), collapse = ", ")
  
  stmt <- paste0('INSERT INTO ', table, ' (', ins_names, ') VALUES (', values, ')')
  
  return(stmt)
}
# Validate there is good SQL 
.insert_statement('SomePath', list(RefNum = '100', Date = '2020-01-01', Amount = 100))
.insert_statement('SomePath', journal_entry)

# Create a method for the qb_add generic to add new journal entries to QuickBooks
qb_add.qb_journal <- function(x, ...) {
  
  journal <- list(
    debit = .insert_statement('JournalDebitLine', x),
    credit = .insert_statement('JournalCreditLine', x),
    header = .insert_statement('Journal', x)
  )
  
  return(journal) 
  
}

# Create a method for printing journal entries 
print.qb_journal <- function(x, ...) {
  cat('\nNew journal entry no. xxxx-xxx-xxx\n\n\n')
  dplyr::as_tibble(purrr::compact(x))
}

# Test it out: 
print(journal_entry)
# You do not need to wrap the object in print to get the desired behavior
# of your generic print function 
journal_entry

# The output 
qb_add(journal_entry)
# Looking at the Date method for the generic function print reveals a `max` 
# argument. This means when printing Date objects, we can specify an additional 
# argument `max` to print that will limit the length of the Date vector printed 
# to the consle. 
print.Date

x_date <- as.Date('2020-01-01')
dataframe_dates <- data.frame(dates = c(as.Date('2020-01-01'), '2020-05-02', '2022-05-06'))

attributes(x_date)
typeof(x_date)
unclass(x_date)

attributes(dataframe_dates)
typeof(dataframe_dates)
unclass(dataframe_dates)

attributes(dataframe_dates$dates)
typeof(dataframe_dates$dates)
unclass(dataframe_dates$dates)

print(x_date, max = 10)
print(dataframe_dates$dates, max = 1)

Getting started

Below are a list of helpful links for learning more about object oriented programming, the benefits, and how to do it in R.

SO - https://stackoverflow.com/questions/46393146/r-creating-s3-object-with-separate-internal-and-external-names

S3

S3 allows you to overload functions by defining different methods for one generic. The arguments for each method must contain all the arguments to the generic. Both the generic and the method should contain a … argument.

Use ls.str() to get a better picture of what your list or environemnt object contain.

pp <- list(a = 1, b = c(2, 5, 6, 7), c = 3)
ls.str(pp)

The print generic function could have an unlimited number of methods using the convention outlined below.

someObject <- function(x, y) {
  structure(
    list(
      x = x, 
      y = y
    ), class = 'someObject'
  )
}

print.someObject <- function(x, ...) {
  cat('Print is a generic function.\n')
  cat('You can extend the utility of print by creating new methods.\n')
  cat('Create a new method by writing a function like:\n    `print.new_method`.\n\n')
  
  value <- unlist(x)
  key <- names(x) 
  
  z <- paste0(key, ' = ', value, collapse = '\n')
  cat(z)
  
}

the_object <- someObject(x = 'howdy', y = 'partner')

is.object(the_object)
sloop::otype(the_object)

for the generic.

About

Implementation and best practices for object oriented programming in R.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published