Ruby On Rails : sauvegarde de données dans un fichier temporaire

En Ruby, Si vous souhaitez créer un fichier temporaire (c’est à dire qui sera détruit par le système à la fin de son utilisation), vous pouvez utilisez la classe Tempfile.

Par exemple, vous avez besoin de créer un fichier zip temporaire qui se détruira une fois téléchargé par l’internaute.

Regardons comment fonctionne la classe Tempfile, c’est très simple, nous utilisons Ruby On Rails 5.

Tout d’abord nous déclarons notre fichier temporaire avec un nom et oui cela lui est nécessaire pour générer un nom temporaire à partir de la string passer en paramètres et de plein d’autre facteurs comme surement le temps.

Ensuite nous ouvrons le zip et injectons les fichiers souhaités dedans comme dans l’article sur la gestion des zip en Rails (pensez à installer la gem rubyzip) :

Vous aurez remarqué que le chemin du fichier temporaire zip_tmp se récupère naturellement par zip_tmp.path.

Après ce code nous avons un fichier temporaire qui contient notre fichier, ici pas besoin de « flusher » le fichier temporaire mais si nous avions un fichier temporaire csv écrit à partie d’une variable contenant le contenu il faut appliquer la méthode flush sur le fichier temporaire afin que l’écriture soit réellement réalisée sur le disque sinon impossible de faire appel à la méthode File.read sur le fichier temporaire comme par exemple :

Si vous exécutez le code précédent sans l’appel à la méthode flush, la string retournée sera vide.

Envoyons maintenant le zip à notre internaute par la commande send_file :

La commande send_file passe la main au serveur web pour envoyer le fichier créé, si vous utilisez apache2 le fichier temporaire sera détruit une fois que l’instance d’apache2 sera détruite, ce qui est plutôt bien suivant les cas.

On aurait pu utiliser la méthode send_data en lisant le contenu du fichier temporaire puis en supprimant le fichier temporaire à la main pour être sûr qu’il soit détruit comme suit :

Le fichier temporaire sera supprimé avant l’envoi des données à l’internaute mais son contenu sera bien transmis mais personnellement dans ce cas je ne vois pas trop l’intérêt d’utiliser des fichiers temporaires s’il faut gérer la destruction soit même, l’utilisation d’une instance de la classe File ferait aussi bien.

Voilà pour l’utilisation de la classe Tempfile et des fichiers temporaires en Ruby.

Enjoy 😉

Aïe ma partition /tmp est trop petite, comment déplacer le répertoire /tmp sous debian sur une autre partition ?

Si vous vous retrouvez dans le cas où votre partition /tmp est trop petite, par exemple avec un cron qui demande beaucoup de place dans /tmp et oui cela peut arriver, il existe une astuce pour déplacer votre répertoire /tmp sur une autre partition où vous aurez plus d’espace disque. Je vais faire ici un résumé du forum suivant : https://debian-facile.org/viewtopic.php?pid=43446#p43446, si vous souhaitez plus de détails sur le sujet de nombreuses notions sont abordées sur ce forum.

Dans notre cas d’exemple, nous avons un disque séparé en plusieurs partitions : la partition racine / de 10Go contenant notre fameux dossier /tmp et une partition pour /home de 145Go.

Le problème est survenue de manière silencieuse au départ, un cron qui grossit en terme d’actions à réaliser comme des rsync et donc qui grossit en terme d’espace temporaire nécessaire tant que la partition / avait assez de place pas de soucis le disque se remplissait (pas entièrement) de fichiers temporaires et une fois le cron fini les fichiers était « réellement » supprimé.

Ce point est important, il existe plusieurs commandes pour évaluer la place prises sur une partition « df » qui vous retourne l’espace occupé ainsi que l’espace total et par une soustraction magnifique cette commande arrive à vous retournez l’espace libre (c’est incroyable ;)) et une seconde commande « du » (et son dérivé « ncdu » basé sur le même comportement que « du ») qui elle vous retourne l’espace occupé par le dossier passer en paramètre. Et bien ces 2 commandes n’ont pas le même comportement, la commande « df » tient compte des fichiers supprimés mais encore présent sur le disque car ouvert par un processus, ce n’est pas le cas pour « du » (et son dérivé « ncdu ») qui lui ne considère plus les fichiers supprimés même s’ils sont encore ouvert par un processus. Et là nous avons un souci car on peut passer beaucoup de temps à regarder quel dossier est en surpoids avec « du » sans jamais trouver la réponse.

Ce point expliqué passons au vif du sujet, comme vous l’imaginez le problème n’est réellement apparu que lorsque le cron a saturé la partition racine est a donc fait « planté » le serveur.

A partir de là et après de multiples enquêtes à base de lsof | grep deleted pour récupérer la liste des fichiers effacés encore ouvert par un processus puis un ls -alh /proc/Processus_ID/fd/ pour voir où le ou les fichiers en question étaient stockés, je me suis aperçu que le dossier fautif était toujours /tmp.

Ce faisant j’ai fait des recherches et suis tombé sur ce forum traitant de debian, et voici comment j’ai déplacé le dossier temporaire du serveur debian.

J’ai utilisé la méthode dite « commune » dans le post ci-dessus (mais pas toutes les étapes, par exemple je ne suis pas passé en mode failsafe et oui serveur de prod oblige on évite les redémarrages). Je me suis donc mis en root puis assuré qu’aucun processus n’avait besoin de /tmp avec ps aux | grep tmp, s’il y en a il faut « killé » les processus avec kill -9 ProcessusId. Ensuite j’ai créer mon nouveau répertoire qui allait accueillir le contenu de /tmp (pas sur la partition racine on est d’accord sinon ça ne sert à rien) mkdir /home/MonCheminVersLeNouveauRepertoireTmp (ce dossier peut être préfixé par un point pour le caché ex : /home/MonCheminVersLeNouveauRepertoireTmpCaché/.tmp). On lui met les bons droits pour en faire un répertoire temporaire en bonne et dû forme chmod -R 1777 /home/MonCheminVersLeNouveauRepertoireTmp (Pour plus de détails sur le 1 de 1777 voire le forum). Jusque là tout va bien nous n’avons pas modifié le comportement de notre debian mais là nous allons voir le changement dans pas longtemps.
Avec toutes les précautions et le stress qu’il faut j’ai lancé un rm -rf /tmp, à cet instant il n’y a plus grand chose qui fonctionne (par exemple passenger pour les rubyistes) même la simple complétion des chemins lorsque l’on fait « tab » affiche une erreur très inquiétante, donc très rapidement je lance ln -s /home/MonCheminVersLeNouveauRepertoireTmp /tmp. A ce moment là la complétion avec « tab » refonctionne ouf c’est réparé et bien pas tout a fait encore une fois passenger lui ne se relance pas automatiquement notament son module pour apache2 donc faire un petit /etc/init.d/apache2 restart n’est pas de trop et je pense que d’autres programmes doivent être dans le même cas à vous de voir.

Et voilà, essayer de mettre des gros fichiers dans /tmp et un df -lh, plus tard vous verrez que ce n’est plus la partition / qui se remplie mais bien la partition /home.

Enjoy 😉

Ruby On Rails : gérer un zip

Il arrive parfois d’avoir besoin de retourner plusieurs fichiers lors de l’appel d’une action par exemple un csv avec des infos de base de données et des fichiers image associés à ces entrées en base de données.

Pour cela nous pouvons utiliser un zip dans lequel on mettra tous les fichiers nécessaires. Mais comment générer un zip depuis rails ? Ça tombe bien il existe une gem pour cela : rubyzip.

Regardons comment elle fonctionne sur un projet en rails 5 pour générer un zip puis le transmettre et lire son contenu dans une seconde action.

Pour l’exemple nous aurons un model article avec comme attribut un titre, un contenu, une date de publication et un champ nom_image. À ces articles sont associées des images qui sont stockées dans le dossier db/images/NOM_IMAGE

