[Access]Gestion Auto de la Liaison des Tables

Je sais que j’ai plutôt l’habitude d’écrire à propos de logiciel libre, et que je préfère aborder un point concernant GNU/Linux que le système privateur Windows et ses outils associés. Seulement parfois, un étudiant en informatique n’a pas le choix des technos à utiliser pour réaliser son projet et est contraint d’utiliser des solutions privatrices. Ce fut le cas sur un projet de base de données où le logiciel imposé était Access. Cet article va donc aborder un point précis qui m’avait demandé de nombreuses heures de recherche, essais, etc avant d’arriver à réaliser la fonctionnalité voulue. En espérant que ces informations pourront aider d’autres étudiants par la suite et leur feront gagner un peu de temps. Je vais donc aborder la mise en place d’un système de liaison automatique des tables dans Access.

Tout d’abord, il faut savoir que dans notre situation, nous possédons deux fichiers. L’un contient l’ensemble des tables de notre base de données, l’autre contient l’application les utilisant. Dans le fichier de l’application, il est nécessaire de réaliser la liaison avec les tables de notre base de données. Cette liaison peut être facilement réalisée à la main. Néanmoins, un problème se pose avec cette façon de faire. En effet, Access retient l’emplacement absolu de votre fichier de données depuis la racine du disque dur soit quelque chose comme C:\Utilisateurs\Public\MonProjet\data.mdb par exemple. Et c’est là que l’on commence à s’arracher les cheveux puisque si je déplace le dossier MonProjet, la liaison des tables continue de pointer vers C:\Utilisateurs\Public\MonProjet\data.mdb. L’horreur quand vous êtes amené à changer d’ordinateur. Il existe pourtant une solution qui consiste à vérifier l’attachement des tables à chaque lancement de l’application. Si le chemin est correct, pas de problème, sinon, on refait la liaison automatiquement en construisant nous même le chemin absolu jusqu’au fichier. Il faut noter que dans l’exemple que je vais développer, le formulaire de démarrage de l’application ne fait aucune requête vers la base de données, afin d’éviter des erreurs (bien qu’il doit être possible de faire ces requêtes seulement lorsque la liaison a été vérifié et est correcte).

Je crée donc un fichier Access qui contiendra ma base de donnée et le nomme EXEMPLE_DATA. Pour notre exemple, on y ajoute une table PERSONNES avec divers champs. Par ailleurs, il faut créer le fichier qui contiendra l’application EXEMPLE_APPLI. Maintenant que nous disposons de nos deux fichiers, nous allons pouvoir effectuer la première liaison des tables manuellement. Onglet External Data, icône Linked Table Manager. Une fois la liaison effectuée, nous ajoutons un bête formulaire d’accueil dans notre application. Celui-ci ne contient rien d’autre qu’un simple texte. Nous pouvons alors mettre en évidence notre problème, couper/coller votre fichier EXEMPLE_DATA dans un autre dossier que le dossier courant, ré-ouvrez EXEMPLE_APPLI, le fichier contenant les tables est maintenant introuvable. Nous allons donc y remédier en mettant en place la liaison automatique des tables à chaque démarrage de l’application. Il est bien sûr possible de mettre en place un système permettant de ne lier les tables que si le fichier de base de données est introuvable.

Dans notre application, nous allons donc ajouter le module suivant nommé liaison_auto qui contient les fonctions dont nous avons besoin pour la liaison. Enfin, à l’ouverture de notre formulaire d’accueil, nous appelons les fonctions qui vont bien.

Option Compare Database

Public Function VerifAttach() As Boolean
Dim tdf As DAO.TableDef, strTemp As Variant, strPath As String, i As Long
For Each tdf In CurrentDb.TableDefs
' Recherche d'une table liée
If tdf.Connect <> "" Then
strTemp = Split(tdf.Connect, ";")
For i = LBound(strTemp) To UBound(strTemp)
' Recherche du paramètre de connection
If strTemp(i) Like "DATABASE=*" Then
strPath = Split(strTemp(i), "=")(1)
' Vérification de l'existence de la bdd
If Dir(strPath) <> "" Then
VerifAttach = True
Exit Function
End If
End If
Next i
End If
Next
End Function

