159

I often find myself writing this:

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

The trail of deletes doesn't feel right and neither does:

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

Is there anything simpler and cleaner?

Volte
  • 1,905
  • 18
  • 25
Mark Westling
  • 5,904
  • 4
  • 26
  • 30
  • When I wrote that the second approach didn't feel right, I meant that given the richness of the Hash API, I suspected that there was some method or idiom already out there for this and a monkey patch wouldn't be necessary. Maybe not, though. Many thanks to all who answered! – Mark Westling Oct 13 '09 at 15:08
  • 3
    Hash#except was exactly what I was looking for. I didn't remember that it's a Rails core extension so I was puzzled when I couldn't find it in the Hash API. – Mark Westling Oct 13 '09 at 16:15
  • 2
    Note that strictly the answer is `Hash#except!` but `Hash#except` is the way to go (don't mess with `params`!). As a rule of thumb, don't mess with any object in-place unless absolutely required, the side-effects may be have unexpected results. – tokland Aug 19 '13 at 11:36

7 Answers7

234

I'm guessing you're unaware of the Hash#except method ActiveSupport adds to Hash.

It would allow your code to be simplified to:

redirect_to my_path(params.except(:controller, :action, :other_key))

Also, you wouldn't have to monkey patch, since the Rails team did it for you!

Dominic Sayers
  • 1,783
  • 2
  • 20
  • 26
Ben Crouse
  • 8,290
  • 5
  • 35
  • 50
  • 1
    Ahhh, I knew I'd seen this before but I couldn't remember where! (Hence my "this doesn't feel right" remark.) Thanks! – Mark Westling Oct 13 '09 at 16:10
  • 3
    One of those lesser documented methods. I went looking for something like this while proposing an answer but didn't see it. – tadman Oct 13 '09 at 21:41
  • 1
    For some reason except didn't work. But `except!` did. Rails 3.0 – Trip Aug 17 '12 at 16:53
  • 4
    Rails 3.2 on ActiveRecord attributes, had to use strings for the keys? i.e. `User.attributes.except("id", "created_at", "updated_at")` symbols did not work – house9 Dec 18 '12 at 18:18
  • 1
    Adding to what @house9 mentioned, ActiveRecord `attributes` method returns a `Hash` with keys that are `String`. So then you would have to use string key names in `.except()`. However I get around this using the `Hash.symbolize_keys` a la `@user.attributes.symbolize_keys.except(:password, :notes)` -- using `symbolize_keys` makes it work as one would expect – FireDragon Jan 08 '20 at 01:18
46

While using Hash#except handles your problem, be aware that it introduces potential security issues. A good rule of thumb for handling any data from visitors is to use a whitelist approach. In this case, using Hash#slice instead.

params.slice!(:param_to_keep_1, :param_to_keep_2)
redirect_to my_path(params)
Fralcon
  • 489
  • 6
  • 15
28

I'd be completely happy with the code you originally posted in your question.

[:controller, :action, :other_key].each { |k| params.delete(k) }
Bob Aman
  • 32,839
  • 9
  • 71
  • 95
15

Another way to phrase dmathieu's answer might be

params.delete_if { |k,v| [:controller, :action, :other_key].include? k }
Mike Seplowitz
  • 9,785
  • 1
  • 24
  • 23
7

Fire up a monkey patch?

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
MrYoshiji
  • 54,334
  • 13
  • 124
  • 117
tadman
  • 208,517
  • 23
  • 234
  • 262
4

Starting from Ruby 3.0, Hash#except is supported directly. This means we would not need activesupport to access Hash#except.

From documentation:

Hash#except(*keys) → hash

This method returns a new hash, which includes everything from the original hash except the given keys.

example:

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

the_spectator
  • 1,345
  • 11
  • 26
2

I don't know what you think is wrong with your proposed solution. I suppose you want a delete_all method on Hash or something? If so, tadman's answer provides the solution. But frankly, for a one-off, I think your solution is extremely easy to follow. If you're using this frequently, you might want to wrap it up in a helper method.

Community
  • 1
  • 1
Pesto
  • 23,810
  • 2
  • 71
  • 76