Première étape : Générer le zip

Pour cela nous allons installer la gem rubyzip avec un gem 'rubyzip' à mettre dans le gemfile puis un bundle install.

Maintenant nous allons déclarer une nouvelle action dans le contrôleur articles qui va utiliser cette gem pour créer un fichier zip avec à l’intérieur le csv des infos des articles et les fichiers associés à ces articles. Commençons par générer le CSV (de nombreuses façons sont possibles pour le faire) :

Pensez à mettre votre route comme suit :

get 'articles/exporter_zip'

La génération du csv est terminée.

Passons à l’ajout de ce csv à notre zip grâce à la gem rubyzip.

Pensez à inclure la librairie zip en mettant require 'zip' dans votre contrôleur.

Modifions maintenant notre méthode d’export comme suit :

Jusqu’ici pas de souci cela fonctionne bien.

Passons à l’ajout des fichiers images liés à nos articles.

Regardons comment modifier notre action :

Et voilà nous venons de créer un fichier zip que l’on transmet par la suite.

Si vous souhaitez que ce fichier soit temporaire nous verrons cela dans un prochain article sur les TempFile.

Seconde étape télécharger et extraire ce fichier

Pour cela nous allons créer pour l’exemple une seconde application de test qui ne fera que télécharger le fichier depuis la première application et extraire le zip pour pouvoir manipuler les fichiers qui sont à l’intérieur.

Dans cette application numéro 2 que nous appellerons APPLI2 nous n’aurons qu’une action dans l’application controller qui téléchargera le zip de puis la première application que nous appellerons APPLI1 et en extraira les fichiers.

Dans APPLI2 nous allons utiliser la gem rest-client.

On place donc gem 'rest-client' dans le gemfile puis un bundle install.

Définissons notre route get 'application/essai' puis notre action comme suit :

A ce moment là nous avons bien téléchargé le fichier zip et stocké ce dernier dans db/articles.zip

Maintenant lisons le fichier afin d’extraire les fichiers inclus dans le zip. Pour cela, ajoutez dans le gemfile gem 'rubyzip', faites un bundle install et ajoutez require 'zip' en haut du contrôleur application.

Modifions notre action :

Et voilà nous venons de voir comment manipuler des zip avec Ruby On Rails pour les émettre puis les traiter.

Enjoy 😉

Ruby on Rails helper_method et view_context, quels usages ? Quelles différences ?

Sur une application Ruby on Rails, il arrive parfois que nous ayons besoin d’utiliser une méthode dans un contrôleur et dans une vue.

Plusieurs solutions existent d’un côté les helper_method, de l’autre les méthodes écrit dans des helpers et utilisées dans la vue par un simple appel qui se complexifie un peu lorsque l’on veut l’utiliser dans le contrôleur où il faut faire précéder l’appel de la méthode view_context.

Essayons de prendre un cas de figure assez simple, nous souhaitons retourner l’objet entier de la personne actuellement connecté sur notre application. Pour cela nous avons une session avec une variable de session utilisateur_id qui contient l’identifiant de notre personne dans la table utilisateurs.

Le code pour retourner l’objet est assez simple :

Là où cela se complique c’est où placer ce code car j’en ai besoin à la fois dans mon contrôleur mais aussi dans ma vue. Dans le contrôleur pour faire du contrôle d’accès aux autres objets par exemple, et dans ma vue pour afficher « Bonjour » suivi du prénom de la personne.

La première information est la sémantique du code écrit ici, nous faisons une requête pour récupérer un objet c’est typiquement ce que doit faire un contrôleur donc pas de doute ce code doit être de base définit dans le contrôleur puis rendu accessible à la vue. Nous allons donc définir ce code dans un contrôleur par exemple l’application contrôleur (pour que la méthode soit accessible dans tous les autres contrôleurs) puis toujours dans l’application contrôleur nous allons indiquer que c’est un helper_method, ce qui nous donnerai en Rails 5 :

A partir de là vous pouvez utiliser la méthode soit dans la vue en faisant par exemple :

Soit utiliser la méthode dans le contrôleur pour faire :

Amélioration possible, si la méthode utilisateur_courant est appelée plusieurs fois par action, cela génère plusieurs requête sql alors que dans une même action la session ne change pas trop en général, pour éviter cela nous pouvons faire :

Avec cela, la requête sql s’exécutera au premier appel de notre méthode utilisateur_courant et stockera le temps de l’action le résultat de la requête dans la variable @utilisateur, si sur le temps de l’action nous faisons un second appel à utilisateur_courant, la méthode retournera directement le contenu de la variable @utilisateur et donc ne fera pas une seconde requête sql inutile dans bien des cas.

Ok très bien donc la sémantique nous permet de voir où placer le code et quand faire des helper_method dans le contrôleur, dans ce cas quelles sont les types de méthodes à mettre dans les helpers de vue ?

Déjà quel est l’objectif des helpers ?

Les helpers permettent de :

  • partager du code entre les vues
  • d’alléger les vues
  • de placer la complexité ailleurs que dans la vue (par exemple un case when)

Prenons un exemple :

Dans mon application rails j’ai un objet article avec comme attribut titre qui est une string, contenu qui est un texte et date_de_publication qui est une date. Il fréquent de vouloir afficher le statut de l’article en fonction de la date de publication, si la date n’est pas affectée le statut est brouillon, si la date est dans le futur le statut est programmé, si la date est dans le passé le statut est publié. Nous avons donc à partir d’un champ 3 affichages possibles différents dans notre vue. On peut donc vite se retrouver avec quelque chose comme :

Bon là ce n’est pas le top car la complexité, ici les 3 tests de la valeur de date_de_publication, est directement dans la vue si nous souhaitons « laissé de côté » cette complexité en faisant confiance à une méthode, cela permettrait lors d’une lecture ultérieure du code de ne pas avoir en tête cette complexité si nous n’en avons pas besoin.

Nous allons donc déplacer cette complexité dans un helpers et oui c’est notamment à cela que ça sert, de plus ces statuts ne sont valables pas seulement pour les articles mais aussi pour d’autres modèles comme les catégories ou autre, nous obtenons donc dans notre helper :

et dans notre vue, un code bien plus « léger » :

Voilà notre helper est créé, maintenant imaginons une mise à jour de la date de publication en ajax qui en retour nous retournerai le nouveau statut de l’article ça serait pas mal pour mettre à jour dans notre vue. Un peu de code sur la vue liste des articles :

Ici nous avons utiliser le remote: true qui permet de faire de l’ajax de manière rapide avec rails plutôt pas mal au passage ;). Après il nous faut « binder » le moment où la requête se termine en état succès dans la partie javascript (nous ne traitons pas le cas ajax:error car ce n’est pas le but de cet article). L’action publie_article du controller articles permet de mettre la date du jour en date de publication et de retourner le statut de l’article après mise à jour de celui-ci via notre méthode de helper pour mise à jour de la donnée dans la vue. Regardons du côté controller :

Dans le fichier de routes nous avons ajouté post 'articles/publie_article/:id', to: 'articles#publie_article', as: 'publie_article' pour accéder à notre action.

Voilà nous avons vu les 2 cas d’utilisation d’une méthode à la fois dans la vue et le contrôleur, petite info lors de l’appel à view_context.statut cela instancie une nouvelle vue à chaque appel donc ne pas en abuser est une bonne chose pour les performances.

En conclusion les 2 solutions sont bonnes ce qui est primordial est de placer le code au bon endroit en fonction de ce qu’il fait.
Ici l’exemple aurait pu être réalisé autrement avec un concern de modèle inclut dans les X modèles nécessitant la méthode d’instance statut qui retournerait le statut en fonction de la date_de_publication mais avec notre helper l’avantage est que si les modèles ont l’attribut date_de_publication nommée différemment les uns des autres cela fonctionne toujours, à voir donc en fonction des cas et du contexte.

