Tester la valeur d'un champ booléen en base de données avec Ruby on Rails et SQLite

Si vous développez un projet utilisant le framework Ruby on Rails et le SGBD SQLite, et qu'à cette occasion vous stockez des valeurs booléennes en base de données (correspondant à autant de champs :boolean dans vos modèles), vous constaterez peut-être qu'une requête comme celle-ci :

articles = Article.all(:conditions => 'active = 1')

...ne fonctionne pas comme vous l'attendiez. En effet, dans un tel environnement, les valeurs booléennes ne sont pas stockées en tant que TINYINT prenant 0 ou 1 comme valeur, mais plutôt comme VARCHAR(1) contenant 't' ou 'f'.

Vous pouvez bien sûr simplement modifier votre requête "en dur", mais cela ne vous garantit pas un fonctionnement correct indépendamment du SGBD utilisé (et il y a fort à parier que SQLite ne fera plus partie du paysage une fois en production).

Dans ce cas, il est préférable de passer directement la valeur booléenne attendue, et de laisser Rails se charger de la préparation de la requête :

Article.all(:conditions => ['active = :active', { :active => true }])

Ainsi, vous pouvez même utiliser une variable ou le retour d'une fonction de manière plus directe !

Styler un champ d'upload en CSS

En préambule, je tiens à rassurer ceux qui risquent l'énucléation à la vue du titre de cet article : non, je n'ai pas découvert de solution miracle capable d'outrepasser les limites intrinsèques des navigateurs quant à l'application de styles CSS sur les input[type=file] ! Il s'agit simplement d'une astuce que tout le monde ne connaît peut-être pas et peut se révéler un atout précieux dans la tâche fastidieuse qu'est le design de formulaires HTML.

L'idée est tout simplement de masquer l'input originel, et de transférer grâce à JavaScript son contrôle à d'autres éléments mis en place à cet effet. Commençons par le HTML :

<label>
<input type="file" name="my_file" id="upload" />
<input type="text" id="fake_upload" disabled="disabled" />
<button>Parcourir</button>
</label>

Ce markup est peut-être discutable, étant considéré comme invalide par le W3C : il me paraît personnellement sensé, s'agissant d'un cas très spécifique. À vous de voir !

Voyons ensuite le code CSS lié au fameux champ d'upload. Je passe volontairement sur le champ texte et le bouton, l'objectif étant de leur donner l'apparence que vous souhaitez !

label [type=file] {
position: absolute;
width: 0;
height: 0;
}

Pourquoi s'ennuyer à jouer sur les dimensions alors qu'il suffirait d'un classique mais efficace display: none, me demanderez-vous ? Tout simplement parce sur certains navigateurs (notamment Google Chrome sur Android à l'heure où j'écris ces lignes), les éléments masqués ne peuvent être la cible d'évènements JS. Le positionnement absolu est là pour sortir l'input du flux afin d'éviter un éventuel décalage de quelques pixels (pouvant se produire, et je choisis un exemple tout à fait au hasard, sur Internet Explorer).

Passons maintenant au code JS, pour lequel nous nous appuierons sur jQuery. La difficulté ici est que certains navigateurs (comme par exemple Google Chrome) transmettent automatiquement l'évènement click sur le label à l'input[type=file] qu'il contient, alors que d'autres (tel Mozilla Firefox) n'en font rien. Il nous faut donc ruser un brin :

$('label').on('click', function(e)
{
// Vérifions en premier lieu la cible "réelle" du clic/toucher
var target = e.target || e.srcElement;

// S'il s'agit du bouton, on transmet au champ d'upload
if ($(target).is('button')) {
$(this).find('[type=file]').click();
}

// Lors du clic simulé ci-dessus, ce même handler est appelé à nouveau
// On empêche donc l'éventuel comportement par défaut sauf dans ce cas précis
return $(target).is('[type=file]');
});

$('label [type=file]').on('change', function()
{
// Lorsqu'un fichier est choisi, on remplit notre "faux" input avec son nom
$(this).siblings('[type=text]').val($(this).val());
});

Cette astuce a été testée sans succès sur IE (dans ses versions 8 et 9) : si tout semble fonctionner en apparence, ce dernier refusera catégoriquement de procéder correctement à l'upload du fichier si la pop-up de sélection n'a pas été ouverte via un clic sur l'élément natif (sans JavaScript). Si vous avez une solution, je suis preneur ! Dans l'intervalle, je vous recommande de masquer les éléments décoratifs et de réafficher le "vrai" input[type=file] pour IE.

Utiliser les modèles de RedBean avec un namespace PHP

RedBean est un ORM PHP dont le principal atout est la concision de la syntaxe ; en effet, l'usage générique ne requiert que l'appel à des méthodes de la classe frontale R, ce qui permet d'effectuer toutes sortes d'opérations en base de données en un minimum de caractères. Ceci à un point tel que l'utilisation de modèles pour représenter de manière plus concrète les objets manipulés est un point qui arrive étonnamment tard dans la documentation de l'outil.

De son propre aveu, l'auteur n'est pas un fan des namespaces introduits dans PHP 5.3 ; en toute objectivité, cette vision peut devenir un frein considérable à l'apogée de Composer et de PSR-0, à l'image du petit challenge que je me propose de vous aider à résoudre aujourd'hui.

En effet, RedBean propose une liaison automatique entre les "beans" (les objets de base obtenus lors des requêtes en lecture) et les éventuels modèles plus complexes que vous pouvez être amené à créer selon vos besoins, le tout reposant sur FUSE. Là où le bât blesse, c'est que ce dernier ne sera pas capable de trouver vos classes si celles-ci sont écrites sous un namespace donné. Il est heureusement possible de surcharger le comportement par défaut de l'ORM en la matière, mais le manque de documentation précise à ce sujet peut vous mener droit vers quelques heures de serrage de dents et d'arrachage de cheveux (c'est du vécu). Voici un exemple de solution !

Tout d'abord, nous allons surcharger la classe concernée de RedBean, et plus précisément sa méthode chargée de trouver le modèle correspondant à un "bean" donné :

<?php

namespace MyNamespace;

class MyBeanHelper extends \RedBean_BeanHelper_Facade
{
public function getModelForBean(\RedBean_OODBBean $bean)
{
$modelName = 'MyNamespace\\Model\\'.ucfirst($bean->getMeta('type'));

if (! class_exists($modelName)) {
return null;
}

$obj = \RedBean_ModelHelper::factory($modelName);
$obj->loadBean($bean);
return $obj;
}
}

