Question

I'm attempting to build a set of conditions dynamically using an array as suggested in the first answer here: One or more params in model find conditions with Ruby on Rails. However I seem to be doing something incorrectly and I'm not sure if what I'm trying is fundamentally unsound or if I'm simply botching my syntax.

I'm simplifying down to a single condition here to try to illustrate the issue as I've tried to built a simple Proof of concept along these lines before layering on the 5 different condition styles I'm contending with.

This works:

excluded.push 12
excluded.push 30
@allsites = Site.all(:conditions => ["id not in (?)", excluded])

This results in a private method 'scan' called error:

conditionsSet = []
excluded.push 12
excluded.push 30
conditionsSet << ["id not in (?)", excluded]
@allsites = Site.all(:conditions => conditionsSet)

Thanks for any advice. I wasn't sure if the proper thing was to put this as a followup item to the related question/answers I noted at the top. Since I've got a problem not an answer. If there is a better way to post this related to the existing post please let me know.

Was it helpful?

Solution

Try this:

Rails 2.3

class Site < ActiveRecord::Base

  def self.build_conditions(ids, name=nil, state=nil)
     cond = []
     cond << send(:sanitize_sql_array, ["id NOT IN (?)", ids]) unless ids.empty?
     cond << send(:sanitize_sql_array, ["name = ? ", name]) unless name
     cond << send(:sanitize_sql_array, ["state = ? ", state]) unless state
     cond.join(" and ")
  end    
end

Now somewhere in your controller:

Site.all(:conditions => Site.build_conditions([1,2])) 
Site.all(:conditions => Site.build_conditions(nil, "ABC"))

Rails 3

class Site < ActiveRecord::Base          
  def self.exclude_ids_by_name_and_state(ids, name=nil, state=nil)
    result = scoped
    result = result.where("id NOT IN (?)", ids) if ids.present?
    result = result.where(:name => name) if name.present?
    result = result.where(:state => state) if state.present?
    result
  end    
end

Now somewhere in your controller:

Site.exclude_ids_by_name_and_state([1,2])).all 
Site.exclude_ids_by_name_and_state(nil, "ABC").all

OTHER TIPS

You want:

conditionsSet += ["id not in (?)", excluded]

instead of:

conditionsSet << ["id not in (?)", excluded]

+= adds the two arrays together (think of it as merging the two into one array) while << pushes a new element onto the array. So you are getting: [["id not in (?)", excluded]] when using <<, and :conditions wants an array where this first element is a string (not an array).

Try SmartTuple, it's designed specifically for cases like this.

def self.build_conditions(options = {})
  [
    SmartTuple.new(" AND "),
    (["id NOT IN (?)", ids] if options[:ids].present?),
    ({:name => options[:name]} if options[:name].present?),
    ({:state => options[:state]} if options[:state].present?),
  ].sum.compile
end

...

Site.all(:conditions => Site.build_conditions(:ids => {1,2]))
Site.all(:conditions => Site.build_conditions(:name => "abc", :state => "disabled")

To me it's also preferrable to use options hash instead of ordered arguments. As your project grows, more conditions may appear and you'll lose track of which comes which. Hash looks and behaves clearer plus you can easily validate it to avoid software errors.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top