Public Sub DeleteTables()
' Supprimer toutes les tables attachées
On Error Resume Next
Dim db As DAO.Database 'Database to import
Dim tdf As DAO.TableDef
Dim arrTablename() As String, i As Long
ReDim arrTablename(0)
Set db = CurrentDb
' Répertorier les tables à supprimer
For Each tdf In db.TableDefs
If tdf.Connect <> "" Then
ReDim Preserve arrTablename(UBound(arrTablename) + 1)
arrTablename(UBound(arrTablename)) = tdf.Name
End If
Next
' Suppression
For i = LBound(arrTablename) To UBound(arrTablename)
db.TableDefs.Delete arrTablename(i)
Next i
Set db = Nothing
End Sub

Public Function ActualiserAttaches(ByVal strCheminBd As String, Optional ByVal strMotPasse As String = "") As Boolean
On Error GoTo ActualiserAttaches_Err
Dim tdf As DAO.TableDef

strSourceConnect = "MS Access;PWD=" & strMotPasse & ";DATABASE=" & strCheminBd
' Supprimer les tables avant tout
DeleteTables

'Permet de lier toutes les tables
Dim strConnect As String
Dim strNomsTables() As String
Dim strTemp As String
Dim i As Integer
Dim oDb As DAO.Database
Dim oDbSource As DAO.Database
Dim oTbl As DAO.TableDef
Dim oTblSource As DAO.TableDef

'Définit la chaine de connexion permettant la liaison des tables
strConnect = "MS Access;pwd=" & strMotPasse & ";DATABASE=" & strCheminBd
'Instancie l'objet Database de la base courante
Set oDb = CurrentDb
'Instancie l'objet Database de la base protégée
Set oDbSource = DBEngine.OpenDatabase(strCheminBd, True, True, strConnect)

'Parcours l'ensemble des tables de la base de données protégée
'et stocke leur nom
For Each oTblSource In oDbSource.TableDefs
    'Ignore les tables system
    If (oTblSource.Attributes And dbSystemObject) = 0 Then
        strTemp = strTemp & oTblSource.Name & "|"
    End If
Next
'Ferme la base de données sources (impératif pour la liaison)
oDbSource.Close: Set oDbSource = Nothing
'Parcours le tableau de noms de tables
strNomsTables = Split(Left(strTemp, Len(strTemp) - 1), "|")
For i = 0 To UBound(strNomsTables)
  'Crée une nouvelle table dans la base de données courante
  Set oTbl = oDb.CreateTableDef(strNomsTables(i))
  'Lie les deux tables
  oTbl.Connect = strConnect
  oTbl.SourceTableName = strNomsTables(i)
  'Ajoute la table à la base de données
  oDb.TableDefs.Append oTbl
Next i

'Rafraichit la liste des tables
oDb.TableDefs.Refresh

ActualiserAttaches = True

If ActualiserAttaches = True Then
    MsgBox "Tables de la base de données " & strCheminBd & " liées avec succés"
End If

Exit Function
ActualiserAttaches_Err:
MsgBox "Error " & Err.Number & " (" & Err.Description & _
") in Function ActualiserAttaches of Module mdFonctions", vbCritical
End Function

Ce code devrait donc vous permettre de réaliser sans difficulté la liaison automatique des tables au démarrage de votre application. Il est bien sûr possible de ne faire l’opération de liaison que lorsque celle-ci est nécessaire, ce que j’avais fait à l’époque mais qui me levait des erreurs pour cet exemple et que j’ai donc simplifié, n’ayant pas envie de débugger du VBA pendant des heures ;). Implémenter la fonctionnalité en s’inspirant de cet exemple devrait donc être plutôt rapide.

Il reste toutefois un peu de code à écrire du côté du formulaire d’accueil. Celui-ci est relativement simple, à noter tout de même que l’on considère que nos deux fichiers sont stockés au même endroit sur le disque pour la génération du chemin vers la base Data.

Option Compare Database

Private Sub Form_Open(Cancel As Integer)
' Permet de contrôler la mise à jour des tables

