Requête sql dynamique

Requête sql dynamique - Ruby/Rails - Programmation

Marsh Posté le 08-11-2008 à 14:42:20    

Bonjour à tous,
 
J'aimerais réaliser un petit moteur de recherche pour mon site mais je fais fasse à un problème.
Pour ce moteur, plusieurs champs de recherche sont disponible. En fonction des champs remplit par l'utilisateur, ma requête évolue donc, et c'est la que le problème survient.
 
Je voulais utilisé la méthode classique:
 
User.find(:all, :conditions => ['name = ?', name])
 
Mais je ne vois pas comment fait évoluer cette méthode de manière dynamique afin de rentrer les conditions en fonction de mes champs.
Exemple:
Si le nom de l'utilisateur recherche et son sexe est remplit, je voudrais un truc du genre:
 
User.find(:all, :conditions => ['name = ? AND sexe = ?', name, sexe])
 
Mais comment faire cela dynamiquement ?
 
J'ai essayé avec sql_conditions, mais visiblement, ça ne marche pas très bien avec la méthode que j'ai utilisé:
 
    sql_conditions << [' AND sexe = ? ', @sexe] if !@sexe.nil?
    sql_conditions << [' AND name = ? ', @name]
 
Il me dit "can't convert Array into String".
 
Je veux utiliser les ? pour éviter les sql injections.
 
Quelqu'un peut il m'aider ?
 
Merci.

Reply

Marsh Posté le 08-11-2008 à 14:42:20   

Reply

Marsh Posté le 08-11-2008 à 19:23:36    

Salut,
 
Personnellement j'ai résolu ça de la manière suivante :  
 
 

Code :
  1. # Builds a list based on pagination and sort fields
  2.  def build_list model,conditions=nil,order=nil,items_per_page=nil
  3.    sort = order.downcase unless order.nil?
  4.    sort = (sort =~ /^.+_reverse$/) ? sort.gsub('_reverse','')+' DESC' : sort
  5.    
  6.    
  7.    unless conditions.nil? || conditions[:field].nil? || conditions[:query].nil?
  8.      search_fields = conditions[:field]
  9.      search_values = conditions[:query]
  10.      
  11.      # Check that all the search_fields are valid by comparing them with the attributs of the element
  12.      
  13.      invalid_field = false
  14.      search_fields.each do |field|
  15.        invalid_field = true  unless model.columns_name_hash.value? field.downcase
  16.      end
  17.      
  18.      # If one or more of the search fields don't match with the attributs, the method returns the "normal" list.
  19.      if invalid_field
  20.        conditions=nil
  21.      else
  22.        conditions = ["#{search_fields} LIKE ?", "%#{search_values}%"]
  23.      end
  24.    else
  25.      conditions=nil
  26.    end
  27.    
  28.    elements = nil;
  29.    total = nil;
  30.    
  31.    if items_per_page.nil?
  32.      elements = model.find(:all, :order=>sort,:conditions=>conditions)
  33.    else
  34.      total = model.count(:conditions => conditions)
  35.      elements = model.paginate :page => params[:page], :order => sort, :conditions => conditions, :per_page => items_per_page
  36.    end
  37.    
  38.    return total,elements
  39.  end


 
En gros, je garde la syntaxe avec le ? pour éviter les injections SQL. Mais afin d'éviter qu'on me joue des tours en détournant le nom des champs sur lesquels j'effectue mes recherches, je check qu'ils appartiennent aux champs définis dans le Model en question (lignes 15-18)


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Marsh Posté le 23-01-2009 à 15:58:41    

Quelqu'un a des bonnes sources et des tutoriaux sur la "reflection", parce qu'en fait avant je faisais comment vous, mais c'est super crado. J'arrive pas à invoquer des méthodes dynamiquement à la suite comme par exemple:
 
User.find_by_login(login).valid.author
 
Comment ils font dans Rails? C'est tout des named_scope?

Reply

Marsh Posté le 23-01-2009 à 16:46:29    

