Exploration de l'API Standard de Crystal-lang PART 3 (Class)
Tags: crystal
Published: 2023-07-12 16:45:51

---
title: Exploration de l'API Standard de Crystal-lang PART 3 (Class)
published: true
description: Troisième partie avec usage de Class
tags: crystal
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-07-12 15:00 +0000
---
# Introduction
Partie 3 : Encapsulons les méthodes liées à la persistance des données dans une classe.

# C'est parti.

## Etat des lieux

Pour l'instant, nous gérons nos accès à la persistance via 2 méthodes écrites sur le `Top level`.

```crystal   
def add_task(item : String)
  file = File.open(Config::TODO_FILE_PATH, "a")
  file.puts item
  file.close
end
   
def task_list : Array(String)
  File.read_lines(Config::TODO_FILE_PATH)
end
```

# Vision Objet.

Si nous regardons ça sous la vision d'un objet, notre persistance est un `Repository` que l'on initialise avec le chemin d'accès. Puis nous aurons besoin des méthodes d'ajout d'un élément au dépôt et de lister ce que contient le dépôt.

# Passons aux tests

Pour l'initialisation, nous voulons passer un chemin en argument. La vérification sera que le chemin est bien renseigné et que le fichier existe. Puis nous supprimerons le fichier pour la ré-utilisabilité du test.

```crystal
  describe "#new" do
    it "initialize Repository with given path" do
      path = File.join(Dir.current,"spec", "fixtures", "repo", "new.txt")
      repo = Repository.new(path: path)
      repo.path.should eq path
      File.exists?(path).should eq true
      File.delete(path)
    end
  end
```

Pour la méthode de remontée de la liste des éléments, nous allons créer un fichier `all.txt` dans `./spec/fixtures/repo`. Nous valorisons le fichier avec 3 lignes.

```
test
all
method
```
et le test comme suit
```crystal
  describe "#all" do
    it "return a list for a given file" do
      path = File.join(Dir.current,"spec", "fixtures", "repo", "all.txt")
      repo = Repository.new(path)
      repo.all.should eq ["test","all","method"]
    end
  end
```

Enfin, pour l'ajout, nous initialisons un repo vide. Nous appelons notre méthode d'ajout. Puis vous vérifions que notre repo contient bien notre élément.

```crystal
  describe "#add" do
    it "store todo into the given file" do
      path = File.join(Dir.current, "spec", "fixtures", "repo", "add.txt")
      repo = Repository.new(path)
      repo.add("test add method")
      repo.all.should eq ["test add method"]
      File.delete(path)
    end
  end
```

Le tout dans le fichier `./spec/repository_spec.cr`, dans un `describe` global.
En résumé : 
```crystal
require "./spec_helper"

describe Repository do
  describe "#new" do
    it "initialize Repository with given path" do
      path = File.join(Dir.current,"spec", "fixtures", "repo", "new.txt")
      repo = Repository.new(path: path)
      repo.path.should eq path
      File.exists?(path).should eq true 
      File.delete(path)
    end
  end
  describe "#all" do
    it "return a list for a given file" do
      path = File.join(Dir.current,"spec", "fixtures", "repo", "all.txt")
      repo = Repository.new(path)
      repo.all.should eq ["test","all","method"]
    end
  end
  describe "#add" do
    it "store todo into the given file" do
      path = File.join(Dir.current, "spec", "fixtures", "repo", "add.txt")
      repo = Repository.new(path)
      repo.add("test add method")
      repo.all.should eq ["test add method"]
      File.delete(path)
    end
  end
end
```

#Implémentation de notre `Repository`

Si nous lançons les tests en tant que tel, nous aurons plein d'erreur de méthodes et de classes non définies.

Écrivons le code minimum pour que les tests se lancent

```crystal
class Repository
  getter path : String

  def initialize(@path)
  end

  def all
  end

  def add(element : String)
  end
end
```

La commande `crystal spec` s'executent maintenant sans erreurs et tous nos tests sont en échec.

```bash
crystal spec
.....FFF

Failures:

  1) Repository #new initialize Repository with given path
     Failure/Error: File.exists?(path).should eq true

       Expected: true
            got: false

     # spec/repository_spec.cr:9

  2) Repository #all return a list for a given file
     Failure/Error: repo.all.should eq ["test","all","method"]

       Expected: ["test", "all", "method"]
            got: nil

     # spec/repository_spec.cr:17

  3) Repository #add store todo into the given file
     Failure/Error: repo.all.should eq ["test add method"]

       Expected: ["test add method"]
            got: nil

     # spec/repository_spec.cr:25

Finished in 416 microseconds
8 examples, 3 failures, 0 errors, 0 pending

Failed examples:

crystal spec spec/repository_spec.cr:5 # Repository #new initialize Repository with given path
crystal spec spec/repository_spec.cr:14 # Repository #all return a list for a given file
crystal spec spec/repository_spec.cr:21 # Repository #add store todo into the given file
```
On voit ici l'importance du nommage des tests

## Implémentation de l'init

Nous souhaitons que le chemin passé en argument corresponde à un fichier qui existe. Dans le cas contraire, nous le créons.

```crystal
class Repository
...
  def initialize(@path)
    unless File.exists?(@path)
      file = File.new(@path,"wb")
      file.close
    end
  end
...
```

Le mode `wb` permet de créer le fichier et de s'assurer qu'il soit vide.

## Implémentation de la méthode `all`

Nous avons besoin d'ouvrir le fichier correspondant au `path` et d'en lister le contenu. Nous pouvons reprendre le code d'origine et l'adapter à notre nouveau contexte.

```crystal
class Repository
...
  def all
    File.read_lines(@path)
  end 
...
```

## Implémentation de la méthode `add`

Comme ci dessus, nous adaptons l'existant.

```crystal
class Repository
...

...
```

## Execution des tests.
```bash
crystal spec
........

Finished in 473 microseconds
8 examples, 0 failures, 0 errors, 0 pending
```

Tout va bien.

# Refactoring.
## Etape 1 : Mettre notre nouvelle classe dans un fichier à part.

Nous prenons le code de notre class pour le mettre dans le fichier `./src/repository.cr` et nous le remplaçons par `require "./repository"` dans notre fichier principal.

## Etape 2 : Nous remplaçons les anciennes méthodes par notre classe.

```crystal
    items = task_list
```
devient
```crystal
    items = Repository.new(Config::TODO_FILE_PATH).all
```
et 
```crystal
          add_task(item)
```
devient
```crystal        
  Repository.new(Config::TODO_FILE_PATH).add(item)
```
## Etape 3 : Nettoyage
Nous supprimons le code des anciennes méthodes.

# Conclusion.

Nous avons vu comment créer une classe et la tester.

Dans la prochaine partie, nous allons encapsuler la partie serveur HTTP.