Dim strTemp As String
Dim strChemin As String

  'CHANGER LE NOM DE LA BASE DATA ICI
  strChemin = CurrentProject.Path & "\EXEMPLE_DATA.accdb"

  DeleteTables

  If ActualiserAttaches(strChemin) = True Then
    VerifAttach

  Else

    MsgBox "Mise à jour des Tables non éffectuées, " & vbCrLf & _
      "veuillez contacter l'administrateur de la base.", vbCritical,
      "Liaisons des tables"
    'Fermeture de l'application
    DoCmd.Close

  End If
End Sub

J’espère que cet exemple sera utile au lecteur qui aura fait l’effort de me lire jusqu’au bout. Enfin, n’hésitez pas à laisser une remarque, addition ou autre en commentaire.

Zip contenant les fichiers d’exemple: EXEMPLE.

 

Articles intéressants pour un projet Access:
Gestion de photos par formulaire
Splash-screen
Mode Multi-Utilisateurs

Note: Le module de liaison avait été trouvé sur le net, très certainement sur developpez.com.

Mocha: path to mocha.opts with -f

Mocha is a wonderful (:D) test framework which allows you to write really understandable tests in JavaScript. I’ve already introduced it in a previous article, so today I’ll focus on a pull request I’ve proposed.

Like other programs, you can pass arguments to mocha, like for instance –reporter used to specified a way to display test results. By the way, you should try once the nyan reporter. Concerning mocha, it’s also possible to set arguments in a file named « mocha.opts ». Interesting feature, but the file must absolutely be stored in your test directory.

So the idea is to add an argument: -f, –fileconf <path>. Furthermore, the –fileconf option allows you to specify the configuration file that will be used, by default “/test/mocha.opts”; allowing you to store your configuration file anywhere you wanted. This modification is very useful if you make tests using the same configuration file in many directories. Without –fileconf, you have to copy your file in each directory, and even a small modification must be done on each file. Using -f, you store the mocha.opts file in a unique place, so that any modifications of the file would immediatly impact all tests.

About the code

Add the parameter with commander:

.option('-f,--config <path>','specify the path to configuration file')

Load the file using commander to parse the command line:

//-f, --config
program.parse(process.argv);
var pathConf = 'test/mocha.opts';
if (program.config && exists(program.config)) {
  pathConf = program.config;
}
try {
  var opts = fs.readFileSync(pathConf, 'utf8')
    .trim()
    .split(/\s+/);

  process.argv = process.argv
    .slice(0, 2)
    .concat(opts.concat(process.argv.slice(2)));
} catch (err) {
  // ignore
}

In fact, this code is not available in mocha yet since « parsing twice might have some strange side-effects »…

An other way to augment argv without using commander:

//-f, --config

var pathConf = 'test/mocha.opts'
  , fIndex = process.argv.indexOf('-f')
  , configIndex = process.argv.indexOf('--config');

if(fIndex !== -1 && exists(process.argv[fIndex + 1]) ) {
  pathConf = process.argv[fIndex + 1];
}

if(configIndex !== -1 && exists(process.argv[configIndex + 1]) ) {
  pathConf = process.argv[configIndex + 1];
}

And I’ll finished this article by quoting visionmedia:

Maybe it’s ok but that’s still pretty hacky.

Node-Elastical: Delete by query option

What is Elastical?

Elastical is a Node.js client library for the ElasticSearch REST API.

That’s it for the presentation.

Until a few weeks ago, it wasn’t possible to delete data by query without using a hack. The hack was the following:

client.delete('twitter', 'tweet', '', {q: 'user:Shay'} );

Here, we set id to an empty string and we use the options parameter to perform a search on user with value Shay. Not so practical at all.

Using curl to interact with ElasticSearch, it is possible to delete by query:

curl -XDELETE 'http://localhost:9200/twitter/tweet/_query' -d ' 
  { "term" : { "user" : "kimchy" } 
}
'

This handy way of deleting things in ElasticSearch was not possible with Elastical (unless, you managed to use the hack above).

So I’ve added a new parameter options.query which allow us to perform such a query more easily.

client.delete('twitter', 'tweet', '', {query:
  { "term" : { "user" : "kimchy" } } 
});