J'ai pas trop compris là question. Tu veux faire quoi exactement?


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Marsh Posté le 23-01-2009 à 18:48:17    

En fait générer dynamiquement une seule méthode et l'invoquer je sais faire:
 
Ce que j'arrive pas à faire c'est par exemple exécuter dynamiquement plusieurs méthodes les unes à la suite des autres:

Code :
  1. User.date(start_date, end_date).login(login)


ou

Code :
  1. User.date(start_date, end_date).login(login).group(group_id)


date, login et group sont des named_scope et je veux les invoquer de manière conditionelle si leur paramètre est passé. Faire une boucle avec la méthode send ne marche pas, car les méthodes sont exécutées indépendemment les unes des autres.


Message édité par igarimasho le 23-01-2009 à 18:50:43
Reply

Marsh Posté le 23-01-2009 à 20:03:37    

Si j'ai bien compris, ça devrait être dispo pour Rails 2.3 ou 3 : http://ryandaigle.com/articles/200 [...] pe-methods
 
Sinon si tu veux just dynamiquement générer des critères de recherche qui porte sur un seul model, tu peux faire comme ça:
Dans mon cas j'ai nécessairement une recherche par site

Code :
  1. def self.super_finder(site_id, search = {})
  2.   find_methods = "find_by_site_id"
  3.   arguments = [site_id]
  4.  
  5.   if search
  6.     find_methods << "_and_login" and arguments << search[:login] if !search[:login].empty?
  7.     find_methods << "_and_id" and arguments << search[:id] if !search[:id].empty?
  8.   end
  9.   send(find_methods.to_sym, *arguments)
  10. end


 
Dans mon controlleur, je l'appelle avec: User.super_finder(site_id, search). Donc si en paramètre de recherche je ne mets rien, ça me génère:

Code :
  1. User.find_by_site_id(site_id)


 
Si en paramètre je passe login et id, ça me génére dynamiquement:

Code :
  1. User.find_by_site_id_and_login_and_id(site_id, login, id)


C'est plus propre non?  :)
 
La limite, c'est qu'il faut que les critères de recherche porte sur le même Model. Donc par exemple:

Code :
  1. User.find_by_site_id_and_login


ça marche nickel. Mais par exemple si je dois ajouter un critère qui porte sur un champ d'une table jointe, ça ne marche plus, par exemple:

Code :
  1. User.find_by_site_and_book_title


Dans ce cas il faut utiliser un named_scope, malheureusement je ne sais pas comment faire par exemple un truc du genre:

Code :
  1. find_by_site_id(site_id).booktitle(booktitle)


 
EDIT: le module NamedScope possède une méthode scoped qui m'a l'air importante pour ce que je veux faire. D'ailleurs dans le patch Rails pour les named_scope dynamique c'est ce qui est utilisé, j'ai regardé le code source:  
http://api.rubyonrails.org/classes [...] Scope.html
http://github.com/rails/rails/comm [...] 287afe2b55


Message édité par igarimasho le 23-01-2009 à 20:27:27
Reply

Marsh Posté le 23-01-2009 à 21:23:14    

Sinon une manière fort élégante de faire cette fameuse recherche par critère et qui utilise scoped: http://railscasts.com/episodes/112-anonymous-scopes
 
Il est fort ce Ryan Bates, et j'aime bien regarder le code source de son site railscasts  :D
 
 
Sinon pour exécuter des méthodes à la chaîne il y a la méthode Kernel#eval qui exécute un String comme si c'était du code Ruby, à utiliser avec beaucoup de précautions donc.


Message édité par igarimasho le 23-01-2009 à 21:38:11
Reply

Marsh Posté le 23-01-2009 à 22:14:25    

D'accord :D
Pour le moment j'avais jamais eu besoin, mais c'est clair ça peut le faire :o
 
Par contre, je suis contre l'utilisation de Kernel#eval .. à mon avis ça doit être vraiment utilisé en dernier ressort ..


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed