Classe qui gère un historique

Le problème exposé dans ce sujet a été résolu.

Bonjour,
Depuis ce matin, j'essaye de concevoir une classe qui gère un fichier. Cette classe est History et doit donc gérer un historique de toute les transactions effectuées avec le logiciel. C'est un logiciel de gestion d'argent de poche tout ce qu'il y a de plus simple. Voici donc la fameuse classe :

Je sais que pas grand monde ici pratique le Ruby. Mais le langage m'importe peu, c'est la conception de la classe qui m'intéresse. De plus, je trouve le Ruby assez explicite !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env ruby
# encoding: utf-8

load 'Transaction.rb'

class History
    @@HISTORY_PATH = "history.txt" 

    def initalize()
        @hist.open(@@HISTORY_PATH, "r")
        if !@hist.exist?
            self.create
        else
            @hist.close
        end
    end

    def create()
        @hist.open(@@HISTORY_PATH, "w")
        @hist.close
    end


    def write(transaction)
        @hist.open(@@HISTORY_PATH, "a")
        @hist << transaction
        @hist.close
    end
end

if __FILE__ == $0
    hist = History.new
    t = Transaction.new("+", 2.36, "Merci mamie!")
end

Je ne suis absolument pas satisfait de mes méthodes initialize(le constructeur) et create qui sert à créer le fichier. J'ai l'impression qu'il manque quelque chose à ma classe, ou que je passe par des chemins tordus… Je vous laisse juger et me corriger.

Merci de votre lecture et de votre aide, bonne journée !

+0 -0

Bonjour,

En effet, il y a moyen de simplifier ce code !

D'abord, la méthode "File.exist?" prend en paramètre le nom du fichier à vérifier et s'utilise comme ça :

1
2
3
unless File.exist? @@HISTORY_PATH
  # code à exécuter si le fichier n'existe pas
end

Deuxièmement, les fichiers sont créés automatiquement quand ils sont ouverts en écriture (par exemple avec le mode "a" comme tu le fais dans la méthode #write). Pas besoin de le faire manuellement. :)

Troisièment ouvrir un fichier, écrire une ligne (transaction) à l'intérieur puis le refermer tout de suite à chaque fois n'est pas très optimisé. C'est plus rapide de l'ouvrir une seule fois dans le constructeur, écrire toutes les lignes voulues et le fermer une fois que tout le travail est terminé.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# history.rb
class History
  FILE_PATH = 'history.txt'

  def initialize
    # ouvre le fichier en écriture (il sera créé automatiquement si nécéssaire)
    @hist = File.new FILE_PATH, 'a'
  end

  def write(transaction)
    @hist << transaction
  end

  def close
    @hist.close
  end
end

# main.rb
history = History.new

history.write Transaction.new('+', 2.36, 'Merci mamie!')
history.write Transaction.new('-', 13_499.75, 'Cable HDMI AudioQuest')
# ...

history.close

Si tu as l'intention de lire/faire des recherches dans l'historique en plus d'écrire à l'intérieur, je te conseille plutôt d'utiliser une base de donnée (du genre SQLite). C'est beaucoup plus facile à gérer qu'un fichier plat et ça permet de faire des requêtes plus complexes (par exemple "récupère toutes les transaction de plus de 10$") sans se casser la tête.

+3 -0

Merci de ta réponse, je savais bien que quelque chose clochait dans ce fichier ! En faite le truc c'est que je n'avais pas ouvert directement le fichier en mode a parce que je comptait aussi le lire.. Mais peu importe, la base de donnée m'intéresse ! Mais avant, quelques questions sur la classe :

  • Pourquoi dans ta constante FILE_PATH n'est pas une constante de classe ? Je pensais que toutes les variables à l'intérieur d'une classe devaient être préfixées de @ !

Comme tu comprends le Ruby, et que la doc française est vraiment pauvre, j'aurai quelques questions :

  • C'est quoi ces symbol en Ruby ? Ça me dépasse totalement…
  • Pourquoi une constante est… modifiable !?

Ensuite, c'est vrai qu'une base de donnée serait bien plus adapté à mon cas (car je compte lire le fichier, et maintenant que tu en parles, des recherches ça peut être pas mal aussi), je n'y avais pas pensé ! Bon le seul problème c'est que j'en ai jamais manipulé et encore moins en Ruby. Donc SQLite me parait bien compliqué… Si vous avez un article qui explique ça bien, je suis preneur ! Par contre Sequel me parait plus simple. Il a sûrement des fonctionnalités en moins et est moins rapide, mais ça devrait me suffire. Qu'en penses-tu ?

PS : pas donné ton câble HDMI! :lol:

+0 -0
  • Pourquoi dans ta constante FILE_PATH n'est pas une constante de classe ? Je pensais que toutes les variables à l'intérieur d'une classe devaient être préfixées de @ !

Pas toutes. Une classe peut contenir trois types de variable :

@variable avec un seul @ est une "variables d'instance". C'est le type le plus commun. Sa valeur diffère d'une instance à l'autre :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Car
  def initialize
    @passengers = 0
  end

  def passengers; @passengers; end
  def passengers=(how_many); @passengers = how_many; end
end

a = Car.new
b = Car.new

puts a.passengers # => 0
puts b.passengers # => 0

a.passengers = 3

puts a.passengers # => 3
puts b.passengers # => 0

@@variable est une "variable de classe". Ce n'est pas une constante et elle est partagée avec chaque instance :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Car
  @@passengers = 0

  def passengers; @@passengers; end
  def passengers=(how_many); @@passengers = how_many; end
end

a = Car.new
b = Car.new

puts a.passengers # => 0
puts b.passengers # => 0

a.passengers = 3

puts a.passengers # => 3
puts b.passengers # => 3 aussi !

Tandis que les constantes (variables commencant par une majuscule et sans préfixe) sont figés :

1
2
3
4
5
6
7
class Bacon
  QUALITY = :perfect
  QUALITY = :terrible
end
# warning: already initialized constant Bacon::QUALITY

puts Bacon::QUALITY # affiche "terrible"

…ou pas. Comme ruby n'est pas bien strict à ce sujet (d'ailleurs, les avertissement peuvent être désactivés…), les variables constantes sont plus une indication par l'auteur du code aux autres programmeurs pour indiquer que la valeur ne devrait pas changer, plutôt que autre chose. ;)