When using the query parameter, id and all other options except ignoreMissing will be ignored.

Let’s take a quick look at the code involved:

if(params.query) {
  url = '/' + encode(this.name) + '/' + encode(type) + '/_query';

  this.client._request(url, {method: 'DELETE', json: params.query}
  , function (err, res) {
    if (err) {
      if (ignoreMissing && res && res.found === false) {
        return callback(null, res), undefined;
      } else {
        return callback(err, res), undefined;
      }
    }
    callback(null, res);
  });
} else {

It’s pretty simple. First, we need to build the url, then we’re requesting ElasticSearch on the given url, passing the query in the json parameter. If options.query doesn’t exist, then the original code is used.

As a conclusion, delete by query is now available in Elastical and you should use it!

 

ElasticSearch: Delete By Query API

[NodeJs] Spawn ou la création de processus fils

Un petit mot sur la façon de créer des processus fils sous node.
Pas compliqué et bien pratique, pour réaliser le cas de test d’une nouvelle fonctionnalité de npm par exemple.

Regardons le prototype:
child_process.spawn(command, [args], [options])
command correspond au programme appelé, ls, grep ou autre…
[args] est un tableau contenant tous les arguments qui seront passés au programme appelé via command.
[options] Plusieurs possibilités, dont le fait de pouvoir définir des règles pour le comportement de stdin, stdout et stderr.

Exemple:

//Set the no-proxy configuration
var noProxy = spawn(node, [npm, 'config', 'set', 'noproxy=localhost']
, { stdio: 'ignore' });

Ici, on crée un processus fils node avec pour paramètres: npm config noproxy=localhost.
On indique également que les entrées et sorties seront ignorées: { stdio: ‘ignore’ }.

Il est bien sûr intéressant de pouvoir effectuer une action particulière dès que notre fils se termine. Ceci est possible grâce à .on(‘exit’, function() { //CODE } ).
Soit avec l’exemple précédent:

noProxy.on('exit', function (code) {
console.log('noProxy process exited with exit code '+code);
});

Concernant les entrées/sorties, il est possible de laisser le processus écrire dans celles du processus parent en spécifiant cette fois: { stdio: ‘inherit’ }.
Un autre point utile consiste à récupérer le contenu de la sortie standard du processus fils.
Pour ce faire, on utilise les options suivantes: [‘ignore’, ‘stream’, ‘ignore’].
stdin et stderr sont ignorés, seule reste stdout.
On peut donc récupérer les données via .stdout.on(‘data’, function (data) { //CODE } ) , où data contient les données.
Pour reprendre l’exemple de npm, on peut ainsi récupérer la valeur de la variable de configuration proxy et la stocker dans une variable locale proxySave

var getProxy = spawn(node, [npm, 'config', 'get', 'proxy']
, ['ignore', 'stream', 'ignore']);
getProxy.stdout.on('data', function (data) {
proxySave = data.toString().trim();
});

Voilà pour les principales infos concernant spawn. Pour en savoir plus, direction la doc officielle ;).

[Conky] Script de démarrage

Conky est maintenant configuré… Prochaine étape, le démarrage automatique à l’ouverture de la session. On se place donc dans notre home où on va créer un dossier .conky pour y placer le script: mkdir .conky. On va ensuite écrire le script dans un fichier que l’on nommera .conkyboot.sh : vim .conkyboot.sh; utilisez ici l’éditeur avec lequel vous êtes à l’aise, nano, vi, vim ou autres.

Le script est très simple et tient en trois lignes:

#!/bin/bash
sleep 20;
conky -d;

Il faut ensuite rendre le fichier exécutable: chmod +x .conkyboot.sh.
Dernière étape, programmer l’exécution du script à chaque démarrage de la session. Pour cela, onglet Système – Préférences – Applications au démarrage. Puis Ajouter, et compléter le champ avec /home/user/.conky/.conkyboot.sh en remplaçant user par votre nom d’utilisateur. Remplissez le champ Nom avec Conky par exemple.

Et voilà, votre conky devrait désormais s’afficher automatiquement 20 secondes après l’ouverture de votre session.