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.

 
Fork me on GitHub