Il y a une semaine de cela, Yehuda Katz a fait un article expliquant comment, dans Rails 3, obtenir les fonctionnalités de ActiveModel dans un modèle n’utilisant pas ActiveRecord.
Cela permet notamment de pouvoir utiliser les méthodes de sérialisation de Rails (export json et xml) et les validations.
C’est ce second point que je vais détailler aujourd’hui parce que c’est celui que je trouve le plus intéressant.

Notez que vous devez tout d’abord avoir le gem ActiveModel 3.0.pre installé sur votre machine.
Il est disponible sur gemcutter. Il vous suffit donc de faire

sudo gem install activemodel -v=3.0.pre -source=http://gemcutter.org

Prenons un fichier « test.rb », totalement intépendant de Rails. C’est juste du ruby pur et dur.
Mais parce qu’on y définit une classe, on souhaite pouvoir valider le format des données passées à celle-ci.

Voici donc le contenu de notre fichier test.rb

#
# Nous ne sommes pas dans Rails. Rubygems n'est donc pas inclus par défaut.
# ActiveModel ne l'est pas non plus. On doit donc les appeller.
#
require 'rubygems'
require 'active_model'

class Test
  #
  # On charge les validations de ActiveModel
  #
  include ActiveModel::Validations

  #
  # First Name et Last Name doivent absolument être présent
  #
  validates_presence_of :first_name, :last_name

  #
  # On définit les attributs First Name et Last Name
  # Et on les initialise dans le constructeur
  #
  attr_accessor :first_name, :last_name
  def initialize(first_name, last_name)
    @first_name, @last_name = first_name, last_name
  end
end

Vous noterez qu’il nous suffit d’inclure ActiveModel::Validations afin d’avoir accès à toutes ces méthodes de validations. Plutôt cool ! :)

Dès à présent, nous pouvons tester si nos validations passent !

test = Test.new('Jane', 'Doe')
p test.valid?

Inévitablement, nos validations passent puisque nous vérifions simplement la présence de First Name et de Last Name et que sans ceux-ci, nous ne pourrions même pas instancier la classe.

Mais … Comme nous avons accès à toutes les méthodes de validation d’ActiveModel, nous pouvons créer notre propre validateur !

class Test
  # ... Contenu de la classe définit précédemment dans l'article

  #
  # Et on ajoute une validation, empêchant quelqu'un de prendre le nom de famille "Doe"
  #
  validate do |t|
    t.errors[:last_name] << 'You must know your own name. Doe is only for anonymous guys' if t.last_name == 'Doe'
  end
end

Constatez le « validate » que nous avons ajouté et qui empêche quelqu’un de prendre le nom de famille « Doe ».

Maintenant, réexecutons notre code précédent. Notre méthode « valid? » nous retournera bien évidemment false.
Et si nous faisons un print de test.errors, nous constaterons que celui-ci est un Hash contenant chacune de nos erreurs.

#<OrderedHash {:last_name=>["You must know your own name. Doe is only for anonymous guys"]}>

N’oubliez pas bien évidemment de vérifier que la validation de vos données est correcte avant de sauvegarder celles-ci. Sans quoi l’utilité est particulièrement restreinte :)

Vous pouvez du coup, d’une manière similaire à ce que vous faites déjà avec vos modèles ActiveRecord, valider les données de tous vos objets Ruby en utilisant ActiveModel.
Par exemple si vous utilisez une solution NoSQL telle que CouchDB ou MongoDB.
Ou encore sur des modèles vous permettant de faire le lien avec une API distante (XML ou JSON par exemple).

Tous vos modèles utilisent ainsi la même API, celle d’ActiveModel. Ils sont tous homogènes et vous pouvez d’autant plus aisément manipuler ceux-ci.

Vous pouvez, en Ruby, comme avec tout langage évolué, générer et gérer des exceptions.

Exemple rapide :

begin
    raise "Only a test"
rescue
    # Le raise nous emmene ici
