Ruby on Rails:删除多个哈希键

159

我经常写下如下代码:

params.delete(:controller)  
params.delete(:action)  
params.delete(:other_key)  
redirect_to my_path(params)  

删除的轨迹感觉不对,这样做也不对:

[:controller, :action, :other_key].each do |k|
  params.delete(k)
end

有没有更简单、更干净的方法?


当我写道第二种方法感觉不对时,我的意思是考虑到Hash API的丰富性,我怀疑是否已经存在某种方法或惯用语来解决这个问题,而不需要进行monkey patch。也许并不是这样。非常感谢所有回答的人! - Mark Westling
3
Hash#except 正是我需要的。我没记得它是 Rails 核心扩展,所以当我在 Hash API 中找不到它时感到困惑。 - Mark Westling
2
请注意,严格来说答案是 Hash#except!,但最好使用 Hash#except(不要搞乱 params!)。一般来说,除非绝对必要,否则不要直接修改任何对象,因为副作用可能会导致意外的结果。 - tokland
7个回答

235
我猜你可能不知道ActiveSupport添加到Hash中的Hash#except方法。
它可以让你的代码简化为:
redirect_to my_path(params.except(:controller, :action, :other_key))

此外,您无需进行猴子补丁,因为Rails团队已经为您完成了!


1
啊,我知道我以前见过这个,但我不记得在哪里看过了!(因此我的“这感觉不对”的评论。)谢谢! - Mark Westling
3
其中一种较少记录的方法。当我提出一个答案时寻找类似这样的方法,但没有找到。 - tadman
1
由于某些原因,except 没有起作用。但是 except! 却可以。Rails 3.0 - Trip
4
在ActiveRecord属性中,Rails 3.2版本需要使用字符串作为键,例如User.attributes.except("id", "created_at", "updated_at")。无法使用符号作为键。 - house9
1
除了@house9提到的内容之外,ActiveRecord attributes方法返回一个带有String键的Hash。因此,您必须在.except()中使用字符串键名。但是,我使用Hash.symbolize_keys来解决这个问题,例如@user.attributes.symbolize_keys.except(:password, :notes)--使用symbolize_keys使其按预期工作。 - FireDragon

46

虽然使用Hash#except可以解决您的问题,但请注意它会引入潜在的安全问题。处理来自访问者的任何数据的一个好的经验法则是使用白名单方法。在这种情况下,应该使用Hash#slice

params.slice!(:param_to_keep_1, :param_to_keep_2)
redirect_to my_path(params)

1
感谢您提到有关重定向安全问题的事项。 - David J.
14
提醒一下:Hash#slice和#slice!方法是ActiveSupport提供的,而不是Ruby本身提供的。可以参考这个链接:http://as.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Hash/Slice.html - David J.
1
我无法让David James的链接正常工作,但这个链接似乎没问题:http://api.rubyonrails.org/classes/Hash.html#method-i-slice - Dominic Sayers
未定义方法'slice!'用于{:b=>2, :c=>3}:Hash - Khurram Raza

28

我完全满意你在问题中最初发布的代码。

[:controller, :action, :other_key].each { |k| params.delete(k) }

不修改 Hash,这是最好的答案 :+1: - Dan Bradbury
我使用了这种方法,但将params替换为哈希的名称,然后它就起作用了!哈希被改变了。 - Pablo

15

重新表述dmathieu的答案的另一种方式可能是

params.delete_if { |k,v| [:controller, :action, :other_key].include? k }

7
点火进行猴子补丁?
class Hash
  def delete_keys!(*keys)
    keys.flatten.each do |k|
      delete(k)
    end

    self
  end

  def delete_keys(*keys)
    _dup = dup
    keys.flatten.each do |k|
      _dup.delete(k)
    end

    _dup
  end
end

6
猴子补丁是最后的救命稻草工具。 - Bob Aman
15
替换现有函数的猴子补丁是最后的手段。添加新功能的猴子补丁是 Ruby 的基础。 - David Seiler
4
应该使用 delete(k) 而不是 delete(key) - Vincent
为了进行代码维护,非破坏性的 delete_keys 实现应该简单地是 dup.delete_keys!(*keys) - Phrogz
@Phrogz 将一个定义为另一个并不总是一个坏主意,但这里只是为了清晰起见而未展开。 - tadman

5
从 Ruby 3.0 开始,可以直接支持Hash#except,这意味着我们不需要使用 activesupport 来访问Hash#except
根据文档:
Hash#except(*keys) → hash
该方法返回一个新的哈希表,其中包含除了给定键之外原始哈希表中的所有内容。
例如:
h = { a: 100, b: 200, c: 300, d: 400 }
h.except(:a, :d) #=> {:b=>200, :c=>300}

Reference:

https://docs.ruby-lang.org/en/3.0.0/Hash.html#method-i-except


2

我不知道你认为你提出的解决方案有什么问题。我猜想你想在Hash上添加一个delete_all方法或者类似的东西?如果是这样,tadman的答案提供了解决方案。但是,坦率地说,对于一次性的操作,我认为你的解决方案非常容易理解。如果你经常使用它,你可能需要将其封装在一个帮助方法中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接