Note qu'il y a aucune différence entre une variable "constante" à l'intérieur d'une classe et une autre à l'exérieur.

  • C'est quoi ces symbol en Ruby ? Ça me dépasse totalement…

Ce sont des chaînes de caractères légères. Ça signifie qu'elles sont créées une seule fois en mémoire et ne peuvent pas être manipulés autant qu'un objet de type String :

1
2
3
4
5
puts 'hello_world'.object_id == 'hello_world'.object_id # => false, ce n'est pas le même objet
puts :hello_world.object_id == :hello_world.object_id # => true

puts 'hello world'.sub 'world', 'cfillion' # => hello cfillion
puts :'hello world'.sub 'world', 'cfillion' # => NoMethodError: undefined method `sub' for :"hello world":Symbol

En pratique les symboles sont utilisés quand les fonctionalités avancés de String ne sont pas requises. Par exemple tu pourrais les utiliser pour spécifier le type des transactions :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Transaction
  def initialize(type, amount, description)
    case type
    when :+
      puts 'dépôt'
    when :-
      puts 'retrait'
    end

    # ...
  end
end

Transaction.new :-, 10, 'assez pour commander un sandwich au bacon' # affiche "retrait"
Transaction.new :+, 2.5, "finalement le sandwich a couté que 7.5" # affiche "dépôt"

Le même code avec des chaîne des caractères normales aurait créé deux fois plus d'objets en mémoire (c'est vraiment pas beaucoup mais à la longue, ça compte !).

  • Pourquoi une constante est… modifiable !?

Parce que ruby n'impose pas (ou peu ?) de limites artificielles au programmeur. Si il veut modifier sa constante, c'est son problème !

Bon le seul problème c'est que j'en ai jamais manipulé et encore moins en Ruby. Donc SQLite me parait bien compliqué… Si vous avez un article qui explique ça bien, je suis preneur ! Par contre Sequel me parait plus simple. Il a sûrement des fonctionnalités en moins et est moins rapide, mais ça devrait me suffire. Qu'en penses-tu ?

Je pense que tu trouvera plus de documentation et d'aide en utilisant directement la syntaxe SQL plutôt qu'un ORM moins populaire comme Sequel (qui l'utilise de toute façon derrière la scène).

Pour débuter il y a plusieurs ressources :

N'hésite pas à lire des tutoriels écrits pour d'autres langages ou systèmes de base de données : le langage SQL est identique partout à l'exception de quelques petits détails. :)

+0 -0

Un énorme merci ! Donc en faite les constantes de classe n'existent pas ? Genre @@CONST ne veut rien dire ?

Un autre merci pour les symbols, je commence à les cerner ! Seulement une question persiste. On peut voir ce code sur la doc de ruby de la classe Symbol :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
module One
  class Fred
  end
  $f1 = :Fred
end
module Two
  Fred = 1
  $f2 = :Fred
end
def Fred()
end
$f3 = :Fred
$f1.object_id   #=> 2514190
$f2.object_id   #=> 2514190
$f3.object_id   #=> 2514190

Notamment, dans le module Two, on peut lire

1
2
Fred = 1
$f2 = :Fred

Est-ce que le :Fred aurait un quelconque rapport avec la variable Fred ? Car pour faire les accesseurs d'une classe j'utilise cette syntaxe :

1
2
3
4
5
6
7
class Foo
    attr_reader :bar

    def initialize()
        @bar = 1
    end
end 

Comment :bar peut renvoyer à la variable @bar si ce n'est qu'une chaine de caractère tronqué de certaines fonctionnalités ?

Et enfin merci pour les différents lien, je vais aller les lire et apprendre à me servir de SQL.

Merci beaucoup !

Donc en faite les constantes de classe n'existent pas ? Genre @@CONST ne veut rien dire ?

Exactement. @@CONST n'est rien de plus qu'une variable de classe, même si elle est toute en majuscules.

Est-ce que le :Fred aurait un quelconque rapport avec la variable Fred ?

Non. $f2 ne vaudra pas 1 mais bien 'Fred', si c'est ce que tu voulais dire. Le but de l'exemple est de démontrer que peu importe le contexte, le symbole est toujours le même objet.

Comment :bar peut renvoyer à la variable @bar si ce n'est qu'une chaine de caractère tronqué de certaines fonctionnalités ?

attr_reader prend une chaîne de caractère en paramètre et génère une méthode correspondante automatiquement (ça s'appelle de la métaprogrammation, ruby va loin dans ce domaine). Ce n'est pas obligatoirement un symbole :

1
2
3
class Foo
  attr_reader 'bar'
end
+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte