views:

86

answers:

3

Hello,

I have a an method that retrieves Groups that are present in certain areas. Groups are given a country_id, region_id and city_id

The UI gives three select boxes to choose a country, a region from that country and then a city from that region. To find all groups in a particular city, I have this code:

@groups = Group.find(:all, :conditions => {:city_id => params[:city_id]})

This all works fine, but I also want it to find all groups in an area when the lower criteria isn't specified. For example, If a country and region are given, but not city, I'd like to find it by the region.

What I'm doing is this:

if !params[:city_id].nil?
    @groups = Group.find(:all, :conditions => {:city_id => params[:city_id]})
else
    if !params[:region_id].nil?
         @groups = Group.find(:all, :conditions => {:region_id => params[:region_id]})
    else
         @groups = Group.find(:all, :conditions => {:country_id => params[:country_id]})
    end
end

This works perfectly well, but it seems like it's a little inefficient. Am I doing it the best way or can I streamline a little?

One idea I had was to have a single find checking against all parameters, but I could not work out how to effectively 'ignore' parameters that were nil - my main thought was to check which ones were not set and set them to something like '*' or 'true', but that's not how SQL plays the game.

+1  A: 

If every value in params is a candidate for :conditions you can just do this:

@groups = Group.all(:conditions => params.reject { |idx, val| val.nil? })

This just throws out nil values from params and uses the remaining values for conditions.

If you don't want to use all of the values in params, you have two options. You can just get rid of a bunch of redundancy in your original code:

conditions =  if !params[:city_id].nil?
                { :city_id    => params[:city_id] }
              elsif !params[:region_id].nil?
                { :region_id  => params[:region_id] }
              else
                { :country_id => params[:country_id] }
              end

@groups = Group.all(:conditions => conditions)

You can knock of a few more lines like this, but it sacrifices a bit of readability IMO:

conditions =  if    !params[:city_id].nil?   then { :city_id => params[:city_id] }
              elsif !params[:region_id].nil? then { :region_id => params[:region_id] }
              else { :country_id => params[:country_id] }
              end

Or you can do something like this:

conditions = [:city_id, :region_id, :country_id].inject({}) do |hsh, sym|
  hsh[sym] = params[sym] unless params[sym].nil?
  hsh
end

@groups = Group.all(:conditions => conditions)

This has the advantage that you don't need to add another condition for each symbol.

Jordan
Thank you for taking the time to provide the insightful answer. I haven't used your suggestion in this case, but I can see your suggestion for .inject() being useful to me elsewhere in my project. Many thanks!
SaoiseK
+1  A: 

Sounds like a job for named scopes:

class Group < ActiveRecord::Base
  named_scope :in_city, lambda { |city_id| {
    :conditions => { :city_id => city_id }
  }}

  named_scope :in_region, lambda { |region_id | {
    :conditions => { :region_id => region_id }
  }}

  named_scope :in_country, lambda { |country_id | {
    :conditions => { :country_id => country_id }
  }}
end

This establishes some simple scopes for restricting the Group records. Presumably you have indexed your database properly so these are quick to resolve.

The controller is much easier to implement then:

def index
  @group_scope = Group

  if (!params[:city_id].blank?)
    @group_scope = @group_scope.in_city(params[:city_id])
  elsif (!params[:region_id].blank?)
    @group_scope = @group_scope.in_region(params[:region_id])
  elsif (!params[:country_id].blank?)
    @group_scope = @group_scope.in_country(params[:country_id])
  end

  @groups = @group_scope.all
end

Generally you should be testing for .blank? instead of .nil? as some form elements can send in empty results, such as a select with something akin to "All" as the default.

tadman
I hadn't come across named_scopes in my short time with rails - these fit the bill perfectly, in this problem and in other areas. Many thanks.
SaoiseK
+1  A: 

You could use some Ruby idioms to get something a little more succinct.

Try something like this: (untested code!)

def index
   @groups = Group.find :all, :conditions => [:city_id, :region_id, :country_id].inject {} do |conditions, name|
      conditions[name] = params[name]  unless params[name].blank?
      conditions
    end
end
Ben
Thank you for taking the time to answer my question. I hadn't come across the .inject() before and I can see them being useful to me in the future. Many thanks!
SaoiseK