Ruby on Rails - 用于排序的用户输入

时间:2018-06-12 18:20:31

标签: ruby-on-rails ruby

为了学习Ruby on Rails,我正在编写一个Web应用程序,用于根据迄今为止的表现对锦标赛中的团队进行排序。

复杂的是,我希望每个锦标赛组织者(系统用户)能够以任意顺序使用各种指标。

表示为SQL(我的背景)我希望用户1能够选择:

ORDER BY
   METRIC1
  ,METRIC2
  ,METRIC3

虽然用户2可以选择:

ORDER BY
   METRIC2
  ,METRIC3
  ,METRIC1

我如何接受此用户输入并使用它在Team表上创建查询?

编辑1 忽略提及(抱歉)指标本身是即时计算的。目前它们是实例方法(例如@team.metric1等)。到目前为止,我所做的失败尝试都涉及尝试将用户字符串转换为看似错误的方法名称(而且我还没有能够让它工作)。

编辑2 teams_controller.rb中的一些示例代码:

class Team < ApplicationRecord
  belongs_to :tournament
  has_many :matches

  def score_for
    matches.sum(:score_for)
  end
  def score_diff
    matches.sum(:score_for) - matches.sum(:score_against)
  end
end

2 个答案:

答案 0 :(得分:0)

ActiveRecord允许将多个参数传递给order方法。所以你可以这样做:

Team.order(:metric2, :metric3, metric1: :desc)

另一个选项是您也可以使用ActiveRecord动态构建查询。 ActiveRecord查询被懒惰地评估,因此在您调用需要加载记录的操作之前,SQL不会被执行。

例如,你可以像这样在Team上构建一个范围:

class Team < ApplicationRecord

  scope :custom_order, lambda { |sorting_order|
    sorting_order.each do |metric|
      order(metric)
    end
  }
end

然后,您只需要按照您希望执行的order by子句的顺序输入属性集合。例如:

Team.custom_order([:metric2, :metric3, :metric1])

答案 1 :(得分:0)

一个可行但可能很糟糕的解决方案:

class Tournament < ApplicationRecord

  has_many :teams

  serialize :tiebreaker, Array

  TIEBREAKER_WHITELIST = %w[score opponent_score possession].freeze

  def sorted_teams
    list = teams.shuffle
    (TIEBREAKER_WHITELIST & tiebreaker).reverse.each do |metric|
      list = list.sort_by { |team| [team.send(metric), list.find_index(team)] }
    end
    list.reverse
  end
end

每个tournament有很多teamstournament实例具有一个称为tiebreaker的序列化字段。它包含一个类似于["score", "possession"]的字符串数组,其中每个字符串都与team上的公共实例方法的名称匹配。每个方法都返回一个数字。

tiebreaker字段的优先级从高到低,因此对于上面的示例,我只希望possession影响具有相等score的团队的排序。

list = teams.shuffle -如果团队与以下所有决胜局并列,则此列表会随机列出。

(TIEBREAKER_WHITELIST & tiebreaker) -仅返回同时出现在tiebreaker字段和白名单常量中的字符串,以防止最终用户运行任意方法。

.reverse.each do |metric| -这会颠倒指标数组,以便该列表首先按照最低优先级指标进行排序。

[team.send(metric), list.find_index(team)] -这是每个指标的排序方式。 send将字符串转换为方法调用。我发现find_index对于保留先前排序的排序顺序很有必要。也就是说,如果我第一次为possession排序,这将保留具有相同score的团队的订单。

list.reverse -反转列表然后返回。这是因为我想让得分更高/拥有更多人的团队首先出现在我的名单上,并且sort_by排名上升。

我希望一些度量按升序(opponent_score)排序,而其他度量按降序(score排序,因此我在相应的方法中进行了处理,例如返回opponent_score的负值。

我对解决方案并不完全满意,但是它确实可以正常工作!