Teraz, gdy mamy już zdefiniowany interfejs Database, zaimplementujemy system podobny do bazy danych, który używa plików CSV do przechowywania informacji zamiast rzeczywistej bazy danych. Po pierwsze, upewniamy się, że przenosimy zależności klasy CSVFiles za pomocą funkcji source(), aby przenieść pliki, które mają potrzebne definicje. W szczególności wprowadzamy klasy Exchange i User (które zostaną zdefiniowane później), a także interfejs Database. Definiujemy również stałą DIR z katalogiem, który będzie zawierał pliki CSV z naszymi danymi systemu. Rzeczywista klasa CSVFile jest definiowana przy użyciu standardowych metod R6 przedstawionych wcześniej. Zauważ, że dziedziczy on z klasy Database i zapewnia przesłonięcia dla każdej metody w interfejsie Database, tak jak powinno. Zwróć również uwagę, że wewnątrz konstruktora, czyli funkcji initilize, wywołujemy funkcję initialize_csv_files() i wysyłając listę table_names, którą otrzymujemy podczas inicjalizacji. Ponieważ chcieliśmy, aby czytelnik zapoznał się z pełną definicją klasy w pojedynczym fragmencie kodu, a nie kawałek po kawałku, zawarliśmy tutaj wszystko i wyjaśnimy w kolejnych akapitach. Jest trochę za długi, ponieważ zawiera logikę dla wszystkich metod w interfejsie Database, ale na wysokim poziomie jest to nic innego jak implementacja wspomnianego interfejsu. Ponieważ chcieliśmy, aby czytelnik zapoznał się z pełną definicją klasy w pojedynczym fragmencie kodu, a nie kawałek po kawałku, zawarliśmy to wszystko tutaj i wyjaśnimy w kolejnych akapitach. Jest trochę za długi, ponieważ zawiera logikę dla wszystkich metod w interfejsie Database, ale na wysokim poziomie to nic innego jak implementacja wspomnianego interfejsu:
source(„../assets/exchange/exchange.R”. hdir = TRUE)
source(„../users/user.R”, chdir = TRUE)
source(„.database.R”)
DIR <- „./csv-files/”
CSVFiles <-R6Class (
„CSVFiles”,
inherit = Database,
public = list (
initialize – function(table_names) {
super$set_table_names(table_names)
initialize_csv_files(table_names)
},
read_exchanges = function() {
return(list (Exhange$new(„CoinMarketCap{)))
},
read_users = function(storage) {
data <- private$read_csv(„users”)
return(lapply(data$email, user_constructor, storage))
},
read_wallets = function(email) {
data <- private$read_csv(„wallets)
wallets <- NULL
if (nrow(data) >= 1) {
for ( i in 1:nrow(data)) {
if (data[i, „email”] == email) {
wallets <- c(wallets, list(Wallet$new(
data[i, „email”],
data[i, „symbol”],
data[i, „address”],
data[i, „note”])
))
}
}
} else { wallets <- list() }
return(wallets)
},
read_all_wallets = function() {
data <- private$read_csv(„wallets”)
wallets <- NULL
if (nrow(data) >= 1 ) {
for (i in 1 L nrow(data) ) {
wallets <- c(wallets, list(Wallet$new (
data[i, „email”],
data[i, „symbol”],
data[i, „address”],
data[i, „note”])
))
}
}else {wallets <- list() }
return(wallets)
},
write_user = function(user) {
data <- private$read_csv(„users”)
new_row <- as.data.frame(data$3(user))
print(new_row)
if (private$user_does_not_exist(user, data)) {
data <- rbind(data , new_row)
}
private$write_csv(„users”, data)
},
write_wallets = function(wallets) {
data <- private$read_csv(„wallets”)
for (wallet in wallets)
new_row <- as.data.frame(wallet$data() )
print(new_row)
if (private$wallet_does_not_ exist(wallet, data)) {
data <- rbind(data, new_row)
}
}
private$write_csv(„wallets”, data)
},
write_assets = function(assets) {
data <- private$read_csv(„assets”)
for (asset in assets) {
new_row <- as.data.frame(dataS4(asset))
print(new_row)
data <-rbind(data, new_row)
}
private$write_csv(„assets”, data)
}.
write_markets = function(markets) {
data <- private$read_csv(„markets”)
for (marjet in markets) {
new_row <- as.data.frame(market$data() )
print(new_row)
data <- rbind(data, new_row)
}
private$write)csv(„markets”, data)
}
),
private = list(
read_csv = function(table_name) {
return(read.sv (
private$file(table_name),
stringsAsFactors = FALSE))
},
write_csv = function(table_name, data) {
write.csv(data,
file = private$file(table_name),
row.names = FALSE)
},
file = function(table_name) {
return(paste (
DIR, suoer$get_table_names()[[table_name]],
„.csv”, sep = „ „))\
},
user_does_not_exist = function(user, data) {
if (dataS3(user) [[„email]] %in% data$email) {
return(FALSE)
}
return(TRUE)
},
wallet_does_not_exist = function(wallet, data) {
current_addresses <-data[
data$email == wallet$get_email()&
data$symbol == wallet$get_symbol(),
„address”
]
if (wallet$get_address() %in% current_addresses_ {
return(FALSE)
}
return(TRUE)
}
)
)
Teraz pokrótce wyjaśnimy mechanikę implementacji każdej metody. Zacznijmy od read_exchanges() Teoretycznie metoda ta powinna zajrzeć do przechowywanych danych i pobrać listę central zarejestrowanych w systemie, utworzyć instancję dla każdej z nich i odesłać ją. W praktyce nie jest to jednak konieczne, ponieważ samo zakodowanie giełdy CoinMarketCap jest wystarczające do naszych celów. Jak widać, to wszystko, co robi metoda: zwraca listę z pojedynczym Exchange w środku, czyli taką dla CoinMarketCap. Metoda read_users() odczytuje dane z pliku „user” metodą prywatną read_csv() zdefiniowaną poniżej i zwraca listę utworzoną za pomocą funkcji lapply(), która pobiera każdy e-mail w danych i wysyła go przez user_construtor() wraz z obiektem storage odebrany jako parametr do utworzenia instancji User , które są następnie odsyłane w wyniku wywołania metody. Metoda read_wallets() jest nieco bardziej złożona. Otrzymuje email jako parametr, odczytuje plik „wallets” i tworzy listę instancji Wallet. Ponieważ sprawdzamy, czy konkretna obserwacja w danych zawiera email równą żądanej, możemy po prostu użyć funkcji lapply() (moglibyśmy, gdybyśmy utworzyli oddzielną funkcję która zawiera to, sprawdź, ale decydujemy się nie iść tą trasą). Należy również zauważyć, że funkcja będzie próbować iterować po wierszu w ramce danych tylko wtedy, gdy ramka danych zawiera co najmniej jeden wiersz. To sprawdzenie zostało wprowadzone po tym, jak odkryliśmy, że gdy nie było go, gdy mieliśmy puste pliki, otrzymywaliśmy błąd, ponieważ pętla for była faktycznie wykonywana, nawet jeśli nie było żadnych wierszy. Jeśli okaże się, że email jest takie samo, jak żądane, dodajemy nową instancję Wallet do listy wallets i zwracamy ją. Jeśli nie ma portfeli do utworzenia, obiekt wallets jest przekształcany w pustą listę. Metoda read_all_wallets() działa w ten sam sposób, ale pomija kontrolę email. Metoda write_user() otrzymuje instancję User, odczytuje data dla pliku „users”, tworzy ramkę danych z danymi wyodrębnionymi za pomocą funkcji dataS3 wywołaną z obiektu User, wypisuje ją na konsoli w celach informacyjnych, a jeśli nie zostanie znaleziona w bieżących danych, zostanie do niej dodana. Na koniec dane są zapisywane z powrotem do pliku „users”. Faktyczne sprawdzenie jest wykonywane metodą prywatną user_does_not_exist(), która po prostu sprawdza, czy e-mail User nie jest zawarty w kolumnie email w danych, jak widać we wspomnianej wcześniej definicji. Metoda write_wallets() otrzymuje listę instancji Wallet, odczytuje plik „wallets” i dodaje go dla każdego wallet, którego nie znaleziono już w danych. Koncepcyjnie jest podobna do metody write_user(), a sprawdzenie jest wykonywane przez prywatną metodę wallet_does_not_exist() , która odbiera instancję Wallet i używa zawartych w niej email i symbol, aby uzyskać addresses, które są już skojarzone z takimi kombinacjami (przypomnij sobie, że jeden użytkownik może mieć wiele portfeli dla tego samego rodzaju aktywów i byłby rozróżniany tylko na podstawie adresów portfeli). Jeśli okaże się, że address w instancji Wallet już istnieje w takim podzbiorze, nie jest dodawane.
Metody write_assets() i write_markets() są podobne i należy je łatwo zrozumieć. Różnica polega na tym, że na razie nie zawierają one żadnych sprawdzeń i zapisują odpowiednio obiekty S4 i R6. Możesz to stwierdzić po tym, że wywołują metodę datS4() i składnię, aby uzyskać dane Market, będąc market$data(). Prywatne metody używane do odczytu i zapisu plików CSV powinny być łatwe do zrozumienia. Pamiętaj tylko, że rzeczywiste nazwy plików pochodzą z metody prywatnej file(), która używa table_names zawartej w nadklasie (Database) przez wywołanie metody pobierającej super$get_table_names() i pobranie odpowiedniej nazwy pliku skojarzonego z daną table_name. Lista table_name zostanie później zdefiniowana w scentralizowanym pliku ustawień, ale jest po prostu listą zawierającą ciąg znaków dla każdej nazwy tabeli (w przypadku CSVFiles nazwa pliku) skojarzone z każdym typem obiektu, który ma być przechowywany.
Teraz przechodzimy do omawiania funkcji initialize_csv_files() . Ta funkcja otrzymuje listę table_names i upewnia się, że katalog DIR istnieje z funkją dir.create(). Parametr showWarnings = FALSE ma na celu uniknięcie ostrzeżeń kiedy katalog już istnieje na dysku. Następnie dla każdego elementu na liście table_names utworzy odpowiedni filename i sprawdzi, czy istnieje na dysku z funkji file.exist() . Jeśli tak się nie stanie, rozpocznie tworzenie pustej ramki danych pliku odpowiedniego typ i zapisz go na dysku:
initialize_csv_files <-funtion(table_names) {
dir.create(DI, showWarnings = FALSE)
for (table in table_names) {
filename <- paste(DIR, table, „.csv”, sep = „ „)
if (!file.exist(filena,e) ) {
data <- empty_dataframe(table)
write.csv(data, file = filename, row.naes =FALSE)
}
}
}
Różne typy pustych ramek danych jest wybieranych funkcją empty_dataframe(), która otrzymuje określoną nazwę tabeli w parametrze table i zwraca odpowiadającą jej pustą ramkę danych. Zauważ, że testy zakładają, że słowa dla różnych obiektów, które należy zapisać, znajdują się w nazwach tabel zdefiniowanych w scentralizowanym pliku ustawień i że nazwy dwóch różnych abstrakcji nie pojawiają się razem w jednej nazwie tabeli:
empty_datafrmae <- function(table) {
of (grepl(„assets”, table)) {
return(empty_assets() )
}
else if (grepl(„markets”, table)) {
return(empty_markets() )
} else if (grepl(”users”, table)) {
return(empty_users() )
} else id (grepl(„wallets”, table)) {
return(empty_wallets() )
} else {
stop(„Unknown table name”)
}
}
Rzeczywiste puste ramki danych są tworzone przez funkcje empty_assets() empty_markets(), ,empty_users() i empty_wallets(). Każda z nich zawiera specyfikację danych, które mają znajdować się w takich plikach. W szczególności każda obserwacja w danych zasobu powinna mieć adres e-mail, znacznik czasu, nazwę, symbol, sumę i adres. Oczekuje się, że każda obserwacja w danych rynkowych będzie miała znacznik czasu, nazwę, symbol, rangę, cenę w BTC i cenę w USD. Ranking to kolejność kryptowalut na podstawie kwoty wolumenu transakcji w ciągu ostatnich 24 godzin. Dane użytkowników powinny zawierać tylko e-maile. Wreszcie, oczekuje się, że dane portfela będą zawierać adres e-mail, symbol, adres i notatkę. Notatka jest notatką, którą użytkownik może określić, aby rozpoznawać różne portfele od siebie, szczególnie jeśli są one używane dla tego samego rodzaju kryptowaluty. Może jeden portfel Bitcoin jest długoterminowy, a drugi krótkoterminowy; wtedy te informacje można by określić w polu uwagi. Spójrzmy na następujący kod:
empty_assets <- function() {
return(data.frame (
email = character(),
timestamp = character(),
name = character(),
symbol = character(),
total = numeric(),
address = character()
))
}
empty_markets <- function() {
return(data.frame (
timestamp = character(),
name = character(),
symbol = character(),
rank = numeric(),
price_btc = numeric()
price_usd = numeric()
))
}
empty_users <- function() {
return(data.frame (
email = character(),
))
}
empty_walletst <- function() {
return(data.frame (
email = character(),
symbol = character(),
address = character(),
note = character()
))
}