我正在构建一个Rails(4.1.8)/ Ruby(2.1.5)应用程序。我有以下collection_select表单字段:
<%= f.collection_select :target_ids, Target.order(:outcome), :id, :outcome, { :prompt => "-- Select one to two --" }, { :multiple => true, :size => 6 } %>
这可以按预期工作,但我需要帮助排序下拉菜单中的项目顺序,其中包含带数字的字符串。我当前的代码按字母按升序对列表进行排序,但字符串中的数字不按顺序排列。这是一个片段,显示菜单现在的样子:
-- Select one to two --
Gain 10 pounds
Gain 15 pounds
Gain 1 pound
Lose 10 pounds
Lose 15 pounds
Lose 1 pound
Reduce body fat by 15%
Reduce body fat by 2%
以下是我要对菜单进行排序的方法:
-- Select one to two --
Gain 1 pound
Gain 10 pounds
Gain 15 pounds
Lose 1 pound
Lose 10 pounds
Lose 15 pounds
Reduce body fat by 2%
Reduce body fat by 15%
如何让字符串中的数字按我想要的顺序显示?是否有一种Ruby排序方法可以处理这种排除在外的方法,或者我是否需要编写自定义排序方法?我如何才能最好地达到我想要的结果?谢谢!
答案 0 :(得分:2)
使用Enumerable#sort_by
一个块,从每个outcome
中提取前导文本和数字(转换为Fixnums):
class Target < ActiveRecord::Base
OUTCOME_PARTS_EXPR = /(\D+)(\d+)/
# ...
def self.sort_by_outcome
all.sort_by do |target|
outcome = target.outcome
next [ outcome ] unless outcome =~ OUTCOME_PARTS_EXPR
[ $1, $2.to_i ]
end
end
end
然后,在你的控制器中:
@targets_sorted = Target.sort_by_outcome
最后,在你看来:
<%= f.collection_select :target_ids, @targets_sorted, :id, :outcome,
{ :prompt => "-- Select one to two --" }, { :multiple => true, :size => 6 } %>
一般来说,你应该尝试在数据库中进行所有排序,但由于我不知道你正在使用什么数据库,我将限制我在Ruby中如何做到这一点的答案。对于相对较少的记录,无论如何,性能损失都可以忽略不计 - 过早优化和所有这些。
在Ruby中,Enumerable#sort_by
方法将按块的结果对Enumerable进行排序。看起来您希望首先按数字前面的部分(例如"Gain "
,"Reduce body fat by "
)对数值进行排序,然后按数字的整数值(例如Fixnum 10
而不是字符串"10"
)。让我们分成几步:
使用正则表达式很容易:
OUTCOME_PARTS_EXPR = /(\D+)(\d+)/
str = "Gain 10 pounds"
str.match(OUTCOME_PARTS_EXPR).captures
# => [ "Gain ", "10" ]
注意:此正则表达式假定始终是数字之前的某些非数字文本。如果情况并非如此,则可能需要进行一些修改。
使用Enumerable#map
我们可以为整个Enumerable执行此操作,而我们可以将数字转换为Fixnums(例如"10"
到10
):
arr = [
"Gain 10 pounds",
"Gain 15 pounds",
"Gain 1 pound",
"Lose 10 pounds",
"Lose 15 pounds",
"Lose 1 pound",
"Reduce body fat by 15%",
"Reduce body fat by 2%"
]
arr.map do |str|
next [ str ] unless str =~ OUTCOME_PARTS_EXPR
[ $1, $2.to_i ]
end
我们传递给map
的块做了两件事:如果字符串与我们的正则表达式不匹配,它只返回字符串(作为单元素数组)。否则,它返回一个数组,第一个捕获("Gain "
)作为字符串,第二个捕获(10
)作为Fixnum。这是结果:
[ [ "Gain ", 10 ],
[ "Gain ", 15 ],
[ "Gain ", 1 ],
[ "Lose ", 10 ],
[ "Lose ", 15 ],
[ "Lose ", 1 ],
[ "Reduce body fat by ", 15 ],
[ "Reduce body fat by ", 2 ] ]
现在我们知道如何获取我们需要的部分,我们可以将同一个块传递给sort_by
进行排序:
arr.sort_by do |str|
next [ str ] unless str =~ OUTCOME_PARTS_EXPR
[ $1, $2.to_i ]
end
# => [ "Gain 1 pound",
# "Gain 10 pounds",
# "Gain 15 pounds",
# "Lose 1 pound",
# "Lose 10 pounds",
# "Lose 15 pounds",
# "Reduce body fat by 2%",
# "Reduce body fat by 15%" ]
大!但还有最后一步......
我们没有对字符串数组进行排序,我们正在对Target
个对象进行排序。然而,这并不是一个复杂的问题;我们只需要指向正确的属性,即outcome
:
Target.all.sort_by do |target|
outcome = target.outcome
next [ outcome ] unless outcome =~ OUTCOME_PARTS_EXPR
[ $1, $2.to_i ]
end
请注意,我将Target.order(:outcome)
更改为Target.all
。由于我们在Ruby中进行排序,因此在数据库查询中进行排序可能没有意义。
为了清理,你应该把它放在目标模型中,例如:
class Target < ActiveRecord::Base
OUTCOME_PARTS_EXPR = /(\D+)(\d+)/
# ...
def self.sort_by_outcome
all.sort_by do |target|
outcome = target.outcome
next [ outcome ] unless outcome =~ OUTCOME_PARTS_EXPR
[ $1, $2.to_i ]
end
end
end
作为一种类方法,您可以将其称为Target.sort_by_outcome
,或者将其置于Target.where(...).sort_by_outcome
之类的查询的末尾。因此,在您的情况下,您希望将其放在控制器中(根据经验,您不应该在视图中执行ActiveRecord查询):
@targets_sorted = Target.sort_by_outcome
...然后在您看来,您将使用@targets_sorted
代替Target.order(:outcome)
。