如何在Rails模型中重构复杂的搜索逻辑

时间:2012-11-01 13:51:37

标签: ruby-on-rails ruby refactoring dry

我的搜索方法很臭,很臃肿,我需要一些帮助来重构它。我是Ruby的新手,我还没有想出如何有效地利用它,这会导致像这样膨胀的方法:

  # discussion.rb
  def self.search(params)
    # If there is a search query, use Tire gem for fulltext search
    if params[:query].present?
      tire.search(load: true) do
        query { string params[:query] }
      end

    # Otherwise grab all discussions based on category and/or filter
    else

      # Grab all discussions and include the author
      discussions = self.includes(:author)

      # Filter by category if there is one specified
      discussions = discussions.where(category: params[:category]) if params[:category]

      # If params[:filter] is provided, user it
      if params[:filter]
        case params[:filter]
        when 'hot'
          discussions = discussions.open.order_by_hot
        when 'new'
          discussions = discussions.open.order_by_new
        when 'top'
          discussions = discussions.open.order_by_top
        else
          # If params[:filter] does not match the above three states, it's probably a status
          discussions = discussions.order_by_new.where(status: params[:filter])
        end
      else

        # If no filter is passed, just grab discussions by hot
        discussions = discussions.open.order_by_hot
      end
    end
  end

  STATUSES   = {
    question:   %w[answered],
    suggestion: %w[started completed declined],
    problem:    %w[solved]
  }

  scope :order_by_hot,  order('...') DESC, created_at DESC")
  scope :order_by_new,  order('created_at DESC')
  scope :order_by_top,  order('votes_count DESC, created_at DESC')

这是一个可以按类别过滤(或不过滤)的讨论模型:questionproblemsuggestion

所有讨论或单个类别都可以通过hotnewvotesstatus进一步过滤。状态是模型中的哈希值,它具有多个值,具体取决于类别(仅当存在params [:category]时才会显示状态过滤器。)

使问题复杂化是使用轮胎的全文搜索功能

但是我的控制器看起来很整洁:

  def index
    @discussions = Discussion.search(params)
  end

我可以干这个/重构一下,也许使用元编程或块?我设法将其从控制器中提取出来,但随后却没有想法。我不太了解Ruby,以便进一步发展。

1 个答案:

答案 0 :(得分:3)

首先,“根据类别和/或过滤器抓取所有讨论”可以是一种单独的方法。

params[:filter]重复多次,所以请在顶部取出:

filter = params[:filter]

您可以使用

if [:hot, :new, :top].incude? filter
  discussions = discussions.open.send "order_by_#{filter}"
...

另外,如果是else else语句,则将其除外。我更喜欢分成单独的方法并提前返回:

def do_something
  return 'foo' if ...
  return 'bar' if ...
  'baz'
end

discussions = discussions...多次出现,但看起来很奇怪。您可以使用return discussions...吗?

为什么常量STATUSES出现在最后?通常常量出现在模型的顶部。

请务必在重构之前编写所有测试。

回复有关return 'foo' if ...的评论:

考虑:

def evaluate_something
  if a==1
    return 'foo'
  elsif b==2
    return 'bar'
  else
    return 'baz'
  end
end

我建议将其重构为:

def evaluate_something
  return 'foo' if a==1
  return 'bar' if b==2
  'baz'
end

也许你可以重构一些if..then..else..if语句。

推荐书:Clean Code