end

Ainsi, nous pouvons aisément gérer les erreurs générées par notre application et éviter de tout casser pour un simple enregistrement non trouvé dans la base de données.
Tous vos modèles et vos contrôleurs dans Rails pourront soulever des exceptionset elles seront gêrées par l’application.

Ainsi si, dans votre contrôleur, vous avez :

Post.find params[:id]

Et que l’uplet ayant pour clé primaire params[:id] n’existe pas, une exception ActiveRecord::RecordNotFound sera soulevée.
En développement vous verrez un beau message d’erreur et en production une belle erreur 500.
Mais nous ne voulons pas de cette erreur 500. Si l’enregistrement ne peut pas être trouvé, cela signifie que la page n’existe pas et alors, on désire une erreur 404.

En Ruby pur, nous ferions donc :

begin
    Post.find params[:id]
rescue ActiveRecord::RecordNotFound
    # On affiche l'erreur 404
end

Vade retro beurk !

Rendons nous plutôt dans application_controller et utilisons rescue_from (ou dans rails 3).
Nous allons donc avoir, dans ApplicationController :

rescue_from ActiveRecord::RecordNotFound, :with => :render_missing
def render_missing
    render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
end

Rails s’occupe de faire l’appel à cette méthode lorsque l’exception est soulevée et nous avons bien notre erreur 404 générée :)

Bien évidemment, vous pouvez gérez n’importe quelle exception avec rescue_from et ainsi éviter les erreurs 500 pas jolies et qui anéantissent l’expérience utilisateur afin de les remplacer par des joli messages.

Attention cependant à ne pas partir dans des excès ! En voici typiquement un.
Ok pour gérer les exceptions dans le contrôleur lorsqu’elles ont un impact direct sur l’utilisateur (service indisponible, erreur 404, …).
Mais pas pour gérer toutes les erreurs comme ceci.
Ne vous amusez donc pas à utiliser rescue_from afin de gérer les enregistrements invalides. Travaillez intelligemment :)

J’ai déjà mentionné la récupération d’informations par l’utilisateur en ruby console dans l’article Read Eval Print Loop.
Cependant il arrive qu’il soit nécessaire de récupérer des informations sensibles via la console. Un mot de passe par exemple. Celui-ci ne devra pas s’afficher sur l’écran pendant que vous le taperez.
La librairie Ruby Password permet de faire cela de manière simple.
Cependant cela implique dépendre de cette librairie. Et c’est quelque chose que je ne désire pas, surtout pour quelque chose d’aussi simple.

La solution que je vous propose ici utilise donc simplement les fonctionnalités de toute console linux (pas testé sous windows. Mais qui utilise encore windows de nos jours ? :mrgreen: ).

begin
    print "Username: "
    username = $stdin.gets.chomp

    print "Password: "
    # We hide the entered characters before to ask for the password
    system "stty -echo"
    password = $stdin.gets.chomp
    system "stty echo"
rescue NoMethodError, Interrupt
    # When the process is exited, we display the characters again
    # And we exit
    system "stty echo"
    exit
end

Que fait-on ?
Nous commençons par demander un nom d’utilisateur.

print "Username: "
username = $stdin.gets.chomp

Celui-ci n’est pas une donnée sensible et peut donc être affiché sans problème. Rien de particulier à faire.

Ensuite nous demandons un mot de passe. La il faut le masquer.

print "Password: "
system "stty -echo"
password = $stdin.gets.chomp
system "stty echo"

Le « stty -echo » masquera tous les caractères qui devraient être affichés en console par la suite.
Le « stty echo » permet d’afficher les caractères qui seront tapés ensuite.

Jusque la, ça fonctionne. Cool !
Mais d’un coup j’ai un utilisateur chiant (oui toi là-bas à côté du radiateur), qui décide au dernier moment de faire Ctrl-C pour quitter le programme et se retrouve dans une console en -echo et ne voit donc plus ce qu’il tape. On vient pas de se faire un ami.

Heureusement on gère l’arrêt du système notre rescue, ou l’on indique que lorsqu’un Ctrl-C est envoyé, on désire quitter l’application.
Lorsque c’est le cas, nous n’avons donc plus qu’à également réafficher les caractères et le tour est joué !

begin
    # ...
rescue NoMethodError, Interrupt
    system "stty echo"
    exit
end

Hop ! :)
Et le projet qui utilise ceci, c’est glynn :)

Si j’étais marseillais un verre de ricard à la main, je me serai posé cette question bien plus tôt !
Mais ce n’est pas le cas. Heureusement il n’est jamais trop tard ! ;)

Par habitude, afin de mettre à jour un repository GIT distant, j’utilise git pull.
Ca fonctionne à merveille, tout le monde a ses données à jour et on se croirait presque au pays des bisounours.
Mais Gargamel vient mettre son nez là-dedans (ouais je mélange Schtroumpf et Bisounours. Honte sur moi !).

Ce matin, j’ai voulu, avant de faire la release de la version 1.0.0 de jesus (car il est prêt pour la production), mettre à jour la documentation.
Le principe est simple. J’utilise GitHub Pages. Il me faut donc juste une branche « gh-pages », dont le contenu correspond à ce qui est en ligne chez GitHub pour ma documentation.
Dès que je commit sur cette branche, la page en ligne est mise à jour.

Cependant mes deux branches master et gh-pages n’ont pas le même index. Les deux ne peuvent pas être mergées !
Ce matin, je me vois donc faire un

git pull origin gh-pages:gh-pages

En me disant « comme cela, je récupère le contenu de la branche de documentation ».
Et la je me retrouve avec une dizaine d’erreurs de conflit ! WTF comme dirait l’autre.

Et justement pour le coup, j’aurai du faire un

git fetch origin gh-pages:gh-pages

La différence entre ces deux commandes en apparence similaires est pourtant énorme.
Git fetch ne fait que récupérer les données. Git pull les récupère et fait un merge avec la branche courante …
Du coup merge mes deux branches, vu qu’elles ont pas la même base, c’est pas super cool.

Moralité : vous pouvez faire un git fetch afin de mettre à jour votre copie locale avec les derniers commits distants. La branche sur laquelle vous faites le fetch aura les derniers commits.
Mais votre branche de travail ne sera pas modifiée.
Si vous faites un git pull, non seulement la branche sur laquelle vous faites le fetch aura les derniers commits distants. Mais votre branche de travail sera également mergée avec ces derniers commits.

En y réflechissant bien, fetch est bien plus recommandable que pull, qui peut casser un repository assez facilement.

Ou repl ! Le dernier projet open source de Chris Wanstrath.
Il est plutôt productif. Donc je ne parle pas de tous ses projets, même si plusieurs sont particulièrement intéressants (jetez donc un coup d’oeil à resque et à fakefs (j’ai fait deux commits sur ce dernier)).

Mais revenons en à repl.
Supposons que vous soyez en train de travailler sur un projet utilisant GIT.
En bon barbu que vous êtes, vous n’utilisez évidemment pas d’interface graphique (c’est tout à fait faisable, j’en suis la preuve vivante).
Cependant passer son temps à taper « git add », « git rm », « git commit », … est parfois assez rébarbatif (j’éviterai le terme chiant).
Pouvoir avoir un terminal spécific à git et avoir uniquement à taper « add », « rm », « commit » etc serait plus intéressant.
Et c’est justement là que repl arrive. Pas seulement pour git, mais pour n’importe quelle commande linux !

Commençons pas installer la bête.

sudo gem install repl

Vous suivez toujours ? :mrgreen:

Maintenant utilisons notre nouveau jouet avec git justement.

repl git

Et la, la magie opère et vous êtes un nouvel homme (ou une femme. Pas de machisme). Vous avez un shell spécifiquement pour git.

