Seconde partie du refactoring de SORM, mon toy ORM qui me sert de prétexte pour quelques articles ;)

Parce que j’espère que vous avez compris que je n’était pas sérieusement en train d’écrire un nouvel ORM pour Ruby, hein ? C’est juste pour étudier un peu ensemble comment ça fonctionne…

Bref, il est temps je pense d’utiliser quelques namespace. SORM::Database pour gérer la connexion et SORM::Base comme modèle de base. Voici donc les tests remaniés:

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
      Article.sql("INSERT INTO article VALUES(1, 'Foo');")
      Article.sql("INSERT INTO article VALUES(2, 'Bar');")
    end

    after do
      Article.sql("DELETE FROM article;")
    end

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

    it 'returns correct values' do
      rows = Article.sql("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 { Article.sql("DELETE FROM article;") }

    it 'creates a record' do
      Article.save(id: 1, name: 'bépo')
      rows = Article.sql("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 bien sûr la nouvelle implémentation qui va avec:

require 'sqlite3'

module SORM

  class Database
    @@db = false

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

    def self.connected?
      @@db ? true : false
    end

    def self.connection
      @@db
    end
  end

  class Base
    def self.sql(raw_query)
      Database.connection.execute(raw_query)
    end

    def self.save(parameters)
      Recorder.new(Database.connection, 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(connection, table, parameters)
      @connection = connection
      @table = table
      @parameters = parameters
    end

    def save
      @connection.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

Voilà, ça fait pas mal de code sans explication :( mais j’ai peu de temps aujourd’hui. La prochaine on fera… je sais pas… on verra bien ;)

To be continued

À demain.