如何简化大条件

时间:2018-09-12 07:31:49

标签: ruby-on-rails ruby

我有五个下拉菜单,我需要为每个下拉列表设置条件。我的代码是:

   def search(search, compare, year, rain_fall_type)
    if search == 'All'
      if rain_fall_type == 'All'
        all
      else
        if year == 'All'
          if rain_fall_type == "None"
            where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
          else
            # all
             where(Sector: rain_fall_type).order('id')
          end
        else
          if rain_fall_type == "All"
            order("#{year} ")

          elsif rain_fall_type == "None"
            where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
           else
            where(Sector: rain_fall_type).order("#{year} ")
           end
        end
        # where(Year: year).order("#{rain_fall_type} ")
      end
    elsif compare != "None"
      if year == 'All'
        where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
      else
        where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
      end
    else

      if rain_fall_type == 'All'
        all.order('id')
      else
        if year == 'All'

          if rain_fall_type == "None"
            where('Sector = ? ', search).order('id')
          else

            where('Sector = ? ', rain_fall_type).order('id')
          end
        else

          if rain_fall_type == "None"

            if search == "All"
              where('Sector = ? ', search).order('id')
            else
              where('Sector = ? ', search).order('id')
            end
          else
            # all
            where('Sector = ? ', rain_fall_type).order('id')
          end
        end
      end
    end
  end

它有许多ifelse。我正在努力使条件最小化。缩减此代码的最佳方法是什么?有人建议我改用switch case。我应该使用它吗?如果可以,怎么办?

3 个答案:

答案 0 :(得分:2)

您可以使用基本为return something if some_condition?的保护声明。这仅在特定情况下(条件之一是执行单个语句)才可行:

错误的例子:

if condition?
  do_something
else
  do_something_else
end

这可以写为:

return do_something if condition?
do_something_else

这将减少代码分支。


另外,另一建议是调用具有更多条件的另一种方法,而不是在单个镜头中嵌套条件。

错误的例子:

if condition?
  if condition_two?
    do_something_two
  else
    do_something
  end
else
  do_something_else
end

这可以写为:

if condition?
  call_another_method 
else
  do_something_else
end

def call_another_method
  if condition_two?
    do_something_two
  else
    do_something
  end
end

您的代码中的示例可能是:

if rain_fall_type == 'All'
  all
else
  if year == 'All'
    if rain_fall_type == "None"
      where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
    else
      # all
        where(Sector: rain_fall_type).order('id')
    end
  else
    if rain_fall_type == "All"
      order("#{year} ")

    elsif rain_fall_type == "None"
      where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
      else
      where(Sector: rain_fall_type).order("#{year} ")
      end
  end
end

可以将其转换为:

return all if rain_fall_type == 'All'
if year == 'All'
  return where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id') if rain_fall_type == "None"
  where(Sector: rain_fall_type).order('id')
else
  return order("#{year} ") if rain_fall_type == "All"
  return where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id') if rain_fall_type == "None"
  where(Sector: rain_fall_type).order("#{year} ")
end

我希望这会有所帮助:)

注意::这是为了回答How to simplify big conditions?的原始问题。但是最初的帖子并没有遵循Rails / Ruby的方式进行搜索和过滤,也没有充分利用范围。

答案 1 :(得分:2)

这可能是最好的设置说明。

class Product < ActiveRecord::Base
  # custom_scope_1
  scope :status, -> (status) { where status: status }
  # custom_scope_2
  scope :location, -> (location_id) { where location_id: location_id }
  # custom_scope_3
  scope :search, -> (name) { where("name like ?", "#{name}%")}
end

def index
   @products = Product.where(nil) # creates an anonymous scope
   @products = @products.status(params[:status]) if params[:status].present?
   @products = @products.location(params[:location]) if params[:location].present?
   @products = @products.search(params[:search]) if params[:search].present?
end

这可以通过...进一步清理。

def index
  @products = Product.where(nil)
  filtering_params(params).each do |key, value|
    @products = @products.public_send(key, value) if value.present?
  end
end

private

# A list of the param names that can be used for filtering the Products
def filtering_params(params)
  params.slice(:status, :location, :search)
end

此方法使用ruby元编程来遍历参数并在模型上动态调用预定义的scopes

您可以将此代码移动到模块中,并将其包含在任何支持过滤的模型中

app/models/concerns/filterable.rb

module Filterable
  extend ActiveSupport::Concern

  module ClassMethods
    def filter(filtering_params)
      results = self.where(nil)
      filtering_params.each do |key, value|
        results = results.public_send(key, value) if value.present?
      end
      results
    end
  end
end

app/models/product.rb

class Product
  include Filterable
  ...
end

app/controllers/product_controller.rb

def index
  @products = Product.filter(params.slice(:status, :location, :search))
end

您现在可以使用控制器中的一行和模型中的一行来过滤和搜索模型

答案 2 :(得分:1)

首先,您的某些逻辑没有意义:

def search(search, compare, year, rain_fall_type)
  if search == 'All'
    if rain_fall_type == 'All'
      all
    else
      # rain_fall_type != 'All'
      if year == 'All'
        if rain_fall_type == "None"
          where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
        else
          where(Sector: rain_fall_type).order('id')
        end
      else
        # in rain_fall_type != 'All' branch, so meaningless 'if'
        if rain_fall_type == "All"
          order("#{year} ")
        elsif rain_fall_type == "None"
          where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
        else
          where(Sector: rain_fall_type).order("#{year} ")
        end
      end
    end
  elsif compare != "None"
    # both are same, so meaningless 'if'
    if year == 'All'
      where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
    else
      where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
    end
  else
    # search != 'All'
    if rain_fall_type == 'All'
      all.order('id')
    else
      if year == 'All'
        if rain_fall_type == "None"
          where('Sector = ? ', search).order('id')
        else
          where('Sector = ? ', rain_fall_type).order('id')
        end
      else
        if rain_fall_type == "None"
          # in search != 'All' branch, so meaningless 'if'
          # AND both are same, so again meaningless 'if'
          if search == "All"
            where('Sector = ? ', search).order('id')
          else
            where('Sector = ? ', search).order('id')
          end
        else
          where('Sector = ? ', rain_fall_type).order('id')
        end
      end
    end
  end
end

还有更多类似的东西,我不会指出全部,因为无论如何我们都会扔掉所有if的东西。

最终,我们将查询推迟到方法的结尾,如下所示:

def search(search, compare, year, rain_fall_type)

  ...

  @query = all 
  @query = @query.where(Sector: @sectors) if @sectors
  @query = @query.order(@order) if @order
  @query

end

这样,您将whereorder语句的 all 全部取出,最后只做一次。这样可以节省大量的输入。请参阅muistooshort的评论,以了解(Sector: @sectors)为何起作用。

因此,诀窍在于设置@sectors@order。首先,我将输入变量分配给实例变量,因为我喜欢这样(并避免变量@search和方法search之间的混淆):

def search(search, compare, year, rain_fall_type)
  @search, @compare, @year, @rain_fall_type = search, compare, year, rain_fall_type

  ...

  @query = all 
  @query = @query.where(Sector: @sectors) if @sectors
  @query = @query.order(@order) if @order
  @query
end

现在,这个答案已经进行了太长时间了,因此我不会在所有gorey细节中拖拉您。但是,添加几个辅助方法(sectors_to_useorder_to_use)并用它们代替@sectors@order,基本上可以得出以下结论:

def search(search, compare, year, rain_fall_type)
  @search, @compare, @year, @rain_fall_type = search, compare, year, rain_fall_type
  @query = all 
  @query = @query.where(Sector: sectors_to_use) if sectors_to_use
  @query = @query.order(order_to_use) if order_to_use
  @query
end

private 

def sectors_to_use
  return [@rain_fall_type, @compare] if @search != 'All' && @compare != 'None'
  unless @rain_fall_type == 'All'
    if @rain_fall_type == 'None'
      @search == 'All' ? ['Primary', 'Secondary', 'Tertiary'] : [@search]
    else
      [@rain_fall_type]
    end  
  end
end

def order_to_use
  return nil if (@search == 'All') && (@rain_fall_type == 'All')
  return @year if (@search == 'All') && !(@year == 'All')
  return :id
end

不到代码行的一半,字符减少了上千个,ifs也减少了很多。