Notez que pour l'exemple, je me contente d'un copier-coller de la fonction d'origine où j'ajoute mon namespace "en dur" devant le nom de modèle généré par défaut (tout en optimisant un peu l'ensemble). Que cela ne vous empêche pas de mettre à profit le fait de devoir créer une classe dans l'unique but de faire entendre raison à RedBean, afin d'en faire quelque chose d'un peu plus maniable (par exemple, en générant le namespace selon le type de "bean" auquel vous êtes confronté... tout dépend de la structure de votre code).

Achevons le travail en remplaçant l'instance de la classe d'origine par une de la nôtre :

// Faisons en sorte de conserver le petit nom de la classe façade de l'ORM
use \RedBean_Facade as R;

R::$toolbox->getRedBean()->setBeanHelper(new MyNamespace\MyBeanHelper());

C'est tout ! Je n'ai pas trouvé de solution vraiment universelle pour pallier ce type de situation ; si vous vous en sortez mieux, n'hésitez surtout pas...

Empêcher le commit d'un fichier déjà versionné sur SVN

Posons le décor : vous rejoignez un projet utilisant le gestionnaire de versions SVN. Vous clonez le dépôt, et dans 99% des cas, vous adaptez certains fichiers (de configuration par exemple) à votre environnement. Le hic, c'est que ces modifications seront prises en compte lors du prochain commit, et l'utilisation de la propriété svn:ignore ne vous sera d'aucun secours puisqu'elle ne concerne que les fichiers non versionnés. Un svn rm --keep-local est également hors de question puisqu'il supprimerait le fichier du dépôt, voire chez les autres utilisateurs (sans compter que cette option n'est disponible qu'à partir de la version 1.5.0). Voici comment passer outre cet état de fait :

cp config.file /some/other/folder # on copie le fichier hors du dépôt
svn rm config.file # on supprime le fichier à la fois aux yeux de SVN et "physiquement"
svn propset svn:ignore "config.file" . # le fichier n'existant plus, on peut dire à SVN de l'ignorer à l'avenir
mv /some/other/folder/config.file . # on ramène le fichier ; il n'est plus versionné, et ne sera plus pris en compte grâce à la commande précédente
svn revert config.file # on demande à SVN d'"oublier" la suppression programmée de ce fichier

Désormais, ce fichier ne sera plus committé, même avec un svn add --force. Pour appliquer le même traitement à un répertoire entier, il vous suffit d'ajouter l'option --recursive à la commande svn revert.

Modifier un modèle sur PrestaShop 1.5

La dernière version en date du CMS e-commerce PrestaShop en améliore pas mal d'aspects d'un point de vue technique, surtout concernant la surcharge des comportements par défaut. Ainsi, dans l'esprit de frameworks tels que FuelPHP, vous disposez à la racine du site d'un dossier override où vous pouvez facilement redéfinir tout ou partie de n'importe quelle classe du système. Ceci vous permettra de mettre PrestaShop à jour en conservant vos modifications.

Malgré tout, cela reste assez peu évident à mes yeux d'ajouter par exemple un champ aux produits (pour leur définir une caractéristique supplémentaire), les modèles de PrestaShop n'étant pas vraiment conçus en tant qu'entités. Voici la marche à suivre !

Tout d'abord, créons le champ correspondant dans la table qui va bien :

ALTER TABLE `ps_product` ADD `custom_field` INT NOT NULL;

Ensuite, il faut déclarer ce nouveau champ dans la classe Product en tant que membre, et le référencer dans un énorme tableau statique définissant les contraintes de chaque champ. Comme vu plus haut, nous allons surcharger la classe d'origine en créant le fichier override/classes/Product.php. Mais plutôt que de copier-coller tout le tableau, rusons un brin :

Product::$definition['fields']['custom_field'] = array('type' => ObjectModel::TYPE_STRING, 'validate' => 'isString');

class Product extends ProductCore
{
public $custom_field;
}

Jusqu'ici tout va bien ! En revanche, il serait quand même sympa que l'on puisse éditer la valeur de ce champ pour chaque produit directement en back-office, non ? Il faut pour cela ajouter, dans le template correspondant, un champ HTML portant le nom de votre propriété et étant hydraté avec sa valeur par Smarty (exactement comme les champs déjà présents). En l'occurence, ce fichier sera à choisir (selon l'endroit où le champ devra apparaître) dans le dossier admin/themes/myawesometheme/template/controllers/products.

Last but not least : si vous suivez les bonnes pratiques de développement sur PrestaShop, vous avez conservé le thème par défaut intact et l'avez dupliqué sous un autre nom afin de pouvoir travailler dessus. Idéalement, vous avez suivi le même cheminement en back-office, au cas où vous seriez justement amené à effectuer des modifications telles que celle décrite à l'instant. Dans ce cas, sachez qu'il faut indiquer à PrestaShop quel thème sera utilisé lors de l'accès au back-office, et ce, indépendamment du thème sélectionné pour la boutique, et individuellement pour chaque compte administrateur ! Gagnons donc du temps :

UPDATE `ps_employee` SET bo_theme='myawesometheme';