Xavier Nayrac

Rubyiste accro au TDD, serial blogger, apprenti data scientist, heureux utilisateur de Vim, accordéoniste.
Si vous vous sentez particulièrement généreux, suivez moi sur Twitter.

Ruby - Et si on écrivait un ORM ? - partie 10

| Comments

Niveau : intermédiaire

Je continue toujours le refactoring de SORM, cette fois je veux supprimer le paramêtre connection dans l’initialisation de Recorder:

1
2
3
4
5
6
7
8
9
10
  class Recorder
    def initialize(connection, table, parameters)
      @connection = connection
      @table = table
      @parameters = parameters
    end

    def save
      @connection.execute(query)
    end

Bon, c’est tout simple, on utilise directement la classe Database:

1
2
3
4
5
6
7
8
9
  class Recorder
    def initialize(table, parameters)
      @table = table
      @parameters = parameters
    end

    def save
      Database.execute(query)
    end
1
2
3
4
5
6
7
  class Base
    # ...

    def self.save(parameters)
      Recorder.new(self.to_s.downcase, parameters).save
      self.new(parameters)
    end

Ok, c’est trop court comme article, non ? Alors je continue. Comme dit la dernière fois, la méthode Base.sql me semble inutile. Je la supprime donc, ce qui conduit à ce code:

sorm.rb
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
require 'sqlite3'

module SORM

  class Database
    @@connection = false

    def self.connect(database_filename)
      @@connection = SQLite3::Database.open(database_filename)
    end

    def self.connected?
      !!@@connection
    end

    def self.execute(sql)
      @@connection.execute(sql)
    end
  end

  class Base
    def self.save(parameters)
      Recorder.new(self.to_s.downcase, parameters).save
      self.new(parameters)
    end

    def initialize(attributes)
      attributes.each do |name, value|
        instance_variable_set("@#{name}", value)
        singleton_class.class_eval{attr_reader name.to_sym}
      end
    end
  end

  class Recorder
    def initialize(table, parameters)
      @table = table
      @parameters = parameters
    end

    def save
      Database.execute(query)
    end

    def query
      "INSERT INTO #@table (#{columns}) VALUES(#{values});"
    end

    def columns
      @parameters.keys.join(',')
    end

    def values
      @parameters.values.map do |item|
        item.class == String ? "'#{item}'" : item
      end.join(',')
    end
  end
end

Mais maintenant ce sont les tests qui sont dans les choux ! Il faut remplacer toutes les occurences de Article.sql par SORM::Database.execute. Ce qui donne:

sorm_spec.rb
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
require './sorm'

describe SORM::Database do
  describe 'connection' do
    it 'is not connected' do
      expect(SORM::Database.connected?).to be false
    end

    describe 'after connection' do
      it 'is connected' do
        SORM::Database.connect('test.db')
        expect(SORM::Database.connected?).to be true
      end
    end
  end
end

describe SORM::Base do
  class Article < SORM::Base ; end

  describe '.sql' do
    before do
      SORM::Database.execute("INSERT INTO article VALUES(1, 'Foo');")
      SORM::Database.execute("INSERT INTO article VALUES(2, 'Bar');")
    end

    after do
      SORM::Database.execute("DELETE FROM article;")
    end

    it 'returns the correct number of rows' do
      rows = SORM::Database.execute("SELECT * FROM article;")
      expect(rows.size).to eq 2
    end

    it 'returns correct values' do
      rows = SORM::Database.execute("SELECT * FROM article;")
      expect(rows[0][0]).to eq 1
      expect(rows[0][1]).to eq 'Foo'
      expect(rows[1][0]).to eq 2
      expect(rows[1][1]).to eq 'Bar'
    end
  end

  describe 'object creation' do
    after { SORM::Database.execute("DELETE FROM article;") }

    it 'creates a record' do
      Article.save(id: 1, name: 'bépo')
      rows = SORM::Database.execute("SELECT * FROM article WHERE id = 1;")
      expect(rows[0][0]).to eq 1
      expect(rows[0][1]).to eq 'bépo'
    end

    it 'returns an object with correct class' do
      article = Article.save(id: 1, name: 'bépo')
      expect(article.class).to eq Article
    end

    it 'returns an object with correct attributes' do
      article = Article.save(id: 1, name: 'bépo')
      expect(article.id).to eq 1
      expect(article.name).to eq 'bépo'
    end
  end
end

Et les tests passent de nouveau. Mais on a un nouveau problème ! Le bloc describe '.sql' suivant est devenu ridicule:

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
describe SORM::Base do
  class Article < SORM::Base ; end

  describe '.sql' do
    before do
      SORM::Database.execute("INSERT INTO article VALUES(1, 'Foo');")
      SORM::Database.execute("INSERT INTO article VALUES(2, 'Bar');")
    end

    after do
      SORM::Database.execute("DELETE FROM article;")
    end

    it 'returns the correct number of rows' do
      rows = SORM::Database.execute("SELECT * FROM article;")
      expect(rows.size).to eq 2
    end

    it 'returns correct values' do
      rows = SORM::Database.execute("SELECT * FROM article;")
      expect(rows[0][0]).to eq 1
      expect(rows[0][1]).to eq 'Foo'
      expect(rows[1][0]).to eq 2
      expect(rows[1][1]).to eq 'Bar'
    end
  end

1) Il n’y a plus de méthode sql et 2) il teste Database.execute et n’a donc rien à faire ici. Je corrigerais ça la prochaine fois, et je pense qu’il est temps aussi de splitter les fichiers tests et sources…

To be continued

À demain.

Articles connexes

Commentaires