Et bien évidemment, ça fonctionne avec n’importe quoi. Git, Apt-get, Gem, … Et même votre propre exécutable fait maison :)

Oui mais comment ça fonctionne ?

Petit curieux va ! Bonne question !
Comme quoi la taille d’une application n’est pas proportionnelle à son utilisé, repl tient en tout et pour tout en 80 lignes de Ruby !
Et encore c’est parce qu’il a une fonctionnalité d’autocomplétion. On pourrait conserver ta fonctionnalité principale uniquement et le réduire à 20 lignes de code (on s’en tape les …).

Voyons ici uniquement l’indispensable.

loop do
  print ENV['REPL_PROMPT'] || '>> '

  begin
    line = $stdin.gets.chomp
  rescue NoMethodError, Interrupt
    exit
  end

  puts "$ #{command} #{line}" if debug
  system "#{command} #{line}"
  warn "Use Ctrl-D (i.e. EOF) to exit" if line =~ /^(exit|quit)$/
end

Nous créons une boucle infinie (car elle ne sera rompue que par un Ctrl-D) afin d’avoir l’impression d’être dans une interface shell.
Puis au début de chaque ligne, nous affichons les caractères « >> », toujours dans un souci d’esthétique.

Le $stdin.gets.chomp permet de récupérer toute commande tapée par l’utilisateur.

Puis, une fois que la commande a été tapée, on l’exécute et on affiche ce qu’elle retourne (fonction « system »).
Si c’est « exit » qui est entrée, on invite la personne à taper Ctrl-D pour quitter repl.

Cool, simple, efficace et utile :)

Dans deux articles précédents, nous avons vu une introduction à CouchDB et l’utilisation de CouchDB avec Ruby.
Nous sommes donc maintenant capable d’ajouter des données dans notre base et de récupérer une liste de ceux-ci. Mais il peut parfois arriver d’avoir besoin de faire des requêtes plus complexes.

Prenons par exemple une table CouchDB générée par CouchREST. Vous y verrez, pour chacun de vos modèles, un document nommé « _design/Model ». Par exemple, « _design/File ».
Il ne s’agit en réalité par d’un document. Mais d’une vue.
Regardons l’une des vues générées par CouchREST.

{
    "all": {
        "map": "function(doc) {
            if (doc['couchrest-type'] == 'Image') {
                emit(doc['_id'],1);
            }
        }"
   }
}

Vous lisez bien, les vues CouchDB sont du javascript ! :)

Après avoir chargé cette vue, tentez de visiter la page votre_base/_design/votre_vue/_view/all.
Vous constaterez que seuls les éléments de type « Image » sont retournés.
Notre fonction javascript est exécutée sur chacun des éléments. Et seuls ceux que vous désirez (en les insérant avec « emit ») sont retournés.

Prenons le cas d’une plateforme de blog (oui cas bidon).
Supposons que nous désirions visualiser uniquement les documents ayant l’attribut « published » à true.

{
    "all": {
        "map": "function(doc) {
            if (doc['published'] == true) {
                emit(doc['_id'], doc);
            }
        }"
    }
}

De la même manière que tout à l’heure, nous ne retournons que les billets publiés dans notre blog.
Voici ainsi le contenu qui m’est retourné pour la vue précédente

{
    "total_rows":2,
    "offset":0,
    "rows":[
        {
            "id":"7c46156162a59d145cf9cf7850e6b677",
            "key":"7c46156162a59d145cf9cf7850e6b677",
            "value":{
                "_id":"7c46156162a59d145cf9cf7850e6b677",
                "_rev":"5-f726b6d7f469b686079fbe4c5f50726b",
                "title":"My First Blog Post",
                "content":"My Post Content",
                "published":true,
                "comments":[
                    {"author":"John","content":"First Comment"},
                    {"author":"James","content":"Second Comment"}
                ]
            }
        },
        {
            "id":"f14fde843b20e18561ea5e8055dbc3b3",
            "key":"f14fde843b20e18561ea5e8055dbc3b3",
            "value":{
                "_id":"f14fde843b20e18561ea5e8055dbc3b3",
                "_rev":"1-5eef22da217f5858542d175c41d2ef3d",
                "title":"My Second Post",
                "content":"Second Content",
                "published":true
            }
        }
    ]
}

Vous constatez que j’ai inséré plusieurs commentaires sur l’un de mes billets. Il ne s’agit pas de documents différents mais bien du même document.
L’attribut « comments » est un tableau de commentaires, chacun pouvant contenir les éléments que je désire.

Et la, la problématique à laquelle on pense rapidement, c’est : « Mais comment je fais pour récupérer la liste de tous mes documents ? »
Comme précédemment, avec une vue bien évidemment !

{
    "all": {
        "map": "function(doc) {
            for (var i in doc.comments) {
                emit(doc, doc.comments[i]);
            }
        }"
    }
}

Nous parcourons chacun de nos documents. Puis dans chacun de ceux-ci, nous parcourons tous les commentaires.
Et insérons au resultat tous les commentaires.
Le résultat obtenu est le suivant.

{
    "total_rows":2,
    "offset":0,
    "rows":[
        {
            "id":"7c46156162a59d145cf9cf7850e6b677",
            "key":"My First Blog Post",
            "value":{
                "author":"James",
                "content":"Second Comment"
            }
        },
        {
            "id":"7c46156162a59d145cf9cf7850e6b677",
            "key":"My First Blog Post",
            "value":{
                "author":"John",
                "content":"First Comment"
            }
        }
    ]
}

Nous avons bien la liste de tous nos commentaires. Et (vu que nous l’avons ajoutés au résultat), nous avons même le titre billet qui va avec ! :)

Et les performances dans tout ça ?
Comme c’est de l’HTTP, il y a du cache natif, que CouchDB gère parfaitement bien. Si vous chargez plusieurs fois votre vue, vous verrez donc que la page retournée est bien un 304 not modified.
Je n’ai pas testé avec énormément d’enregistrements. Mais j’ai discuté de cela avec Mirsal lundi soir lors de l’apéro Web Event Lyon. Et apparemment, même avec des milliers de billets, la chose ne posera pas trop de problèmes.
Je reviendrai là-dessus dans quelques mois si il y a matière à en dire quelque chose ;)

Quoi qu’il en soit, je trouve les vues particulièrement intéressantes. Je me retrouve régulièrement frustré par les limitations de SQL.
En développant celles-ci en Javascript, la chose devient virtuellement illimitée.

Mirsal disait également hier: « Les bases de données orientées document sont généralement ce qu’il y a de plus adapté.
C’est SQL qui devrait être utilisé uniquement dans des cas atypiques. »
Je n’irai pas jusqu’à m’avancer comme cela. Mais les possibilités sont tellement impressionnantes que cela laisse rêveur.

Hier, un PHP wannabee (berk), Romain cherchait à faire un screenshot d’une page web en « ligne de commande ».
Plusieurs solutions lui ont été proposées. Pour ma part, ma préférée est Selenium ! Sachant bien qu’il ne ferait jamais la chose en Ruby, j’ai donc décidé de regarder cela d’un peu plus près.

Tout d’abord, vous devez avoir Selenium RC installé et lancé.
C’est simple. Téléchargez le, rendez-vous dans le dossier selenium-server-1.0 et entrez

java -jar selenium-server.jar

Votre serveur Selenium est lancé sur le port 4444. Y’a plus qu’à l’utiliser !
Vous devez également avoir la librairie selenium-client installée.

sudo gem install selenium-client

Votre disque dur étant maintenant un peu moins vide, on peut s’attaquer au code en lui même ! :)
Je vais faire un screenshot de mon tout nouveau portfolio (encore en chantier).

On commence par la magie et on explique après.

require 'rubygems'
require 'selenium'

# We load Selenium
@selenium = Selenium::SeleniumDriver.new("localhost", 4444, "*firefox", "http://42.dmathieu.com/", 10000);
@selenium.start
@selenium.set_context("test_new")

# We go to the main page and take the screenshot
@selenium.open "/"
@selenium.capture_entire_page_screenshot(File.expand_path(File.dirname(__FILE__)) + 'screenshot.png', '');

# We unload Selenium
@selenium.stop

On commence par charger les librairies requises. Pas compliqué, on a besoin que de Selenium.

require 'rubygems'
require 'selenium'

Puis on charge Selenium en lui renseignant l’URL que l’on désire visiter et le navigateur avec lequel on désire visiter celle-ci.
Et en définissant le contexte (ici, un firefox vierge).

@selenium = Selenium::SeleniumDriver.new("localhost", 4444, "*firefox", "http://42.dmathieu.com/", 10000);
@selenium.start
@selenium.set_context("test_new")

On charge la page, on prends le screenshot et on sauvegarde l’image créée :)

@selenium.open "/"
@selenium.capture_entire_page_screenshot(File.expand_path(File.dirname(__FILE__)) + 'screenshot.png', '');

Et on oublie pas de libérer la mémoire.

@selenium.stop

Et zou, c’est super magique ! Notre beau screenshot (de la page entière, pas seulement de l’écran) est alors généré.

Vous noterez que le javascript est exécuté (tentez de désactiver le javascript de votre navigateur et regarder ce qu’il advient de mon email affiché sur la page).
Et que le rendu est effectivement le même que ce que l’on a dans le navigateur.

A quand un outil open source utilisant Selenium et générant des thumbnails ? :p

Je vous ai présenté il y a quelques jours CouchDB.
La librairie que je vous ai présenté dans le précédent article était très bien dans un but éducatif. Mais en pratique, c’est vraiment pas puissant. Aujourd’hui nous allons donc parler de CouchREST.

Commencons par installer le gem. Si vous n’avez pas Rubygems configuré pour Gemcutter, faites le maintenant !

sudo gem install couchrest

Zou ! Maintenant on se connecte, on crée une base et un document dans celle-ci !

require 'rubygems"
require "couchrest"

@db = CouchRest.database!("http://127.0.0.1:5984/my_database")
response = @db.save_doc({:name => 'My Document', :content => 'The content'})
p @db.get(response['id']).inspect

Super, mais jusque la, ça ne va pas beaucoup plus loin que ce que nous avons fait précédemment.
Voyons donc comment créer une classe qui représentera un modèle de données CouchDB.

class Document < CouchRest::ExtendedDocument
    use_database 'my_database'

    property  :name
    property  :content

    timestamps!
end

Nous créons un modèle « Document », qui possèdera les champs name et content. Mais également created_at et updated_at (remplis automatiquement grâce à timestamps!).
Maintenant nous pouvons faire les choses d’une manière similaire de ce que nous faisons avec ActiveRecord.

Créer un nouveau document.

doc = Document.new(:name => 'My Doc', :content => 'The Content')
doc.save!

Récupérer tous les documents existants

docs = Document.all

Et également appeller n’importe quelle vue grâce à des méthodes virtuelles. Par exemple si vous avez une vue « by_name ».

docs = Document.by_name

CouchRest gère même la pagination des documents (avec les méthodes de pagination internes à CouchDB).

docs = Document.all.paginate(:page => 1, :per_page => 10)

Vous pouvez donc utiliser CouchDB de manière particulièrement avancée, comme vous le faites déjà avec ActiveRecord pour vos SGBDR de type SQL.
Cependant avec CouchREST il vous faut toujours gérer la connexion à votre base de données « manuellement », dans un fichiers que vous placez dans config/initializers/ par exemple. Pas super pratique.
Heureusement il existe CouchREST Rails, qui implémente CouchREST (et uniquement CouchREST) mais gère automatiquement la connexion au serveur.

Commencez par installer le plugin.

script/plugin install git://github.com/hpoydar/couchrest-rails.git

Puis générez les divers fichiers de configuration relatifs à celui-ci.

script/generate couch_rest_rails

Vous allez maintenant notamment avoir un fichier config/couchdb.yml, qui contient la configuration relative à CouchDB en fonction de votre environnement.
Voici le contenu par défaut de ce document.

base: &base
 database_prefix:
 database_suffix: _<%= RAILS_ENV %>

development:
  host: localhost
  port: 5984
  <<: *base

test:
  host: localhost
  port: 5984
  <<: *base

A vous de configurer celui-ci comme vous le désirez en fonction de vos divers environnements et de vos besoins :)
Attention : vos modèles CouchREST ne doivent maintenant plus dépendre de ExtendedDocument mais de CouchRestRails::Document

Notre modèle précédent sera donc maintenant ainsi

class Document < CouchRestRails::Document

Redémarrez votre application et accédez directement à chacun de vos documents CouchDB via le modèle approprié :)
Si vous regardez le contenu de votre base de données, vous verrez que CouchREST fonctionne d’une manière similaire aux Single Table Inheritance de ActiveRecord.
Chaque document a un champ « couchrest-type », qui définit le modèle relatif au document.
Vous pouvez donc particulièrement aisément transformer un document en un autre document.

Supposons que vous ayez des documents de type « File » et d’autres de type « Image », vous transformerez toutes vos images en fichiers de la manière suivante.

File.all.each do |file|
    doc['couchrest-type'] = 'Image'
    doc.save!
end

Ne faites pas cela sans avoir intégralement testé toutes les conséquences possibles bien évidemment (sauf si avez le désir de mourir dans d’affreuses souffrances ;) ).

Comme vous le constatez, avec CouchREST, (même si le projet a quelques problèmes de communication), vous pouvez gérer vos données stockées dans CouchDB de manière aussi aisée que toute donnée stockée dans un SGBDR geré par ActiveRecord.

Dans un prochain (et dernier) article à propos de CouchDB, nous ferons un peu de javascript avec les vues, comment les implémenter (avec CouchREST) et comment s’en servir afin de profiter de toutes les fonctionnalités du SGBD.

Pour une raison que j’expliquerai dans un prochain billet, je m’intéresse actuellement à CouchDB.
Il s’agit d’un serveur de base de données orienté document.

Plus clairement, chaque serveur va avoir plusieurs bases.
Et chacunes de ces bases va avoir plusieurs documents. Chaque document pourra avoir un ou plusieurs champs et ces champs pourront varier en fonction du document.

Du coup pour une base « fichiers » par exemple, vous pourrez avoir un document qui représente une image et qui aura les champs « nom », « tumbnail » et « size ».
Et un document représentant un fichier texte et qui aura uniquement les champs « nom » et « size ». Chaque document ne possède que les champs dont il a besoin.

Pour l’installation du serveur, je vous invite à vous référer à la documentation officielle.
Une fois que cela est fait, passons à la suite de cet article.

CouchDB implémente un serveur HTTP acceptant des requêtes JSON. Mettons en place une librairie Ruby simple permettant de faire ces appels (et honteusement récupérée de la documentation CouchDB officielle).

require 'net/http'
module Couch
    class Server
        def initialize(host, port, options = nil)
            @host = host;
            @port = port;
            @options = options;
        end

        def delete(uri)
            request(Net::HTTP::Delete.new(uri))
        end

        def get(uri)
            request(Net::HTTP::Get.new(uri))
        end

        def put(uri, json)
            req = Net::HTTP::Put.new(uri)
            req["content-type"] = "application/json"
            req.body = json
            request(req)
        end

        def post(uri, json)
            req = Net::HTTP::Post.new(uri)
            req["content-type"] = "application/json"
            req.body = json
            request(req)
        end

        def request(req)
            res = Net::HTTP.start(@host, @port) {|http|
                http.request(req)
            }
            handle_error(req, res) if (not res.kind_of?(Net::HTTPSuccess))
            res
        end

        private
        def handle_error(req, res)
            e = RuntimeError.new("#{res.code}:#{res.message}\nMETHOD:#{req.method}\nURI:#{req.path}\n#{res.body}")
            raise e
        end
    end
