Rafał Komorowski
2005-Oct-31 10:43 UTC
How to programatically build :contitions with elegance?
Hello Railers, I wonder how to avoid a matrix of 3x3=9 IF''s when constructing a :conditions string for find query, when I have three independent sets of optional conditions. Details: my app displays a task list. A task is associated with a project, a person and it also has a creation date. My Task-view has a set of popup-menus that trigger 3 display options: - display tasks from projects: all, project-1, project-2, etc... - display tasks for persons: all, person-1, person-2, etc... - display tasks for month: all, January, February, etc... Those popups are grouped in a form, that sends a params [:display_options] hash to TaskController. It works OK, the controller builds a DisplayOptions object from this Hash (to populate popups with the ''collection_select'' helper). Now I try to implement a query that meets the conditions set by the DisplayOptions object. If I had only one popup, this would be simple: if (params[:display_options] != nil) # we''ve got options @display_options = TaskHelper::DisplayOptions.new(params [:display_options]) # build object for view @tasks = Task.find( :all, :conditions => [ "project = ?", @display_options["project"] ] ) ... But I have 3 independent popups, so a query conditions can look like this: [ "project = ? and person_id = ?", ..... ] or like this: [ "person_id = ? and month = ?", .... ] or any other from 3x3=9 variants, depending what options are set in my popups. For example if ''Projects'' popup is set to ''All'' then I don''t need to include it in the query, since tasks from any project should be found. Is there some more elegant way of building such a query? Some neat SQL statement or some neat form of ''find'' method with incremental conditions builder? My knowledge of SQL is only rudimentary (thanks to Rails!), but I''ve checked, that the following syntax doesn''t work (at least for MySQL): SELECT * FROM tasks WHERE project_id=* AND person_id=3 ... so I can''t substitute ''*'' if popup is set to "All". Any ideas? TIA -- Rafał Komorowski komor-ee4meeAH724@public.gmane.org GG: 4083718 <http://homepage.mac.com/komor/iblog/>
Rafał Komorowski
2005-Oct-31 11:19 UTC
Re: How to programatically build :contitions with elegance?
A quick addendum. Of course a matrix of 3 popups gives 2^3=8 variations, not 3x3=9, sorry for lame math. Anyway, if I add a forth popup, then it goes crazy to 2^4=16 variations. If my previous post was too complicated, here''s a short version: how to get rid of this code: def generate_find_conditions case when @project==0 and @month==0 and @user!=0 ["user = ?", @user] when @project==0 and @month!=0 and @user==0 ["month = ?", @month] when @project==0 and @month!=0 and @user!=0 ["month = ? and user = ?", @month, @user] when @project!=0 and @month==0 and @user==0 ["project = ?", @project] when @project!=0 and @month==0 and @user!=0 ["project = ? and user = ?", @project, @user] when @project!=0 and @month!=0 and @user==0 ["project = ? and month = ?", @project, @month] when @project!=0 and @month!=0 and @user!=0 ["project = ? and month = ? and user = ?", @project, @month, @user] end end ..... Task.find( :all, :conditions => @display_options. generate_find_conditions ) -- Rafał Komorowski komor-ee4meeAH724@public.gmane.org GG: 4083718 <http://homepage.mac.com/komor/iblog/>
Alex Young
2005-Oct-31 11:35 UTC
Re: How to programatically build :contitions with elegance?
Ugly and untested: def generate_find_conditions pairs = [["user", @user], ["month", @month], ["project", @project]] conds = pairs.collect do |pair| pair[1]!=0 ? ["#{pair[0]} = ?", pair[1]] : nil end.compact cond_str = conds.collect{|c| c[0]}.join(" and ") cond_objs = conds.collect{|c| c[1]} return [cond_str, cond_objs] end -- Alex Rafał Komorowski wrote:> A quick addendum. Of course a matrix of 3 popups gives 2^3=8 > variations, not 3x3=9, sorry for lame math. Anyway, if I add a forth > popup, then it goes crazy to 2^4=16 variations. > > If my previous post was too complicated, here''s a short version: how to > get rid of this code: > > def generate_find_conditions > case > when @project==0 and @month==0 and @user!=0 > ["user = ?", @user] > when @project==0 and @month!=0 and @user==0 > ["month = ?", @month] > when @project==0 and @month!=0 and @user!=0 > ["month = ? and user = ?", @month, @user] > when @project!=0 and @month==0 and @user==0 > ["project = ?", @project] > when @project!=0 and @month==0 and @user!=0 > ["project = ? and user = ?", @project, @user] > when @project!=0 and @month!=0 and @user==0 > ["project = ? and month = ?", @project, @month] > when @project!=0 and @month!=0 and @user!=0 > ["project = ? and month = ? and user = ?", @project, @month, > @user] > end > end > ..... > Task.find( :all, :conditions => @display_options. > generate_find_conditions ) > >
Martin DeMello
2005-Oct-31 13:41 UTC
Re: How to programatically build :contitions with elegance?
On 10/31/05, Rafał Komorowski <komor@mac.com> wrote:> A quick addendum. Of course a matrix of 3 popups gives 2^3=8 > variations, not 3x3=9, sorry for lame math. Anyway, if I add a forth > popup, then it goes crazy to 2^4=16 variations. > > If my previous post was too complicated, here's a short version: how > to get rid of this code: > > def generate_find_conditions > case > when @project==0 and @month==0 and @user!=0 > ["user = ?", @user] > when @project==0 and @month!=0 and @user==0 > ["month = ?", @month] > when @project==0 and @month!=0 and @user!=0 > ["month = ? and user = ?", @month, @user] > when @project!=0 and @month==0 and @user==0 > ["project = ?", @project] > when @project!=0 and @month==0 and @user!=0 > ["project = ? and user = ?", @project, @user] > when @project!=0 and @month!=0 and @user==0 > ["project = ? and month = ?", @project, @month] > when @project!=0 and @month!=0 and @user!=0 > ["project = ? and month = ? and user = ?", @project, > @month, @user] > end > endclass A def initialize(p,m,u) @project = p @month = m @user = u end def test q = [] ary = [] [:project, :month, :user].each {|i| iv = instance_variable_get("@#{i}") unless iv.zero? q << "#{i} = ?" ary << iv end } return [q.join(" and ")].concat(ary) end end a = A.new(1,2,3) b = A.new(0,1,2) c = A.new(1,0,2) d = A.new(0,0,1) p a.test p b.test p c.test p d.test martin _______________________________________________ Rails mailing list Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails
Francois Beausoleil
2005-Oct-31 14:54 UTC
Re: How to programatically build :contitions with elegance?
Hi ! 2005/10/31, Rafał Komorowski <komor@mac.com>:> I wonder how to avoid a matrix of 3x3=9 IF's when constructing > a :conditions string for find query, when I have three independent > sets of optional conditions.<snip>> Any ideas? TIAYes, as a matter of fact, I do. See this article on my blog: Building the SQL WHERE clause dynamically in Rails [1] http://rubyurl.com/tGQ The meat of the article is this: class SearchController < ApplicationController def search conditions = ['1=1'] conditions << 'cond1 = :cond1' if params[:cond1] conditions << 'cond2 = :cond2' if params[:cond2] @results = Model.find(:all, :conditions => [conditions.join(' AND '), params]) end end Hope that helps ! François [1] http://blog.teksol.info/articles/2005/10/31/building-the-sql-where-clause-dynamically-in-rails _______________________________________________ Rails mailing list Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails
Mike Wernsing
2006-Jan-12 18:36 UTC
[Rails] How to programatically build :contitions with elegance?
I am interested in using the method described below to dynamically build :conditions. As far as I can tell this would leave the door open for sql injection attacks. Is this so? If so, does anyone have any alternative ideas? On 10/31/05, Francois Beausoleil <francois.beausoleil@gmail.com> wrote:> Building the SQL WHERE clause dynamically in Rails [1] > http://rubyurl.com/tGQ > > The meat of the article is this: > class SearchController < ApplicationController > def search > conditions = [''1=1''] > > conditions << ''cond1 = :cond1'' if params[:cond1] > conditions << ''cond2 = :cond2'' if params[:cond2] > > @results = Model.find(:all, > :conditions => [conditions.join('' AND ''), params]) > end > end > [1] http://blog.teksol.info/articles/2005/10/31/building-the-sql-where-clause-dynamically-in-rails
Mike Wernsing
2006-Jan-12 18:49 UTC
[Rails] How to programatically build :contitions with elegance?
Sorry for the hasty post, the following would take care of the sql injection problems. :conditions => [conditions.join('' AND ''), params] duh... On 1/12/06, Mike Wernsing <mwernsing@gmail.com> wrote:> I am interested in using the method described below to dynamically > build :conditions. As far as I can tell this would leave the door open > for sql injection attacks. Is this so? If so, does anyone have any > alternative ideas? > > On 10/31/05, Francois Beausoleil <francois.beausoleil@gmail.com> wrote: > > Building the SQL WHERE clause dynamically in Rails [1] > > http://rubyurl.com/tGQ > > > > The meat of the article is this: > > class SearchController < ApplicationController > > def search > > conditions = [''1=1''] > > > > conditions << ''cond1 = :cond1'' if params[:cond1] > > conditions << ''cond2 = :cond2'' if params[:cond2] > > > > @results = Model.find(:all, > > :conditions => [conditions.join('' AND ''), params]) > > end > > end > > [1] http://blog.teksol.info/articles/2005/10/31/building-the-sql-where-clause-dynamically-in-rails >