Source : http://apidock.com/rails/v4.2.7/AbstractController/Helpers/ClassMethods/helper_method, http://stackoverflow.com/questions/5130150/rails-how-to-use-a-helper-inside-a-controller

Ruby On Rails, JavaScript, execjs et paramètres par défaut

Sur Ruby On Rails, les assets notamment javascript sont compilés par l’asset pipeline et notamment par execjs.

Si vous souhaitez mettre un paramètre par défaut dans une déclaration de fonction javascript et que vous êtes sur un projet Ruby On Rails avec compilation de ce javascript par l’asset pipeline, vous aurez le droit à une belle erreur par exemple le code

Vous retournera une erreur comme « ExecJS::ProgramError: Unexpected token operator «=», expected punc «,» ».

Pourquoi ?

La première chose à savoir est que la déclaration de fonction avec un paramètre par défaut en javascript vient de « la spécification de langage » ECMAScript 2015 (ES6) en date de Juin 2015.
Voici ensuite la compatibilité de cette spécification pour la partie paramètre par défaut dans une déclaration de fonction, autant le dire le support est loin d’être acquis hormis sur Chrome et Firefox mais nous nous en serions doutés.

Dû à cette superbe compatibilité et d’après la doc execjs il ne faut compter que sur ES3 autant dire que l’on voit directement le souci.

D’après cet article, il semblerait bien qu’il existe des solutions pour palier à cela notamment compatible avec Ruby On Rails et qui gère ensuite le support navigateur mais cela semble assez lourd à mettre en place à travers du nodeJs et de la surveillance de fichier etc.

Voilà il faudra être patient avant d’avoir la possibilité d’utilisé ce paramètre par défaut mais une méthode alternative consiste à ne pas indiquer dans la définition de la méthode que le paramètre est optionnel et tester dans le corps de la méthode sa valeur et si c’est undefined on la set avec une valeur par défaut cf exemple.

Rails faire une méthode qui s’appliquera sur le résultat d’une requête d’une classe (un activerelation d’instances activerecord de cette classe)

Si vous souhaitez pouvoir faire quelque chose comme exporter le résultat d’une recherche d’une classe en csv avec tous les attributs de l’objet en 2 temps 3 mouvements et le faire sur différentes requêtes voici une petite astuce.

Petite info nous allons utiliser Rails5 mais cela est valable pour Rails4 à ceci prêt que la définition de la classe change un peu mais vous pouvez reprendre la méthode uniquement.

Prenez votre classe par exemple la classe MonObjet avec l’attribut titre dans le model faîtes une définition de méthode de classe comme ceci :

On voit ici le mot clé « all », ce mot clé indique que nous allons traiter chacun des éléments de l’activerecord et non pas forcément toutes les instances en BDD de notre model mais bien les instances de l’activerecord sur lequel nous allons appelés notre méthode « to_csv ».

Il vous suffit alors dans un controler de faire :

Ici on effectue une requête pour retrouver les objets dont le titre contient la valeur passer en paramètre et nous appliquons la méthode « to_csv » sur les résultats de cette requête.

Voilà une méthode simple qui peut vous permettre de factoriser du code d’export csv après on peut imaginer passer en paramètres à la fonction « to_csv » la liste des attributs de l’objet à exporter ou encore des contraintes supplémentaires de transformation de format pour les dates etc….

Julien

Faire une somme de la multiplication de 2 colonnes de chaque ligne d’une requête en Ruby On Rails

Aïe vu le titre ça part mal. Prenons un exemple.

Nous avons une table de lignes de commandes contenant une colonne « quantite », une colonne « prix_unitaire » et une colonne « produit_id », nous aimerions connaitre le chiffre d’affaires généré par le produit phare « un stylo » dont l’identifiant est 1.

On veut donc d’une part calculer le montant total pour chaque ligne de commande du produit dont l’id est 1 pour ensuite faire la somme de ces montant totaux et pour que cela soit rapide on souhaite le faire en sql et oui pas de map et compagnie qui prennent un temps non négligeables en fonction du nombre de ligne.

Pour cela c’est très simple il faut faire ceci :

LigneCommande.where(id_produit: 1).sum("quantite * prix_unitaire")

Et voilà 😉

Gem geocoder et Model

Avec la gem geocoder, déjà présentée ici et , vous pouvez avoir besoin d’importer une première fois vos objets points possédant une colonne address et 2 colonnes de coordonnées GPS latitude et longitude importer à « NULL » pour ensuite venir géocoder ces adresses et les sauvegarder.

Pour cela, c’est assez simple, il suffit de boucler sur tous les points de votre base et faire pour chacun d’entre eux un appel à la méthode « geocode » et ça fonctionne mais stupeur rien dans votre base et oui la méthode « geocode » ne modifie pas votre objet en base mais seulement en mémoire penser donc bien à faire un petit « save » derrière.

Voilà 😉

En MySql faire un distinct sur une combinaison de colonne

En MySql, si vous avez une table avec plusieurs colonnes, par exemple une table véhicule avec une colonne catégorie (auto, moto, camion) et une colonne couleur (rouge, vert, noir) et que vous désirez savoir combien de combinaisons votre table contient de manière unique (dédoublonnée). Il vous faut concaténer ces X colonnes et faire un distinct sur le résultat ou un where ou ce que vous voulez avec cette combinaison.

Exemple :

SELECT DISTINCT(CONCAT(colonne1, ' ', colonne2)) FROM ma_table WHERE 1

Ici nous avons concaténé la colonne 1 et 2 avec un espace mais vous pouvez choisir un séparateur ou non et enchainez autant de colonne ou de séparateur, préfixe ou suffixe que souhaitez.

SELECT CONCAT(colonne1, ' ', colonne2) nom_resultat FROM ma_table WHERE nom_resultat LIKE '%ma_recherche%'

Là nous avons fait de même concaténation de la colonne 1 et 2 avec un espace comme séparateur, nommage de ce résultat pour l’utiliser par la suite dans la condition where.

Voilà, Julien.

Debian : monter un disque externe à chaud sur une VM

Si vous avez une vm sous Debian, avec un disque « branché » après démarrage et que vous ne souhaitez pas redémarrer votre vm pour la prise en compte de ce disque (ce que l’on appelle monter des ressources à chaud), cet article est pour vous.

Première chose, « branchez » votre disque sur votre VM.

Ensuite, connectez-vous en ssh et passez en root.

Un petit fdisk -l, à ce moment là vous ne voyez pas le nouveau disque, zut ! Il y a seulement le disque actuel avec ses partitions, par exemple sda, sda1, sda2, …

Pour cela nous allons devoir demander à notre Debian de « re-scanner » les disques connectés, c’est parti.

Tapez : echo "- - -" > /sys/class/scsi_host/host{NB}/scan, {NB} étant un chiffre souvent 0, 1 et 2 par exemple. Il faut le faire sur tous pour savoir les nombres, faites un ls /sys/class/scsi_host/.

Une fois ceci fait on relance un fdisk -l et là, magie, vous retrouvez votre nouveau disque ainsi que les partitions présentes sur celui-ci, par exemple sdb, sdb1, sdb2, …

A partir de là, cela se simplifie, il ne reste plus qu’à monter la partition souhaitée avec pour la partition sdb1 et un dossier créé pour la « réception » du montage /mon_dossier_de_destination_pour_montage : mount /dev/sdb1 /mon_dossier_de_destination_pour_montage

Ensuite, il ne vous reste plus qu’a parcourir le dossier monté en allant dans le dossier cd /mon_dossier_de_destination_pour_montage

Et voilà, fini 😉

Source : http://monblog.system-linux.net/blog/2014/02/27/ajout-dun-disque-a-chaud-vmwa-lvm/comment-page-1/, https://doc.ubuntu-fr.org/mount_fstab