end

Nous implémentons une méthode pour chacune des actions faisables avec tout serveur de base de données : listage des données, ajout d’un nouveau document, modification d’un document et suppression de celui-ci.

Utilisons cette librairie.

require 'json'
require 'digest/md5'

# Connexion au serveur
server = Couch::Server.new("localhost", "5984")

# Création de la base "foo"
server.put("/foo/", "")

# Ajout d'un document
doc = Hash.new(:name => 'Document Name', :content => 'The Content')
id = Digest::MD5.hexdigest(doc[:name] + '/' + doc[:content])
server.put("/foo/#{id}", doc.to_json)

# Lecture du document créé
p JSON.Parse(server.get("/foo/#{id}").body)

Détaillons ce que nous faisons.

server = Couch::Server.new("localhost", "5984")

Pour commencer, nous instancions la librairie CouchDB en précisant l’adresse du serveur et son port.

server.put("/foo/", "")

Puis nous créons une base de données nommée « foo ».
Une requête « PUT » sur l’url de la nouvelle base crée celle-ci.

doc = Hash.new(:name => 'Document Name', :content => 'The Content')
id = Digest::MD5.hexdigest(doc[:name] + '/' + doc[:content])
server.put("/foo/#{id}", doc.to_json)

Ici cela devient plus intéressant.

Nous commençons par créer un hash contenant tous les champs qui constitueront le document.
Puis nous définissons l’identifiant de ce document.

Nous définissons celui-ci en une chaine md5 et non pas un simple entier.
En effet si vous faites de la réplication de bases, vous risquez d’avoir des duplicats d’identifiants en se contentant d’un identifiant.
Ici avec une chaine md5 contenant le contenu originel du document, il y a extrêmement peu de chances que nous ayons des duplicats.

Puis nous transmettons une requête permettant de créer le document ayant l’identifiant que nous venons d’attribuer avec le contenu donné.

p JSON.Parse(server.get("/foo/#{id}").body)

Pour finir nous lisons le contenu du document que nous venons de créer.
Une requête GET sur l’url du document permet d’en retourner le contenu au format JSON, que nous parsons grâce à la librairie JSON Ruby. Il ne nous reste plus qu’à afficher le contenu de la manière dont nous le désirons.

Bien évidemment il existe de multiples librairies bien plus évoluées que ce que nous venons de faire.
Par exemple CouchREST, ActiveCouch ou RelaxDB.
Mais nous avons vu dans cet article les bases de l’utilisation de CouchDB et son fonctionnement. A vous de lire la documentation de ces projets si vous désirez les utiliser :)

Rassurez vous, je ne vais pas parler de religion.
Depuis quelques semaines déjà, j’utilise God afin de monitorer efficacement les divers processus sur mon serveur.

Ca fonctionne bien. Mais visualiser rapidement l’état de ceux-ci n’est pas évident car il faut pour cela se connecter en SSH et faire un « god status ». Pas super cool.
Après avoir cherché et pas trouvé d’interfaces web pour God, j’ai donc décidé de m’attaquer à en faire une.

Voici donc aujourd’hui la première version de l’interface entre God et les humains, j’ai nommé Jesus.
Il s’agit d’une application Ruby utilisant Sinatra.

Pour le moment, une seule action est possible : visualiser les processus gerés par God et voir si ils sont exécutés ou non.
Mais à l’avenir, il sera également possible de visualiser les logs pour chacun des processus.
Et de démarrer ou arrêter en « live » des processus.

Alors n’hésitez pas à y jeter un coup d’oeil et à me transmettre vos remarques :)

 
Fork me on GitHub