162

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?

3
  • 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! Commented Oct 13, 2009 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. Commented Oct 13, 2009 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. Commented Aug 19, 2013 at 11:36

7 Answers 7

239

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!

Sign up to request clarification or add additional context in comments.

5 Comments

Ahhh, I knew I'd seen this before but I couldn't remember where! (Hence my "this doesn't feel right" remark.) Thanks!
One of those lesser documented methods. I went looking for something like this while proposing an answer but didn't see it.
For some reason except didn't work. But except! did. Rails 3.0
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
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
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)

4 Comments

Thanks for mentioning the security issues surrounding redirection.
Just a heads up: ActiveSupport, not Ruby itself, provides Hash#slice and #slice! as.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Hash/…
I couldn't get David James's link to work but this one seems to be ok: api.rubyonrails.org/classes/Hash.html#method-i-slice
undefined method 'slice!' for {:b=>2, :c=>3}:Hash
28

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

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

2 Comments

without modifying Hash this is the best answer :+1:
I've used this method but replaced params with the name of the hash and then it worked!! The hash gets mutated.
15

Another way to phrase dmathieu's answer might be

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

Comments

10

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

Comments

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

5 Comments

Monkey patches are a tool of last resort.
Monkey patches that replace existing functions are a tool of last resort. Monkey patches that add new functions are Ruby 101.
Should be delete(k) instead of delete(key)
For code maintenance the implementation of the non-destructive delete_keys should be simply dup.delete_keys!(*keys)
@Phrogz Defining one in terms of the other isn't always a bad idea, but it's just left here unrolled for clarity.
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.