Rails : Ré-ordonner des lignes ActiveRecord
Par Christophe, dimanche 4 mars 2007 à 01:37 :: Développement :: #127 :: rss
EDIT: Merci à Bounga de m'avoir montré "the right way"... ça m'apprendra à pas asser fouiller dans la doc...
En fait, ruby permet de réordonner les résultats comme il faut, de cette façon :
Image.find(:all, :order => 'title').sort_by(&:id)
Voilà, c'est tout... donc en fait le helper qui correspondrait pourrait être :
def reorder_results(result, by = :id) result.sort_by(&by) end
Fin. Le reste du billet sert juste d'explication au pourquoi de la méthode, le code même qui suit étant caduque.
Aujourd'hui, nous allons essayer de palier un "manque" de la classe Array, en rapport avec ActiveRecord (hein?)
Si, après être passé dans un filtre relativement compliqué, vous avez une liste de résultats activerecord, dans un array, à afficher, sachez que vous ne pourrez pas la ré-ordonner "comme ça".
En effet, en imaginant que nous utilisons une table "images", Image.find(:all).sort nous dit que les Image ne sont pas comparables (NoMethodError: undefined method `<=>' for #<Image:0x409c6bec>), ce qui est le cas des String, Integer et autres constantes "habituelles" de Ruby.
Donc sans hacker le coeur de ActiveRecord, on peut arriver au même résultat en utilisant ma méthode 'reorder_results' comme ceci :
a_classer = Image.find(:all) reclasses = reorder_results(a_classer, :title)
Je tiens à re-préciser que oui, montré comme cela, ça n'a strictement *aucunt interêt* (totally useless). Oui, on aurait pu faire Image.find(:all, :order => 'title desc') directement, mais :
Imaginez qu'a la place de "a_classer", vous ayez un array de Image, désordonnés, parce qu'issus de filtres de recherches "complexes" tels que "rechercher toutes les images dans toutes les sous-categories (<- récursif) de ImagesCategory.find(1337)".
Eh bien oui il faut réordonner tout cela, et c'est pour ça que reorder_results existe.
Cette méthode permet d'ordonner les résultats par n'importe quoi, leur identifiant par défaut, et permet de les ordonner dans le sens inverse.
Exemple :
reorder_results(a_classer, :title, true)
... ordonnera par titre et inversera l'ordre des résultats.
Le code commenté
Une fois n'est pas coutume, je vais décomposer et commenter mon code :
- Déclaration de la méthode, un argument obligatoire, deux facultatifs avec :id et false comme valeurs par défault
def reorder_results(result, by = :id, reverse = false)
- Pour chaque élément de result :
- On met la valeur du champ line.send(by) (Voir Object#send) par lequel on veut trier dans la variable line_by, sauf si cette valeur est nil (ce qui arrive uniquement quand le champ SQL est à NULL)
- Retourne la valeur de line_by
- Applique un Array#sort sur la liste de résultats retourné, ce qui classe tous les objets comparables : String, Integer, Float et Time sont les seuls suceptibles de contenir la valeur d'un champ SQL par ActiveRecord.
- Met le tout dans determinants
determinants = result.map {|line|
line_by = line.send(by).nil? ? '' : line.send(by)
line_by
}.sort
- Comme ça se lit : Inverse l'ordre de l'array determinants à moins que reverse soit égal à false
determinants.reverse! unless reverse == false
- Prends l'array des résultats passés
- Selectionne son premier élement
- Selectionne sa Classe
- Met cette dernière dans object
object = result.first.class
- Crée un array vide
ordered = []
- Pour chaque element de determinants :
- On passe à l'object (object) "l'argument" find_by_(la-variable-by) que l'on converti en Symbol à qui l'on envoie "l'argument" determinant
- Donc, si l'objet était un Image et by valait :age, avec determinant = 17, par exemple, ce se situe après "<<" serait équivalent à : Image.find_by_age(17)
- Avec '<<', l'on ajoute ce résultat (un Image donc) à l'array ordered
determinants.each do |determinant|
ordered << object.send("find_by_#{by}".to_sym, determinant)
end
- On renvoie la variable ordered
ordered
- Fin (ouf)
end
... ce qui donne si l'on reprends tout :
def reorder_results(result, by = :id, reverse = false)
determinants = result.map {|line|
line_by = line.send(by).nil? ? '' : line.send(by)
line_by
}.sort
determinants.reverse! unless reverse == false
object = result.first.class
ordered = []
determinants.each do |determinant|
ordered << object.send("find_by_#{by}".to_sym, determinant)
end
ordered
end
Améliorer la classe Array
Ah oui, c'est effectivement ce que j'ai dit au tout début de ce billet...
Je voulais dire par là que l'on pourrait surcharger la classe Array (ce qui n'a rien de tordu en soi) en lui rajoutant cette méthode (un peu modifiée pour que ça passe), ainsi, l'on pourrait faire quelquechose comme my_reordered_array = my_disordered_array_of_AR_results.reorder_results(:title).
Vite, du coca, je crève de soif.

Commentaires
1. Le lundi 5 mars 2007 à 16:07, par JP
2. Le lundi 5 mars 2007 à 16:19, par Christophe
3. Le mercredi 7 mars 2007 à 15:48, par Bounga
Ajouter un commentaire
Les commentaires pour ce billet sont fermés.